feat: add "Unbind All" button to keybind settings - #4772
Conversation
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>
WalkthroughAdds an “Unbind All” control to the keybind settings. The action stores ChangesKeybind unbinding
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
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ 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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
resources/lang/en.jsonsrc/client/UserSettingModal.tssrc/core/game/UserSettings.tstests/UserSettings.test.ts
| const unbound: Record<string, string> = {}; | ||
| for (const action of Object.keys(getDefaultKeybinds(isMac))) { | ||
| unbound[action] = "Null"; | ||
| } | ||
| this.setKeybinds(unbound); |
There was a problem hiding this comment.
🗄️ 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 || "",
}));
JSRepository: 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.
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), sokeybinds()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
tests/UserSettings.test.ts: every default action becomes"Null"andkeybinds()resolves to empty.npm test),tsc --noEmitclean.Discord: granster9