diff --git a/src/web/public/index.html b/src/web/public/index.html
index b48e4c08..f8ad5c46 100644
--- a/src/web/public/index.html
+++ b/src/web/public/index.html
@@ -1679,6 +1679,9 @@
diff --git a/src/web/public/settings-ui.js b/src/web/public/settings-ui.js
index 3e8fd626..366e89ad 100644
--- a/src/web/public/settings-ui.js
+++ b/src/web/public/settings-ui.js
@@ -1458,6 +1458,9 @@ Object.assign(CodemanApp.prototype, {
// with no UI left to turn it back off. Preserve the prior stored preference.
if (_prev.showTokenCount !== undefined) settings.showTokenCount = _prev.showTokenCount;
if (_prev.showCost !== undefined) settings.showCost = _prev.showCost;
+ // Shortcut overrides are edited from the Shortcuts tab (not rebuilt from the
+ // general-settings DOM), so the fresh rebuild would drop them on every save.
+ if (_prev.shortcutOverrides !== undefined) settings.shortcutOverrides = _prev.shortcutOverrides;
// Save to localStorage
this.saveAppSettingsToStorage(settings);
diff --git a/src/web/public/styles.css b/src/web/public/styles.css
index ea5dbc07..68dd0731 100644
--- a/src/web/public/styles.css
+++ b/src/web/public/styles.css
@@ -5812,6 +5812,13 @@ kbd {
color: var(--text-dim);
}
+.shortcut-overlay-footer {
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--border);
+ text-align: right;
+}
+
.away-digest-ranges {
display: flex;
flex-wrap: wrap;
diff --git a/test/shortcut-registry-overlay.test.ts b/test/shortcut-registry-overlay.test.ts
index 014c9468..24b63301 100644
--- a/test/shortcut-registry-overlay.test.ts
+++ b/test/shortcut-registry-overlay.test.ts
@@ -59,6 +59,22 @@ describe('shortcut registry and overlay', () => {
expect(css).toContain('.shortcut-capture-btn,');
expect(css).toContain('.shortcut-overlay-row {');
});
+
+ it('saveAppSettings preserves shortcutOverrides (rebuilt-from-DOM saves must not wipe them)', () => {
+ // Same trap as showTokenCount/showCost: saveAppSettings() rebuilds the settings
+ // object fresh from the DOM, so keys edited elsewhere (the Shortcuts tab) must be
+ // explicitly carried over from the previously stored blob.
+ expect(settingsSource).toContain(
+ 'if (_prev.shortcutOverrides !== undefined) settings.shortcutOverrides = _prev.shortcutOverrides;'
+ );
+ });
+
+ it('keeps the full help modal reachable now that Ctrl+? opens the registry overlay', () => {
+ // The legacy #helpModal (full shortcut reference) lost its only opener when
+ // Ctrl+? was rerouted to the overlay; the overlay footer must link to it.
+ expect(htmlSource).toContain('shortcut-overlay-footer');
+ expect(htmlSource).toContain('app.closeShortcutOverlay(); app.showHelp()');
+ });
});
// ─── Functional coverage (vm-sandbox harness, mirrors run-mode-ui.test.ts) ────