Skip to content

Input handler.ts rework#4225

Open
TKTK123456 wants to merge 35 commits into
openfrontio:mainfrom
TKTK123456:InputHandler.ts-Rework
Open

Input handler.ts rework#4225
TKTK123456 wants to merge 35 commits into
openfrontio:mainfrom
TKTK123456:InputHandler.ts-Rework

Conversation

@TKTK123456

@TKTK123456 TKTK123456 commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Before opening a PR: discuss new features on Discord first, and file bugs or small improvements as issues. You must be assigned to an approved issue — unsolicited PRs will be auto-closed.

Add approved & assigned issue number here:

Resolves #4193

Description:

Use activeKeys set in places where it is checking if a key is being pressed in a different way, and it makes more sense to use the activeKeys set. Make the overall code of the InputHandler.ts file more consistent and to make it easier to add new keybinds in the future.

Screenshot from 2026-06-13 20-49-56

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory

Please put your Discord username so you can be contacted if a bug or regression is found:

tktk1234567

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 577587c8-47c0-43f5-b47a-be6814fdc6df

📥 Commits

Reviewing files that changed from the base of the PR and between 817e5a7 and 464c0b8.

📒 Files selected for processing (1)
  • tests/InputHandler.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/InputHandler.test.ts

Walkthrough

InputHandler now uses a registry for keyboard shortcuts. It stores keybind handlers with conditions, matches keys with real or synthesized events, tracks Alt keys in activeKeys, runs handlers from the registry-based keydown flow, and exposes the Alt-key modifier as a user-configurable setting for graphics refresh.

Changes

Keyboard event data-driven refactoring and Alt-key configuration

Layer / File(s) Summary
Registry type and infrastructure
src/client/InputHandler.ts
Adds the keybind entry shape with handler and condition list, introduces a keybind-to-entry registry, generalizes key matching to accept either keyboard events or synthesized {code, shiftKey} objects, and provides a helper method for appending entries to the registry.
Build-key matching refinement
src/client/InputHandler.ts
Removes the strict build-key exact-match helper and updates build-key resolution to use the generalized matching function with synthesized events, leaving digit and Numpad alias matching as the remaining fallback logic.
Key tracking and modifier detection
src/client/InputHandler.ts
Records Alt key presses in activeKeys by storing the configured Alt-code value (AltLeft or AltRight), removes the separate Alt-held helper function, and routes Alt-based action checks through registry-based condition predicates.
Initialize: register all keybinds
src/client/InputHandler.ts
Registers shortcut handlers through the new registry helper, including repeat-gated actions for pause and game-speed, Alt-gated refresh graphics, forced toggleView state changes, and per-build-type ghost activation with condition gating.
Event dispatch via registry iteration
src/client/InputHandler.ts
Replaces the keydown dispatch chain with registry iteration, key matching against each entry, condition predicate evaluation, preventDefault calls, and handler execution when all predicates pass.
User settings and UI for Alt-key modifier
src/core/game/UserSettings.ts, src/client/UserSettingModal.ts, resources/lang/en.json
Adds a default altKey binding to AltLeft in user settings, includes a keybinds UI entry for the Alt modifier with label and description, handles keybind conflict detection by allowing the AltLeft swap between emojiMenuModifier and altKey, and provides translations for the graphics refresh modifier label and description.
Test coverage for registry behavior and build keys
tests/InputHandler.test.ts
Verifies that digit keys trigger correct ghost structure placement when configured as Numpad aliases, and validates that the keybind registry stores multiple handlers for the same key string without overwrites.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Browser
  participant InputHandler as InputHandler.keydown
  participant Registry as keybindAndEvent Array
  participant Matcher as keybindMatchesEvent
  participant Active as activeKeys Set
  participant Handler as registered handler

  Browser->>InputHandler: keydown (KeyboardEvent)
  InputHandler->>Active: add configured Alt code when pressed
  InputHandler->>Registry: iterate entries
  Registry->>Matcher: match entry against event or synthesized object
  Matcher->>Active: consult activeKeys for modifier predicates
  InputHandler->>Registry: evaluate predicates, call preventDefault()
  Registry->>Handler: invoke handler when predicates pass
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • openfrontio/OpenFrontIO#4175: Both PRs modify InputHandler to rely on active key tracking and adjust build/emoji menu keybind handling using tracked keys instead of separate modifier helpers.

Suggested reviewers

  • evanpelle

Poem

Keys once tangled, now in rows aligned,
A map of binds, each handler signed.
Alt and Shift tracked, conditions meet,
Users choose—graphics refresh complete.
Input sings—clean logic, rhythm, and mind. 🎵

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Input handler.ts rework' is vague and generic, describing only the file affected rather than what was changed or improved. Make the title more specific about the main change, such as 'Consolidate keybind dispatch with data-driven registry' or 'Refactor InputHandler to unify key-press detection using activeKeys'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description check ✅ Passed The description relates to the changeset by explaining the refactoring goal (consolidating keybind detection with activeKeys and improving consistency), matching the objectives in issue #4193.
Linked Issues check ✅ Passed The changes address issue #4193's three objectives: using activeKeys more consistently [InputHandler.ts], making code structure uniform through a data-driven registry pattern [InputHandler.ts, UserSettingModal.ts, UserSettings.ts], and simplifying keybind registration via a single registry mechanism [InputHandler.ts].
Out of Scope Changes check ✅ Passed All changes align with the refactoring scope: InputHandler restructures keybind dispatch logic, UserSettingModal adds UI support for the new altKey setting, UserSettings defines its default, en.json provides translations, and tests validate digit/Numpad aliasing and multi-handler registry behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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 and usage tips.

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/client/InputHandler.ts (1)

1-1: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Prettier formatting issues reported by CI.

Run npx prettier --write src/client/InputHandler.ts to fix the code style issues flagged by the pipeline.

🤖 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/client/InputHandler.ts` at line 1, This file has Prettier style
violations; run the formatter and commit the changes by executing npx prettier
--write src/client/InputHandler.ts (or run the project's format script) to
update formatting for the InputHandler module; after formatting, verify that
imports like EventBus and GameEvent and any exported class/function in
InputHandler.ts still compile and then re-run CI.

Source: Pipeline failures

🧹 Nitpick comments (1)
src/client/InputHandler.ts (1)

929-941: ⚡ Quick win

Typo in JSDoc and consider a typed structure.

  1. Line 932: "exectued""executed"

  2. The array-based convention [action, ...conditions] works but requires knowing the index contract. A typed object would be clearer and catch misuse at compile time:

interface KeybindEntry {
  action: (e: KeyboardEvent) => void;
  conditions?: Array<(e: KeyboardEvent) => boolean>;
}
private keybindAndEvent = new Map<string, KeybindEntry>();

This makes the structure explicit and gives proper types for actions (void) vs conditions (boolean).

🔤 Minimal fix for typo
-   * `@param` event The code to be exectued when this keybind is pressed
+   * `@param` event The code to be executed when this keybind is pressed
🤖 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/client/InputHandler.ts` around lines 929 - 941, Fix the JSDoc typo
("exectued" → "executed") and replace the fragile array convention used by
keybindAndEvent with a typed object: define a KeybindEntry (with action: (e:
KeyboardEvent) => void and optional conditions: Array<(e: KeyboardEvent) =>
boolean>), change the Map type to Map<string, KeybindEntry>, and update
addKeybindAndEvent to accept an action and conditions with those explicit types
(use the symbol addKeybindAndEvent and keybindAndEvent to locate the code); also
update any code that reads from keybindAndEvent to use entry.action and
entry.conditions and adjust return/usage expectations accordingly.
🤖 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 `@src/client/InputHandler.ts`:
- Around line 580-592: Rename the misspelled local variable
allConditionsFullfiled to allConditionsFulfilled in the loop inside the
InputHandler logic that iterates over this.keybindAndEvent (the block that uses
keybindMatchesEvent and item[1].slice(1)), updating every reference (assignment,
checks, and the continue branch) to the new name to keep readability consistent;
ensure the same corrected name is used throughout that function scope and no
other behavior changes are introduced.

---

Outside diff comments:
In `@src/client/InputHandler.ts`:
- Line 1: This file has Prettier style violations; run the formatter and commit
the changes by executing npx prettier --write src/client/InputHandler.ts (or run
the project's format script) to update formatting for the InputHandler module;
after formatting, verify that imports like EventBus and GameEvent and any
exported class/function in InputHandler.ts still compile and then re-run CI.

---

Nitpick comments:
In `@src/client/InputHandler.ts`:
- Around line 929-941: Fix the JSDoc typo ("exectued" → "executed") and replace
the fragile array convention used by keybindAndEvent with a typed object: define
a KeybindEntry (with action: (e: KeyboardEvent) => void and optional conditions:
Array<(e: KeyboardEvent) => boolean>), change the Map type to Map<string,
KeybindEntry>, and update addKeybindAndEvent to accept an action and conditions
with those explicit types (use the symbol addKeybindAndEvent and keybindAndEvent
to locate the code); also update any code that reads from keybindAndEvent to use
entry.action and entry.conditions and adjust return/usage expectations
accordingly.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 09de9720-a581-40ca-9829-091e215d1987

📥 Commits

Reviewing files that changed from the base of the PR and between 1db02ac and d6b9cd9.

📒 Files selected for processing (1)
  • src/client/InputHandler.ts

Comment thread src/client/InputHandler.ts Outdated
@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jun 11, 2026
@TKTK123456

Copy link
Copy Markdown
Contributor Author

@coderabbitai help

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

CodeRabbit commands

These commands are invoked using PR/Issue comments.

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai rate limit to show your current review rate limit status.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai evaluate custom pre-merge check --instructions <custom-checks-instructions> --name <custom-checks-title> [--mode <error|warning>] to validate the custom pre-merge checks instructions. Defaults to error when --mode is omitted.
  • @coderabbitai ignore pre-merge checks to override pre-merge checks and get an approval on PR.
  • @coderabbitai run pre-merge checks to run pre-merge checks on the pull request.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai emit path instructions to emit generated path-specific review instructions for this repository.
  • @coderabbitai resolve merge conflict to automatically resolve merge conflicts.
  • @coderabbitai autofix to automatically fix issues identified in unresolved review comments.
  • @coderabbitai resolve to resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai generate configuration to create a PR that adds the current resolved configuration as .coderabbit.yaml (or show it if already present).
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit configuration file (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, support, documentation and community

  • Visit our status page to check the current availability of CodeRabbit.
  • Create a ticket on our support page for assistance with any issues or questions.
  • Visit our documentation site for detailed information on how to use CodeRabbit.
  • Join our Discord community to connect with other users and get help from the community.
  • Follow us on X/Twitter for updates and announcements.

@TKTK123456

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

@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: 3

🤖 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 `@src/client/InputHandler.ts`:
- Around line 317-333: Prettier formatting errors are caused by the block that
defines and transforms buildKeybinds in InputHandler.ts; reformat this section
(variable buildKeybinds, the .map using this.keybinds, the push of numpad keys,
and the for-loop) to match the project's Prettier rules by running the formatter
(run `prettier --write src/client/InputHandler.ts`) or apply equivalent
formatting changes so the file passes CI.
- Around line 317-333: The build keybind registration only adds Numpad0..Numpad9
extras so pressing Digit1 when the keybind is set to Numpad1 bypasses the
fallback; update the registration logic in InputHandler (the buildKeybinds array
mapping and the subsequent push of extras) to also include the Digit0..Digit9
counterparts for each Numpad key (or add a small helper that, for any "NumpadX"
adds both "NumpadX" and "DigitX") so resolveBuildKeybind() will be reached on
both Numpad and Digit keyups; apply the same dual-registration change to the
analogous block around lines 335-347 to cover all build-related keybinds.
- Around line 299-301: Remove the debug console.log calls from the input hot
paths: delete the console.log("TogglePerformanceOverlayEvent") inside the
addKeybindAndEvent handler that emits TogglePerformanceOverlayEvent (and any
other console.log added in the same file during init, e.g., the one around line
334-335). Locate the usages of addKeybindAndEvent and the init/startup code in
InputHandler (search for the string "TogglePerformanceOverlayEvent" and
"console.log") and simply remove the console.log statements so the handler only
calls this.eventBus.emit(new TogglePerformanceOverlayEvent()) and initialization
code has no noisy logs.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d0ddfca9-2f39-45ab-94f4-fd62e41f7d9b

📥 Commits

Reviewing files that changed from the base of the PR and between 1db02ac and d8e52eb.

📒 Files selected for processing (1)
  • src/client/InputHandler.ts

Comment thread src/client/InputHandler.ts
Comment thread src/client/InputHandler.ts
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 12, 2026
@TKTK123456

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 14, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 14, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 14, 2026
@iiamlewis

Copy link
Copy Markdown
Contributor

@TKTK123456 (via Discord):

I do want this to be implemented by v32 or early v33

@iiamlewis

Copy link
Copy Markdown
Contributor

@TKTK123456 (via Discord):

And think it should be

@iiamlewis

Copy link
Copy Markdown
Contributor

@TKTK123456 (via Discord):

<@516828441065160714>

@Celant Celant left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-reviewed against the previous round — this is a big step up, nice work. Both of the critical issues are genuinely fixed:

  • Same-key collisions no longer drop handlers. Switching keybindAndEvent to an array + iterating every entry (with continue outerLoop on a failed condition) restores the old "all matching actions fire" behavior. This was the main one.
  • Digit↔Numpad aliasing works both ways again now that both Digit0–9 and Numpad0–9 are registered as triggers.

Also resolved: the typed KeybindEntry (no more item[1][0]/.slice(1)), early-exit on the first failed condition, the allConditionsFullfiled typo, the debug console.log, and the junk-entry cleanup via the Set. And making altKey a real rebindable setting is a nicer fix than the old isAltKeyHeld — it also fixes the latent bug where Alt+R did nothing for default users.

The one thing I'd block on: tests

This is the same ask as last round — the diff touches 4 files but adds no tests, and the checklist still says tests were added. The two bugs you just fixed are exactly the kind a couple of unit tests would lock down so they can't regress.

Here's a bare-bones starting point (mirrors the (x as any) private-poke style used in ActionableEventsAlliance.test.ts) — it guards the collision fix specifically. It's deliberately minimal; the real value is extending it:

import { InputHandler } from "../../src/client/InputHandler";

describe("InputHandler keybind registry", () => {
  function makeHandler() {
    return new InputHandler(
      {} as any, // gameView
      {} as any, // uiState
      document.createElement("div"), // canvas
      {} as any, // eventBus
    );
  }

  test("two actions bound to the same key are both kept (no overwrite)", () => {
    const ih = makeHandler() as any;
    ih.keybindAndEvent = [];
    ih.addKeybindAndEvent("KeyX", () => {});
    ih.addKeybindAndEvent("KeyX", () => {});

    const entries = ih.keybindAndEvent.filter(([k]: [string, unknown]) => k === "KeyX");
    expect(entries.length).toBe(2); // would have been 1 with the old Map
  });
});

From here the cases worth adding (leaving these to you): a synthesized keydown actually dispatches the matching handler; a failing condition skips it; pressing Digit1 resolves a build bound to Numpad1 and vice-versa. Those last two are what would have caught the original bugs automatically.

A few smaller things — mostly just confirm the intent

These came out of the refactor and may well be fine, but they're behavior changes from the old code, so flagging to make sure they're deliberate rather than accidental:

  1. selectAllWarships now requires Shift to be up. The old code matched on raw e.code and ignored Shift, so it fired even with Shift held; the new plain registration goes through keybindMatchesEvent, which requires shiftKey === false. Is dropping the Shift-held case intended?
  2. Alt+Shift+R no longer refreshes graphics, same mechanism — resetKey only matches with Shift up, whereas the old isAltKeyHeld path ignored Shift. Intended?
  3. Only the configured altKey (default AltLeft) is tracked, so AltRight no longer works as the refresh modifier. The old isAltKeyHeld keyed off event.altKey, which is true for either physical Alt. Worth a conscious decision since some players use right-Alt.

Minor nits (non-blocking)

  • The null-filter doesn't catch what it looks like: this.keybinds[i] returns undefined (not null) for a missing key, so .filter((v): v is string => v !== null) lets undefined/"Shift+undefined" through. Harmless (they never match), but != null or Boolean(v) would actually clean them.
  • The conflict-suppression in UserSettingModal is hardcoded to the emojiMenuModifier/altKey pair against the literal "AltLeft". It works because both default to AltLeft, but it'll break silently if a default changes and doesn't generalize to other overlapping modifiers — a general "modifiers may overlap" rule would be sturdier.
  • addKeybindAndEvent's parameters are still typed (type: KeyboardEvent) => any with the param named type (the stored KeybindEntry is correctly typed now), and the JSDoc still says "exectued". Cosmetic.
  • Optional: dispatch is now a linear scan of ~75 entries calling parseKeybind on every keydown. A Map<normalizedKey, entry[]> would be O(1) and keep the all-matches behavior. Not blocking.

Overall the core refactor looks good to me — add the tests and confirm the three Shift/Alt behavior questions above and I think this is in good shape.

@TKTK123456

Copy link
Copy Markdown
Contributor Author

Re-reviewed against the previous round — this is a big step up, nice work. Both of the critical issues are genuinely fixed:

* **Same-key collisions no longer drop handlers.** Switching `keybindAndEvent` to an array + iterating every entry (with `continue outerLoop` on a failed condition) restores the old "all matching actions fire" behavior. This was the main one.

* **Digit↔Numpad aliasing works both ways again** now that both `Digit0–9` and `Numpad0–9` are registered as triggers.

Also resolved: the typed KeybindEntry (no more item[1][0]/.slice(1)), early-exit on the first failed condition, the allConditionsFullfiled typo, the debug console.log, and the junk-entry cleanup via the Set. And making altKey a real rebindable setting is a nicer fix than the old isAltKeyHeld — it also fixes the latent bug where Alt+R did nothing for default users.

The one thing I'd block on: tests

This is the same ask as last round — the diff touches 4 files but adds no tests, and the checklist still says tests were added. The two bugs you just fixed are exactly the kind a couple of unit tests would lock down so they can't regress.

Here's a bare-bones starting point (mirrors the (x as any) private-poke style used in ActionableEventsAlliance.test.ts) — it guards the collision fix specifically. It's deliberately minimal; the real value is extending it:

import { InputHandler } from "../../src/client/InputHandler";

describe("InputHandler keybind registry", () => {
  function makeHandler() {
    return new InputHandler(
      {} as any, // gameView
      {} as any, // uiState
      document.createElement("div"), // canvas
      {} as any, // eventBus
    );
  }

  test("two actions bound to the same key are both kept (no overwrite)", () => {
    const ih = makeHandler() as any;
    ih.keybindAndEvent = [];
    ih.addKeybindAndEvent("KeyX", () => {});
    ih.addKeybindAndEvent("KeyX", () => {});

    const entries = ih.keybindAndEvent.filter(([k]: [string, unknown]) => k === "KeyX");
    expect(entries.length).toBe(2); // would have been 1 with the old Map
  });
});

From here the cases worth adding (leaving these to you): a synthesized keydown actually dispatches the matching handler; a failing condition skips it; pressing Digit1 resolves a build bound to Numpad1 and vice-versa. Those last two are what would have caught the original bugs automatically.

A few smaller things — mostly just confirm the intent

These came out of the refactor and may well be fine, but they're behavior changes from the old code, so flagging to make sure they're deliberate rather than accidental:

1. **`selectAllWarships` now requires Shift to be _up_.** The old code matched on raw `e.code` and ignored Shift, so it fired even with Shift held; the new plain registration goes through `keybindMatchesEvent`, which requires `shiftKey === false`. Is dropping the Shift-held case intended?

2. **Alt+Shift+R no longer refreshes graphics**, same mechanism — `resetKey` only matches with Shift up, whereas the old `isAltKeyHeld` path ignored Shift. Intended?

3. **Only the configured `altKey` (default `AltLeft`) is tracked, so `AltRight` no longer works** as the refresh modifier. The old `isAltKeyHeld` keyed off `event.altKey`, which is true for either physical Alt. Worth a conscious decision since some players use right-Alt.

Minor nits (non-blocking)

* The null-filter doesn't catch what it looks like: `this.keybinds[i]` returns `undefined` (not `null`) for a missing key, so `.filter((v): v is string => v !== null)` lets `undefined`/`"Shift+undefined"` through. Harmless (they never match), but `!= null` or `Boolean(v)` would actually clean them.

* The conflict-suppression in `UserSettingModal` is hardcoded to the `emojiMenuModifier`/`altKey` pair against the literal `"AltLeft"`. It works because both default to `AltLeft`, but it'll break silently if a default changes and doesn't generalize to other overlapping modifiers — a general "modifiers may overlap" rule would be sturdier.

* `addKeybindAndEvent`'s parameters are still typed `(type: KeyboardEvent) => any` with the param named `type` (the stored `KeybindEntry` is correctly typed now), and the JSDoc still says "exectued". Cosmetic.

* Optional: dispatch is now a linear scan of ~75 entries calling `parseKeybind` on every keydown. A `Map<normalizedKey, entry[]>` would be O(1) and keep the all-matches behavior. Not blocking.

Overall the core refactor looks good to me — add the tests and confirm the three Shift/Alt behavior questions above and I think this is in good shape.

The three shift/alt behaviour is intentional, for the shift behaviour it avoids conflicts with other keybinds and the alt behaviour matches the help menu

@TKTK123456

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Reviews resumed.

@TKTK123456 TKTK123456 requested a review from Celant June 16, 2026 19:31

@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: 1

🧹 Nitpick comments (1)
tests/InputHandler.test.ts (1)

640-653: ⚡ Quick win

Use the shared setup() helper for these new tests.

These new test blocks instantiate InputHandler directly instead of using tests/util/Setup.ts. Please migrate them to setup() so they run through the standard game simulation wiring and stay consistent with repo test conventions.

As per coding guidelines: "tests/**/*.test.ts: Use the setup() helper from tests/util/Setup.ts to create test game instances and exercise core simulation directly."

Also applies to: 674-680

🤖 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 `@tests/InputHandler.test.ts` around lines 640 - 653, The test blocks in
tests/InputHandler.test.ts are directly instantiating InputHandler instead of
using the shared setup() helper from tests/util/Setup.ts. Replace the manual
beforeEach block at lines 640-653 that creates a UIState object and instantiates
InputHandler directly with a call to the setup() helper, which provides the
standard game simulation wiring and maintains consistency with repository test
conventions. The same fix applies at lines 674-680 where InputHandler is also
being instantiated directly instead of through setup().

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 `@tests/InputHandler.test.ts`:
- Around line 640-658: The testSettings.setKeybinds() call is currently
positioned after inputHandler.initialize() in the beforeEach hook, which means
the InputHandler instance is initialized before the custom Numpad keybinds are
set. Move the testSettings.setKeybinds() call to occur before the
inputHandler.initialize() call so that the InputHandler reads the correct custom
keybinds during its initialization, ensuring the test properly validates the
Numpad-bound configuration instead of testing with default keybinds.

---

Nitpick comments:
In `@tests/InputHandler.test.ts`:
- Around line 640-653: The test blocks in tests/InputHandler.test.ts are
directly instantiating InputHandler instead of using the shared setup() helper
from tests/util/Setup.ts. Replace the manual beforeEach block at lines 640-653
that creates a UIState object and instantiates InputHandler directly with a call
to the setup() helper, which provides the standard game simulation wiring and
maintains consistency with repository test conventions. The same fix applies at
lines 674-680 where InputHandler is also being instantiated directly instead of
through setup().
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8d2a3df0-1ae6-4fb8-8e0f-3c288717d107

📥 Commits

Reviewing files that changed from the base of the PR and between 9071867 and 817e5a7.

📒 Files selected for processing (3)
  • resources/lang/en.json
  • src/client/InputHandler.ts
  • tests/InputHandler.test.ts
✅ Files skipped from review due to trivial changes (1)
  • resources/lang/en.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/client/InputHandler.ts

Comment thread tests/InputHandler.test.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Development

Development

Successfully merging this pull request may close these issues.

InputHandler.ts rework

4 participants