fix: harden and de-slop the merged sidebar/chat/settings batch

Post-merge follow-ups for #2740 #2735 #2734 #2690 #2676 #2738 #2684
#2689 #2733 #2739 #2462 #2687 #2736 #2618 #2697, plus three regressions
found while reviewing them:

- ctrl/cmd+digit while typing no longer switches session tabs (#2503 was
  still open in practice: the guard only covered the mod+alt surface binding)
- Shiki template-call sanitizer now covers every bundled grammar, including
  the js/ts aliases and embedding grammars; timed-out highlight requests are
  memoized and no longer cancel unrelated in-flight requests
- settings flush on suspend uses keepalive and also fires on Capacitor
  appStateChange; keeps the selected model persisted across mode switches
- remote-only branches fetch before checkout; range helpers fail clearly
- git status invalidation now fires for runtime adapters too
- settings number inputs and select triggers size in ch so they scale with
  the interface font
- recent-activity timestamps tick from one list-level ticker
- Markdown preview find goes through the shared find_in_file keybind with
  containment, no longer counts its own bar, and debounces observer runs
- #2676 reverted; #2524 fixed by fading the sticky header's own background
  instead of overlaying the content below it
- sticky group headers in the model picker and sidebar render again
  (oc-sticky-fade-scroller class restored after 9b9d7069c)
- project switcher names are left-aligned again (wrapper lost in 26dbc2f30)
- tool card quick-open icon is always visible and opens the same line as the
  expanded card's button
- tautological tests replaced or removed; new oxlint findings fixed
This commit is contained in:
Bohdan Triapitsyn
2026-08-29 01:06:43 +03:00
parent a182f4f4ff
commit 48bcac1758
53 changed files with 1125 additions and 375 deletions
@@ -4,7 +4,11 @@ import { Window } from 'happy-dom';
import { hasOpenDropdown, isEditableEventTarget, shouldStopDropdownImeEscape } from './keyboard-shortcut-dom';
const domWindow = new Window();
Object.assign(globalThis, { document: domWindow.document, HTMLElement: domWindow.HTMLElement });
Object.assign(globalThis, {
document: domWindow.document,
HTMLElement: domWindow.HTMLElement,
KeyboardEvent: domWindow.KeyboardEvent,
});
test('does not treat an unrelated visible listbox as an open dropdown', () => {
const promptNavigator = {} as Element;
@@ -55,3 +59,31 @@ test('does not treat a plain element or non-element target as editable', () => {
expect(isEditableEventTarget(document.createElement('button'))).toBe(false);
expect(isEditableEventTarget(null)).toBe(false);
});
// Both digit shortcuts (switch_context_surface and switch_session_tab) gate on
// isEditableEventTarget(event.target). switch_session_tab's default prefix is a
// bare modifier, so plain ctrl/cmd+1 reaches the handler while the composer has
// focus; the guard only holds if a dispatched keydown reports the focused
// textarea as its target rather than the element the listener sits on (#2689).
test('reports the focused editable element as the target of a bubbled ctrl/cmd+digit keydown', () => {
const textarea = document.createElement('textarea');
document.body.appendChild(textarea);
let observedTarget: EventTarget | null = null;
const listener = (event: Event) => {
observedTarget = event.target;
};
document.addEventListener('keydown', listener);
textarea.dispatchEvent(new KeyboardEvent('keydown', {
key: '1',
metaKey: true,
bubbles: true,
}));
document.removeEventListener('keydown', listener);
textarea.remove();
expect(observedTarget).toBe(textarea);
expect(isEditableEventTarget(observedTarget)).toBe(true);
});
@@ -520,6 +520,10 @@ export const useKeyboardShortcuts = () => {
sessionTabDigit !== null
&& !event.repeat
&& !isVSCodeRuntime()
// Typing a digit in a textarea/input must stay text, never a tab
// switch: the default prefix here is a bare modifier, so this fires
// on plain ctrl/cmd+1 while the composer has focus (#2689).
&& !isEditableEventTarget(event.target)
&& useUIStore.getState().sessionTabsEnabled
&& eventMatchesShortcutPrefix(
event,