Skip to content

feat: add "Unbind All" button to keybind settings - #4772

Open
eastwoodgrant wants to merge 1 commit into
openfrontio:mainfrom
eastwoodgrant:fix/4309-unbind-all-keybinds
Open

feat: add "Unbind All" button to keybind settings#4772
eastwoodgrant wants to merge 1 commit into
openfrontio:mainfrom
eastwoodgrant:fix/4309-unbind-all-keybinds

Conversation

@eastwoodgrant

Copy link
Copy Markdown
Contributor

Resolves #4309

Problem

Rebinding on a fresh install/version meant clearing every key one by one first — there was no way to unbind everything at once.

Fix

Add an "Unbind All" button to the keybind settings tab. UserSettings.unbindAllKeybinds() sets each default action to the "Null" sentinel (the same one a single per-key Unbind uses), so keybinds() drops them all. The modal button calls it, reloads state, and re-renders. Matches the existing per-key Unbind pattern (no confirmation dialog) to keep it focused.

Testing

  • Unit test in tests/UserSettings.test.ts: every default action becomes "Null" and keybinds() resolves to empty.
  • Full suite passes (npm test), tsc --noEmit clean.
  • Verified live: clicking the button cleared all 36 keybinds (incl. seeded custom binds); every row showed "None".

Discord: granster9

Rebinding on a fresh install/version meant clearing every key one by one
first. Add an "Unbind All" button to the keybind settings tab that clears
all bindings at once.

UserSettings.unbindAllKeybinds() sets each default action to the "Null"
sentinel (the same one a single per-key Unbind uses), so keybinds() drops
them all. The modal button calls it, reloads state, and re-renders.

Resolves openfrontio#4309

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds an “Unbind All” control to the keybind settings. The action stores "Null" overrides for every default keybind, reloads the UI state, and is covered by a test verifying no bindings remain resolved.

Changes

Keybind unbinding

Layer / File(s) Summary
Persist unbound keybinds
src/core/game/UserSettings.ts
Adds unbindAllKeybinds(isMac) to persist "Null" for every default keybind action.
Connect and validate unbinding
src/client/UserSettingModal.ts, resources/lang/en.json, tests/UserSettings.test.ts
Adds the localized UI button, refreshes the modal after unbinding, and tests that all resolved keybinds are removed.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Player
  participant UserSettingModal
  participant UserSettings
  participant Storage
  Player->>UserSettingModal: Click “Unbind All”
  UserSettingModal->>UserSettings: unbindAllKeybinds(isMac)
  UserSettings->>Storage: Persist Null overrides
  UserSettingModal->>UserSettings: Reload keybinds
  UserSettingModal->>Player: Render empty keybind assignments
Loading

Possibly related PRs

Suggested labels: UI/UX, Translation

Suggested reviewers: evanpelle, celant

Poem

One click, and every binding sleeps,
"Null" keeps watch where each key was.
The modal refreshes, clean and bright,
Ready for new shortcuts tonight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: adding an Unbind All button to keybind settings.
Description check ✅ Passed The description matches the code changes and explains the feature, behavior, and testing.
Linked Issues check ✅ Passed The PR implements #4309 by adding a button that unbinds all keybinds and updates the UI state.
Out of Scope Changes check ✅ Passed The changes stay within the requested keybind unbind-all feature and related tests/localization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks

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

🤖 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/core/game/UserSettings.ts`:
- Around line 559-563: Update loadKeybindsFromStorage() to normalize the loaded
key through the same logic used by getKeyValue(), converting the bulk-unbind
sentinel "Null" to an empty key before assigning it to keybind rows. Keep normal
key values unchanged and preserve the existing value normalization.
🪄 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 Plus

Run ID: 0f0f001d-01c7-415a-bbe1-a209899649bd

📥 Commits

Reviewing files that changed from the base of the PR and between 02bf82d and 8c4115c.

📒 Files selected for processing (4)
  • resources/lang/en.json
  • src/client/UserSettingModal.ts
  • src/core/game/UserSettings.ts
  • tests/UserSettings.test.ts

Comment on lines +559 to +563
const unbound: Record<string, string> = {};
for (const action of Object.keys(getDefaultKeybinds(isMac))) {
unbound[action] = "Null";
}
this.setKeybinds(unbound);

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
git ls-files | rg '(^|/)UserSettings\.ts$|(^|/)UserSettingModal\.(ts|tsx)$|setting-keybind|keybind' | sed -n '1,200p'

echo
echo "== UserSettings outline =="
file=$(git ls-files | rg '(^|/)UserSettings\.ts$' | head -n1 || true)
if [ -n "${file:-}" ]; then
  echo "FILE=$file"
  wc -l "$file"
  ast-grep outline "$file" --view compact | sed -n '1,220p'
  echo
  echo "== relevant UserSettings lines 520-600 =="
  sed -n '520,600p' "$file" | nl -ba -v520
  echo
  echo "== getDefaultKeybinds/setKeybinds/keybinds occurrences =="
  rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|keybind" "$file" | sed -n '1,200p'
fi

echo
echo "== search keybind modal/loading code =="
rg -n "UserSettingModal|display=|value=" -g '*.{ts,tsx,js,jsx}' . | sed -n '1,240p'

Repository: openfrontio/OpenFrontIO

Length of output: 489


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserSettings core code =="
sed -n '500,585p' src/core/game/UserSettings.ts | nl -ba -v500

echo
echo "== UserSettings keybind usages =="
rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|unbound" src/core/game/UserSettings.ts

echo
echo "== UserSettingModal relevant code =="
wc -l src/client/UserSettingModal.ts
rg -n "SettingKey|keybind|getDisplay|get.*Key|display|value" src/client/UserSettingModal.ts
sed -n '1,260p' src/client/UserSettingModal.ts | nl -ba -v1

echo
echo "== check tests for unbind =="
rg -n "Null|unbind|keybind|getDefaultKeybinds|settings-keybind|setting-keybind" -p tests src | nl -ba -v1 | sed -n '1,260p'

Repository: openfrontio/OpenFrontIO

Length of output: 230


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== UserSettings core code =="
awk '{printf "%6d\t%s\n", NR, $0}' src/core/game/UserSettings.ts | sed -n '500,585p'

echo
echo "== UserSettings keybind usages =="
rg -n "getDefaultKeybinds|setKeybinds|keybinds\(|Null|unbound" src/core/game/UserSettings.ts

echo
echo "== UserSettingModal relevant code =="
wc -l src/client/UserSettingModal.ts
rg -n "SettingKey|keybind|getDisplay|get.*Key|display|value" src/client/UserSettingModal.ts
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '1,260p'

echo
echo "== check tests/usages for unbind =="
rg -n "Null|unbind|keybind|getDefaultKeybinds|settings-keybind|setting-keybind" tests src | sed -n '1,260p'

Repository: openfrontio/OpenFrontIO

Length of output: 47215


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== getKeyValue/getKeyChar loadKeybind path =="
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '40,85p'
awk '{printf "%6d\t%s\n", NR, $0}' src/client/UserSettingModal.ts | sed -n '177,190p'

echo
echo "== SettingKeybind component =="
rg -n "class SettingKeybind|display|value|requestUpdate|render|formatKeyForDisplay" -g '*ts' src/client -A80 | sed -n '1,260p'

echo
echo "== precise SettingKeybind file(s) =="
git ls-files | rg 'SettingKeybind|setting/.*Keybind'
for f in $(git ls-files | rg 'SettingKeybind|setting/.*Keybind'); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,180p' "$f" | awk '{printf "%6d\t%s\n", NR, $0}'
done

echo
echo "== JS-like behavior probe for loadKeybinds validation of string "Null" entry =="
node - <<'JS'
function loadKeybindsFromStorage(parsed) {
  const validated = {};
  for (const [action, entry] of Object.entries(parsed)) {
    if (typeof entry === "string") {
      validated[action] = { value: entry, key: entry };
    } else if (typeof entry === "object" && entry !== null && !Array.isArray(entry)) {
      const rawValue = entry.value ?? "Null";
      const value = Array.isArray(rawValue)
        ? rawValue.find(v => typeof v === "string")
        : rawValue;
      const rawKey = entry.key ?? value;
      const key = Array.isArray(rawKey)
        ? rawKey.find(v => typeof v === "string")
        : rawKey;
      if (typeof value === "string" && typeof key === "string") {
        validated[action] = { value, key };
      }
    }
  }
  return validated;
}
const storage = { unbindAll: { value: "Null", key: "Null" } };
const userKeybinds = loadKeybindsFromStorage(storage);
console.log(JSON.stringify({
  storage,
  validated: userKeybinds,
  value: userKeybinds.unbindAll.value,
  display: userKeybinds.unbindAll.key,
  getKeyValue: userKeybinds.unbindAll.value === "Null" ? "" : userKeybinds.unbindAll.value || undefined,
  getKeyChar: userKeybinds.unbindAll.key || "",
}));
JS

Repository: openfrontio/OpenFrontIO

Length of output: 27349


Normalize "Null" to an empty key after bulk unbind.

loadKeybindsFromStorage() already maps string entries to value and key, while getKeyValue() normalizes the value. Use the same normalization for the key, such as normalizing "Null" to an empty key, so the keybind rows do not receive display="Null" on reload.

🤖 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/core/game/UserSettings.ts` around lines 559 - 563, Update
loadKeybindsFromStorage() to normalize the loaded key through the same logic
used by getKeyValue(), converting the bulk-unbind sentinel "Null" to an empty
key before assigning it to keybind rows. Keep normal key values unchanged and
preserve the existing value normalization.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management Jul 29, 2026
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.

[FEATURE] Within the keybind settings, add a button unbinding all keybinds.

1 participant