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
@@ -18,6 +18,7 @@ import {
SettingsStackedField,
SettingsChipGroup,
SETTINGS_SELECT_SIZE,
SETTINGS_NUMBER_INPUT_CLASS,
SETTINGS_SELECT_ROW_TRIGGER_CLASS,
SETTINGS_ICON_BUTTON_CLASS,
SETTINGS_CUSTOM_TRIGGER_CLASS,
@@ -450,7 +451,7 @@ export const AgentsPage: React.FC = () => {
inputMode="decimal"
placeholder="—"
emptyLabel="—"
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
{temperature !== undefined && (
<Button
@@ -488,7 +489,7 @@ export const AgentsPage: React.FC = () => {
inputMode="decimal"
placeholder="—"
emptyLabel="—"
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
{topP !== undefined && (
<Button
@@ -3,7 +3,6 @@ import { Button } from '@/components/ui/button';
import { Textarea } from '@/components/ui/textarea';
import { toast } from '@/components/ui';
import { useI18n, type I18nKey } from '@/lib/i18n';
import { cn } from '@/lib/utils';
import { reportSettingsSaveState } from '@/lib/persistence';
import { useIsVSCodeRuntime } from '@/hooks/useRuntimeAPIs';
import {
@@ -376,7 +375,7 @@ export const BehaviorPage: React.FC = () => {
onValueChange={(value) => setResponseStylePreset(value)}
disabled={isLoading || !responseStyleEnabled}
>
<SelectTrigger size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_ROW_TRIGGER_CLASS, 'max-w-72')}>
<SelectTrigger size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_ROW_TRIGGER_CLASS}>
<SelectValue>
{(value) => {
if (value === 'custom') return t('settings.behavior.page.responseStyle.option.custom');
@@ -54,6 +54,7 @@ import {
SETTINGS_CLUSTER_CONTROL_CLASS,
SETTINGS_NUMBER_STEPPER_ROW_CLASS,
SETTINGS_NUMBER_UNIT_CLASS,
SETTINGS_NUMBER_INPUT_CLASS,
SETTINGS_FIELDS_STACK_CLASS,
SETTINGS_OPTION_STACK_CLASS,
} from '@/components/sections/shared/SettingsSection';
@@ -1217,7 +1218,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
controlClassName="w-full"
>
<Select value={uiFont} onValueChange={(value) => setUiFont(value as UiFontOption)}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectInterfaceFontAria')} size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_TRIGGER_CLASS, 'max-w-full')}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectInterfaceFontAria')} size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_TRIGGER_CLASS}>
<SelectValue>{UI_FONT_OPTIONS.find((option) => option.id === uiFont)?.label}</SelectValue>
</SelectTrigger>
<SelectContent>
@@ -1247,7 +1248,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
controlClassName="w-full"
>
<Select value={monoFont} onValueChange={(value) => setMonoFont(value as MonoFontOption)}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectCodeFontAria')} size={SETTINGS_SELECT_SIZE} className={cn(SETTINGS_SELECT_TRIGGER_CLASS, 'max-w-full')}>
<SelectTrigger aria-label={t('settings.openchamber.visual.field.selectCodeFontAria')} size={SETTINGS_SELECT_SIZE} className={SETTINGS_SELECT_TRIGGER_CLASS}>
<SelectValue>{CODE_FONT_OPTIONS.find((option) => option.id === monoFont)?.label}</SelectValue>
</SelectTrigger>
<SelectContent>
@@ -1288,7 +1289,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={50}
max={200}
step={5}
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
aria-label={t('settings.openchamber.visual.field.fontSizePercentageAria')}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>%</span>
@@ -1319,7 +1320,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={9}
max={52}
step={1}
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -1349,7 +1350,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={9}
max={32}
step={1}
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -1384,7 +1385,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={50}
max={200}
step={5}
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>%</span>
<Button size="sm"
@@ -1415,7 +1416,7 @@ export const OpenChamberVisualSettings: React.FC<OpenChamberVisualSettingsProps>
min={0}
max={100}
step={5}
className="w-20"
className={SETTINGS_NUMBER_INPUT_CLASS}
/>
<span className={SETTINGS_NUMBER_UNIT_CLASS}>px</span>
<Button size="sm"
@@ -10,7 +10,9 @@ import {
SettingsChipGroup,
SettingsInset,
SETTINGS_ICON_BUTTON_CLASS,
SETTINGS_NUMBER_INPUT_CLASS,
} from '@/components/sections/shared/SettingsSection';
import { cn } from '@/lib/utils';
import { useUIStore } from '@/stores/useUIStore';
import { useSessionAutoCleanup } from '@/hooks/useSessionAutoCleanup';
import { useI18n, type I18nKey } from '@/lib/i18n';
@@ -87,7 +89,7 @@ export const SessionRetentionSettings: React.FC = () => {
max={MAX_DAYS}
step={1}
aria-label={t('settings.openchamber.sessionRetention.field.retentionPeriodAria')}
className="w-24 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
/>
<span className="typography-ui-label text-muted-foreground">{t('settings.openchamber.sessionRetention.field.days')}</span>
<Button
@@ -20,6 +20,7 @@ import {
SettingsControlGroup,
SettingsChipGroup,
SETTINGS_SELECT_SIZE,
SETTINGS_NUMBER_INPUT_CLASS,
SETTINGS_SELECT_ROW_TRIGGER_CLASS,
SETTINGS_CONTROL_CLUSTER_CLASS,
SETTINGS_FIELD_LABEL_CLASS,
@@ -1051,20 +1052,20 @@ export const VoiceSettings: React.FC = () => {
{/* Speech Rate */}
<SettingsFieldRow label={t('settings.voice.page.field.speechRate')}>
{!isMobile && <input type="range" min={0.5} max={2} step={0.1} value={speechRate} onChange={(e) => setSpeechRate(Number(e.target.value))} className={sliderClass} />}
<NumberInput value={speechRate} onValueChange={setSpeechRate} min={0.5} max={2} step={0.1} className="w-20 tabular-nums" />
<NumberInput value={speechRate} onValueChange={setSpeechRate} min={0.5} max={2} step={0.1} className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')} />
</SettingsFieldRow>
{/* Speech Pitch */}
<SettingsFieldRow label={t('settings.voice.page.field.speechPitch')}>
{!isMobile && <input type="range" min={0.5} max={2} step={0.1} value={speechPitch} onChange={(e) => setSpeechPitch(Number(e.target.value))} className={sliderClass} />}
<NumberInput value={speechPitch} onValueChange={setSpeechPitch} min={0.5} max={2} step={0.1} className="w-20 tabular-nums" />
<NumberInput value={speechPitch} onValueChange={setSpeechPitch} min={0.5} max={2} step={0.1} className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')} />
</SettingsFieldRow>
{/* Speech Volume */}
<SettingsFieldRow label={t('settings.voice.page.field.speechVolume')}>
{!isMobile && <input type="range" min={0} max={1} step={0.1} value={speechVolume} onChange={(e) => setSpeechVolume(Number(e.target.value))} className={sliderClass} />}
{isMobile ? (
<NumberInput value={Math.round(speechVolume * 100)} onValueChange={(v) => setSpeechVolume(v / 100)} min={0} max={100} step={10} className="w-16 tabular-nums" />
<NumberInput value={Math.round(speechVolume * 100)} onValueChange={(v) => setSpeechVolume(v / 100)} min={0} max={100} step={10} className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')} />
) : (
<span className="typography-ui-label text-foreground tabular-nums min-w-[3rem] text-right">
{Math.round(speechVolume * 100)}%
@@ -29,6 +29,7 @@ import {
SETTINGS_SECTION_TITLE_CLASS,
SETTINGS_FIELD_LABEL_CLASS,
SETTINGS_SELECT_SIZE,
SETTINGS_NUMBER_INPUT_CLASS,
} from '@/components/sections/shared/SettingsSection';
import { SettingsInfoHint } from '@/components/sections/shared/SettingsInfoHint';
import { useDesktopSshStore } from '@/stores/useDesktopSshStore';
@@ -2322,7 +2323,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={5}
max={240}
step={1}
className="w-20 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
value={draft.connectionTimeoutSec}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2350,7 +2351,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-32 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
value={draft.remoteOpenchamber.preferredPort}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2517,7 +2518,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-32 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
value={draft.localForward.preferredLocalPort}
onValueChange={(next) => {
updateDraft((current) => ({
@@ -2775,7 +2776,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-32 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
value={forward.localPort}
onValueChange={(next) => {
updateForward((item) => ({
@@ -2817,7 +2818,7 @@ export const RemoteInstancesPage: React.FC = () => {
min={1}
max={65535}
step={1}
className="w-32 tabular-nums"
className={cn(SETTINGS_NUMBER_INPUT_CLASS, 'tabular-nums')}
value={forward.remotePort}
onValueChange={(next) => {
updateForward((item) => ({
@@ -6,12 +6,33 @@ import { dropdownTriggerVariants } from '@/components/ui/dropdown-trigger';
import { cn } from '@/lib/utils';
import { SettingsInfoHint } from './SettingsInfoHint';
/**
* Width cap shared by every settings value picker.
*
* `applyTypography` scales the `--typography-*` vars but never the root rem,
* so a rem-based cap (`max-w-48`) stays 192px while the label inside it grows,
* and the value clips at 150200% interface font size. `ch` is measured
* against the trigger's own `typography-ui-label` font, so the cap grows with
* the setting. Below `@xl` the field row stacks and the control is plain
* `w-full`, so this cap only binds on wide panes.
*/
const SETTINGS_TRIGGER_WIDTH_CLASS = 'w-full min-w-[22ch] max-w-[40ch]';
/** Settings select trigger: full column width in stacked cells; capped in field rows via parent. */
export const SETTINGS_SELECT_TRIGGER_CLASS = 'w-full min-w-40 max-w-48';
export const SETTINGS_SELECT_TRIGGER_CLASS = SETTINGS_TRIGGER_WIDTH_CLASS;
export const SETTINGS_SELECT_SIZE = 'settings' as const;
/** Fixed-width select used inside full-width SettingsFieldRow control columns. */
export const SETTINGS_SELECT_ROW_TRIGGER_CLASS = 'w-full min-w-40 max-w-48';
export const SETTINGS_SELECT_ROW_TRIGGER_CLASS = SETTINGS_TRIGGER_WIDTH_CLASS;
/**
* Width for every settings NumberInput stepper. Font-relative for the same
* reason as the trigger cap above: a rem-fixed `w-16`/`w-20`/`w-24`/`w-32`
* clips its own digits once the interface font is scaled up. The explicit
* `typography-ui-label` pins `ch` to the same scaled font the inner numeric
* field renders in, since the wrapper would otherwise inherit an unscaled one.
*/
export const SETTINGS_NUMBER_INPUT_CLASS = 'typography-ui-label w-[16ch]';
/** Compact reset / icon action next to a settings control (matches h-8 controls). */
export const SETTINGS_ICON_BUTTON_CLASS =
@@ -21,7 +42,7 @@ export const SETTINGS_ICON_BUTTON_CLASS =
// eslint-disable-next-line react-refresh/only-export-components
export const SETTINGS_CUSTOM_TRIGGER_CLASS = cn(
dropdownTriggerVariants(),
'w-full min-w-40 max-w-48',
SETTINGS_TRIGGER_WIDTH_CLASS,
);
/** Shared width for stacked control clusters (select/input + reset). */