Skip to content

Add gamepad/controller support - #1001

Merged
byrongamatos merged 6 commits into
got-feedBack:mainfrom
mhglover:feat/gamepad-support
Jul 19, 2026
Merged

Add gamepad/controller support#1001
byrongamatos merged 6 commits into
got-feedBack:mainfrom
mhglover:feat/gamepad-support

Conversation

@mhglover

@mhglover mhglover commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Full gamepad/controller navigation and playback control — d-pad/stick moves selection everywhere (sidebar, dashboard, song library grid, in-screen drill-downs), face buttons confirm/back/play-pause, consistent "B = back to menu" from any screen.
  • Requested by players who use fee[dB]ack on a TV/console setup, and by wheelchair players for whom a controller in the lap is far more convenient than a keyboard + mouse.
  • Confirmed working end-to-end on a real Steam Deck across several rounds of on-device testing (Export Diagnostics was invaluable for tracking down a couple of real bugs — Steam Input's virtual-pad duplicates, and Chromium not running native button-activation for synthetic input events).

What's included

  • static/v3/gamepad.js (new): polls navigator.getGamepads() and dispatches synthetic key events on the focused element, reusing the app's existing keyboard pipeline (shortcuts.js's scope-aware dispatcher, player shortcuts, text-field/modal guards) instead of a parallel action-mapping table. Only acts on gamepads reporting the W3C "standard" mapping, which is what Steam Input presents for the Deck's controls — button order is guaranteed correct, and anything else safely no-ops.
  • static/v3/gamepad-nav.js (new): fills the one real gap — generic Tab-order emulation (sidebar + screen content) for Arrow keys, explicit .click() activation for Enter/Space (Chromium doesn't run native button activation for untrusted synthetic events), and a consistent Escape/B "go back" fallback. Everything here is gated on !e.isTrusted, so it only ever reacts to gamepad input and never touches real keyboard/mouse users, and defers via e.defaultPrevented to any screen that already handles a key itself.
  • static/v3/songs.js: real 2D d-pad navigation for the song library's virtualized grid (only a slice of the library is ever in the DOM at once).
  • static/v3/index.html: wires up the two new scripts.

For Steam Deck users

Add the AppImage as a non-Steam shortcut in Desktop Mode (or launch from Gaming Mode) so Steam Input provides a standard virtual controller — this is how it's been tested.

Test plan

  • Connect/disconnect a controller — toast appears once (Steam Input's virtual-pad duplicates are deduped), polling starts/stops correctly
  • D-pad/stick navigates the sidebar, dashboard, and every other screen (playlists, plugins, profile, progress, shop) from a cold launch
  • D-pad/stick navigates the song library grid, including scrolling off-screen rows into view
  • Face buttons: confirm/select, play/pause, back
  • B/Escape consistently returns to a navigable menu state from any screen, including one-level-back for artist/album/playlist drill-downs
  • Player screen playback controls (play/pause, seek) unaffected
  • Real (trusted) keyboard/mouse input unaffected — verified no regressions
  • All of the above confirmed on a real Steam Deck

Summary by CodeRabbit

  • New Features
    • Added standard gamepad support for keyboard-style app navigation, including focus traversal and item activation.
    • Enabled Escape to return using the on-screen back control (with fallback to the v3 home screen).
    • Added controller shortcut to reveal the player rail.
    • Added cursor-based keyboard/gamepad navigation for the windowed Songs grid, with automatic scrolling, re-rendering, and toolbar overlap avoidance.
    • Added notifications when a compatible controller connects (and stops handling when it disconnects).

Note on gamepad.js vs. songs.js's keydown handling

gamepad-nav.js's generic listener is gated on !e.isTrusted — it only ever reacts
to gamepad-originated synthetic events, by design, since every other screen already
had real Tab/click keyboard accessibility.

songs.js's grid-cursor listener is not gated the same way, on purpose: the
v3-songs virtualized grid had no keyboard navigation at all before this PR, for
real keyboard users or otherwise. That listener intentionally serves both gamepad and
real keyboard input as one real accessibility fix, not just a gamepad affordance.

Adds full gamepad/controller navigation and playback control, driven
by requests from players who use fee[dB]ack on a TV/console setup and
from wheelchair users for whom a controller is far more convenient
than a keyboard + mouse. Confirmed working end-to-end on a Steam Deck
across several rounds of on-device testing.

- static/v3/gamepad.js: polls navigator.getGamepads() and dispatches
  synthetic keydown events (Arrow/Enter/Space/Escape) on the focused
  element (falling back to document), reusing the app's existing
  keyboard pipeline (static/js/shortcuts.js's scope-aware dispatcher,
  player shortcuts, text-field/modal guards) instead of a parallel
  action-mapping table. Only acts on gamepads reporting the W3C
  "standard" mapping — which is what Steam Input presents for the
  Deck's built-in controls, both in Gaming Mode and in Desktop Mode
  via a non-Steam shortcut — so button order is guaranteed correct
  and a non-standard/raw device safely no-ops instead of misfiring.
  Handles Steam Input's virtual-pad duplicates (a real controller
  plus 1-2 mirrored XInput slots) without spamming connect toasts or
  losing input when the live pad isn't at index 0. Xbox-style face
  button mapping: bottom face = Space (play/pause, and activates the
  focused control), right face = Escape (back), top face reveals the
  player screen's tool rail (focuses it into visibility via the
  existing CSS :focus-within rule). D-pad/stick repeat while held,
  mirroring OS keyboard auto-repeat.

- static/v3/gamepad-nav.js: fills the one real gap in that reuse
  strategy — no screen but the song library grid had any arrow-key
  navigation, and Chromium doesn't run native Enter/Space button
  activation for untrusted synthetic events even when dispatched at
  the focused element. Gated entirely on `!e.isTrusted`, so it only
  ever reacts to gamepad-originated events and never touches real
  keyboard/mouse users: emulates Tab-order (the sidebar + active
  screen's real, already-focusable buttons/links) for Arrow keys,
  explicitly .click()s the focused element for Enter/Space, and gives
  Escape a consistent "go back" behavior — an existing in-screen back
  button if one's visible (reusing each screen's own drill-down logic
  for free), else the main menu. Every branch defers via
  `e.defaultPrevented` to any screen that already handles the key
  itself (the song grid, the player, settings), so nothing here
  overrides existing behavior.

- static/v3/songs.js: adds real 2D d-pad/arrow-key navigation to the
  song library's virtualized grid (only a slice of the library is
  ever in the DOM), including fetching/scrolling off-screen rows into
  view and correcting for the sticky filter toolbar's occlusion.

- static/v3/index.html: wires up the two new scripts.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@byrongamatos, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 11607890-22a6-46e6-80e4-37e55fbd48d8

📥 Commits

Reviewing files that changed from the base of the PR and between 346c0e1 and 5a16447.

📒 Files selected for processing (5)
  • static/v3/gamepad.js
  • static/v3/index.html
  • static/v3/songs.js
  • tests/js/gamepad.test.js
  • tests/js/gamepad_nav.test.js
📝 Walkthrough

Walkthrough

Changes

V3 now supports standard gamepad input through synthetic keyboard events, global focus traversal, Escape navigation, and cursor-based Songs grid navigation with lazy window loading and playback activation.

V3 gamepad navigation

Layer / File(s) Summary
Gamepad input and startup wiring
static/v3/gamepad.js, static/v3/index.html
Standard gamepads are polled, mapped to synthetic key events, and loaded through deferred V3 scripts.
Shell focus navigation
static/v3/gamepad-nav.js
Synthetic Enter, Space, Escape, and Arrow events activate controls, navigate back, and traverse visible focusable elements within modal or screen scopes.
Songs grid cursor navigation
static/v3/songs.js
The Songs grid tracks and highlights a cursor, loads and scrolls target rows, and activates playback while excluding interactive controls and dialogs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Gamepad
  participant gamepadjs
  participant SongsGrid
  participant FocusNavigation
  Gamepad->>gamepadjs: provide standard button or direction state
  gamepadjs->>SongsGrid: dispatch synthetic Arrow or activation keydown
  SongsGrid->>SongsGrid: move cursor, load window, or activate playback
  gamepadjs->>FocusNavigation: dispatch synthetic navigation keydown
  FocusNavigation->>FocusNavigation: traverse focus, activate control, or navigate back
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change well, but it doesn't follow the required template sections and omits the checklist/feedpak surface details. Add the required What, feedpak surface (or explicitly omit it if irrelevant), and Checklist sections, including changelog, tests, and DCO sign-off.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title matches the main change: adding gamepad/controller support across v3.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
static/v3/songs.js (1)

4092-4096: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Rename the new helpers to camelCase.

Use names such as gpCardEl, gpApplyHighlight, gpEnsureVisible, gpMove, gpActivate, and gpBlockedTarget.

As per coding guidelines, static/**/*.js: “Use camelCase for JavaScript functions.”

Also applies to: 4109-4109, 4142-4162

🤖 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 `@static/v3/songs.js` around lines 4092 - 4096, Rename the newly added helper
functions in the songs grid navigation flow from underscore-prefixed names to
camelCase, including _gpCardEl, _gpApplyHighlight, _gpEnsureVisible, _gpMove,
_gpActivate, and _gpBlockedTarget. Update every reference and invocation
consistently while preserving their existing behavior.

Source: Coding guidelines

🤖 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 `@static/v3/gamepad-nav.js`:
- Around line 67-83: Update the Escape handling in the key listener to inspect
all matching back buttons rather than only the first querySelector result.
Select and click the first visible element matching [data-ap-back],
[data-albums-back], or `#v3-pl-back`; only call window.showScreen('v3-home') when
none of the matching buttons are visible.

In `@static/v3/gamepad.js`:
- Around line 133-139: Update anyLiveConnectedPad and the gamepad connection
handlers around the related lifecycle logic to consider only pads whose mapping
is "standard". Apply the same filter when announcing connect/disconnect events
and when deciding whether to keep the animation loop active, so non-standard raw
mirrors cannot suppress the supported virtual pad’s disconnect notification.

In `@static/v3/songs.js`:
- Around line 4170-4175: Update the keydown listener around songsActive and
state.view so grid cursor handling runs only for synthetic gamepad events,
excluding trusted physical keyboard events before preventDefault is called.
Preserve the existing activation and arrow-key filtering for eligible synthetic
events.
- Around line 4162-4172: Update _gpBlockedTarget to block INPUT, SELECT,
TEXTAREA, and BUTTON elements only when they are inside `#v3-songs`, while
preserving the existing contenteditable and visible-overlay blocking behavior.
Ensure a focused external launching button does not prevent subsequent Songs
gamepad navigation in the keydown handler.
- Around line 4142-4147: Update _gpMove so an unset cursor selects the first
card (index 0) before applying the movement delta, rather than using the delta
as the initial index. Preserve the existing clamping to the valid range and
no-op behavior when movement cannot change the current index.

---

Nitpick comments:
In `@static/v3/songs.js`:
- Around line 4092-4096: Rename the newly added helper functions in the songs
grid navigation flow from underscore-prefixed names to camelCase, including
_gpCardEl, _gpApplyHighlight, _gpEnsureVisible, _gpMove, _gpActivate, and
_gpBlockedTarget. Update every reference and invocation consistently while
preserving their existing 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 Plus

Run ID: 310fd650-360c-4024-b3a3-f3f35079ef9f

📥 Commits

Reviewing files that changed from the base of the PR and between 1c077c9 and 05afe7f.

📒 Files selected for processing (4)
  • static/v3/gamepad-nav.js
  • static/v3/gamepad.js
  • static/v3/index.html
  • static/v3/songs.js

Comment thread static/v3/gamepad-nav.js
Comment thread static/v3/gamepad.js Outdated
Comment thread static/v3/songs.js Outdated
Comment thread static/v3/songs.js Outdated
Comment on lines +4162 to +4172
function _gpBlockedTarget(el) {
if (!el) return false;
if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(el.tagName)) return true;
if (el.isContentEditable) return true;
if (el.closest && el.closest('[role="dialog"], .feedBack-modal, #lib-filter-drawer')) return true;
return false;
}

document.addEventListener('keydown', (e) => {
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not let the launching button disable Songs navigation.

gamepad-nav.js activates controls using .click() but leaves focus on them. If a dashboard or navigation button opens Songs, Line 4172 blocks every subsequent gamepad key because that external button remains document.activeElement.

Restrict form/button blocking to controls inside #v3-songs, while continuing to block visible overlays.

Proposed fix
     function _gpBlockedTarget(el) {
         if (!el) return false;
-        if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(el.tagName)) return true;
-        if (el.isContentEditable) return true;
         if (el.closest && el.closest('[role="dialog"], .feedBack-modal, `#lib-filter-drawer`')) return true;
+        if (!el.closest || !el.closest('`#v3-songs`')) return false;
+        if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(el.tagName)) return true;
+        if (el.isContentEditable) return true;
         return false;
     }
📝 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.

Suggested change
function _gpBlockedTarget(el) {
if (!el) return false;
if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(el.tagName)) return true;
if (el.isContentEditable) return true;
if (el.closest && el.closest('[role="dialog"], .feedBack-modal, #lib-filter-drawer')) return true;
return false;
}
document.addEventListener('keydown', (e) => {
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;
function _gpBlockedTarget(el) {
if (!el) return false;
if (el.closest && el.closest('[role="dialog"], .feedBack-modal, `#lib-filter-drawer`')) return true;
if (!el.closest || !el.closest('`#v3-songs`')) return false;
if (['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(el.tagName)) return true;
if (el.isContentEditable) return true;
return false;
}
document.addEventListener('keydown', (e) => {
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;
🤖 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 `@static/v3/songs.js` around lines 4162 - 4172, Update _gpBlockedTarget to
block INPUT, SELECT, TEXTAREA, and BUTTON elements only when they are inside
`#v3-songs`, while preserving the existing contenteditable and visible-overlay
blocking behavior. Ensure a focused external launching button does not prevent
subsequent Songs gamepad navigation in the keydown handler.

Comment thread static/v3/songs.js Outdated
Comment on lines +4170 to +4175
document.addEventListener('keydown', (e) => {
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;
const isActivate = e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar';
if (!isActivate && !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) return;
e.preventDefault();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Restrict grid cursor handling to synthetic gamepad events.

This listener also intercepts trusted keyboard arrows, Enter, and Space and calls preventDefault(), contradicting the synthetic-only contract and changing physical keyboard behavior.

Proposed fix
     document.addEventListener('keydown', (e) => {
+        if (e.isTrusted) return;
         if (!songsActive() || state.view !== 'grid') return;
📝 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.

Suggested change
document.addEventListener('keydown', (e) => {
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;
const isActivate = e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar';
if (!isActivate && !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) return;
e.preventDefault();
document.addEventListener('keydown', (e) => {
if (e.isTrusted) return;
if (!songsActive() || state.view !== 'grid') return;
if (_gpBlockedTarget(document.activeElement)) return;
const isActivate = e.key === 'Enter' || e.key === ' ' || e.key === 'Spacebar';
if (!isActivate && !['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.key)) return;
e.preventDefault();
🤖 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 `@static/v3/songs.js` around lines 4170 - 4175, Update the keydown listener
around songsActive and state.view so grid cursor handling runs only for
synthetic gamepad events, excluding trusted physical keyboard events before
preventDefault is called. Preserve the existing activation and arrow-key
filtering for eligible synthetic events.

@mhglover
mhglover force-pushed the feat/gamepad-support branch from fe65d34 to 05afe7f Compare July 18, 2026 12:09
Rebuilt in a fresh clone (not the local working copy). Several plugin
directories (audio_engine, plugin_manager, community_charts, etc.) are
gitignored locally but present on disk from checking out plugin repos
for local dev/testing — Tailwind's content scan picks them up
regardless, so a rebuild against the contaminated local working copy
bakes in extra utility classes that don't belong in the real,
git-tracked build. A clean checkout reproduces CI's expected output
exactly.
@mhglover
mhglover force-pushed the feat/gamepad-support branch from b06eb43 to a58d475 Compare July 18, 2026 12:49
mhglover added 2 commits July 18, 2026 09:16
document.querySelector on the combined [data-ap-back], [data-albums-back],
#v3-pl-back selector only ever inspects the first match in DOM order —
since screens stay in the DOM (hidden, not removed) when you navigate
away, a hidden back button from an unrelated screen could sort before
the one that's actually visible, incorrectly falling through to
showScreen('v3-home') instead of clicking it. Uses querySelectorAll +
find(visible) instead.
…rid nav

- gamepad.js: anyLiveConnectedPad -> anyLiveStandardPad, filtering by
  mapping === 'standard' like firstLiveStandardPad already does, and
  applied at the top of the gamepadconnected handler too. A still-
  connected non-standard raw mirror could otherwise mask the real
  pad's disconnect (toast never fires, polling never stops).

- songs.js _gpMove: an unset cursor now always seeds at index 0
  before the first press, instead of applying that press's delta
  immediately (ArrowDown/Right previously skipped straight past row
  0; Left/Up only looked right by accident of clamping). Matches the
  existing convention in shortcuts.js's legacy _handleLibArrowNav.

- songs.js _gpBlockedTarget: form-control/button blocking now
  requires the element to be visible (offsetParent !== null), not
  just present. Screens stay in the DOM hidden (not removed) when you
  navigate away, so a real button focused on some other now-hidden
  screen could leave document.activeElement pointing at it and block
  all grid navigation indefinitely. (An el.closest('#v3-songs') scope
  was tried first and reverted — it fixed that case but broke
  blocking for the topbar search input, which lives outside
  #v3-songs's DOM subtree even while v3-songs is active; visibility
  is the distinction that actually matters, not DOM nesting.)

Skipped two CodeRabbit suggestions, verified against current code:
gating songs.js's grid keydown listener to synthetic-only events
would regress the real keyboard accessibility this PR intentionally
added (v3-songs' grid had none before); renaming the _gp* helpers to
drop their underscore prefix would break from this codebase's own
established module-private naming convention.

Verified in-browser: first arrow press lands on index 0, stale hidden
focus no longer blocks grid nav, the topbar search input still
correctly blocks it, and normal nav resumes after blur.
byrongamatos and others added 2 commits July 19, 2026 11:46
- songs.js: CRLF-normalize 3-way merge (branch was LF vs main CRLF)
- fix ring-fb-accent -> ring-fb-primary (accent was never a configured
  Tailwind color; primary is the grid's existing selection-ring class)
- drop the unnecessary tailwind.min.css rebuild (no new generatable class)
- strip left-in [gamepad] console.log debug statements

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Byron Gamatos <xasiklas@gmail.com>
- gamepad.test.js (10): standard-mapping filter, Steam Input duplicate-slot
  dedup, disconnect masking, button edge-detection, d-pad/stick repeat timing,
  analog deadzone — driven via a fake navigator + manual rAF queue.
- gamepad_nav.test.js (10): !isTrusted/defaultPrevented gating, arrow focus
  traversal + clamping, hidden-element skipping, Enter/Space click activation
  (not into text fields/body), Escape visible-back-button vs home fallback.

songs.js grid nav is left to on-device coverage (async + windowed-DOM heavy).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Byron Gamatos <xasiklas@gmail.com>
@byrongamatos
byrongamatos merged commit 23c5093 into got-feedBack:main Jul 19, 2026
6 checks passed
@mhglover
mhglover deleted the feat/gamepad-support branch July 22, 2026 23:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants