feat(electrobun): settings overhaul — fonts, shells, providers, retention, chords

- Settings drawer: responsive width clamp(24rem, 45vw, 50rem)
- System font detection: fc-list for UI fonts (preferred sans-serif starred)
  and mono fonts (Nerd Fonts starred), fallback to hardcoded lists
- Scrollback: default 5000, min 1000, step 500
- Shell detection: system.shells RPC, pre-selects $SHELL login shell
- Provider enablement: provider.scan gates toggle, unavailable shown as N/A
- Session retention: count 0-100 (0=Keep all), age 0-365 (0=Forever)
- Chord keybindings: Ctrl+K → Ctrl+S style multi-key sequences,
  1s prefix wait, arrow separator display, 26 tests passing
This commit is contained in:
Hibryda 2026-03-25 01:42:34 +01:00
parent afaa2253de
commit 1de6c93e01
9 changed files with 346 additions and 187 deletions

View file

@ -8,7 +8,8 @@
import ThemeEditor from './ThemeEditor.svelte';
import CustomDropdown from '../ui/CustomDropdown.svelte';
const UI_FONTS = [
// Fallback lists — replaced by system detection on mount
const FALLBACK_UI_FONTS = [
{ value: '', label: 'System Default' },
{ value: 'Inter', label: 'Inter' },
{ value: 'IBM Plex Sans', label: 'IBM Plex Sans' },
@ -17,7 +18,7 @@
{ value: 'Ubuntu', label: 'Ubuntu' },
];
const TERM_FONTS = [
const FALLBACK_TERM_FONTS = [
{ value: '', label: 'Default (JetBrains Mono)' },
{ value: 'JetBrains Mono', label: 'JetBrains Mono' },
{ value: 'Fira Code', label: 'Fira Code' },
@ -27,6 +28,9 @@
{ value: 'monospace', label: 'monospace' },
];
let uiFontItems = $state(FALLBACK_UI_FONTS.map(f => ({ value: f.value, label: f.label })));
let termFontItems = $state(FALLBACK_TERM_FONTS.map(f => ({ value: f.value, label: f.label })));
// ── Local reactive state ───────────────────────────────────────────────────
let themeId = $state<ThemeId>(themeStore.currentTheme);
let uiFont = $state(fontStore.uiFontFamily);
@ -35,7 +39,7 @@
let termFontSize = $state(fontStore.termFontSize);
let cursorStyle = $state('block');
let cursorBlink = $state(true);
let scrollback = $state(1000);
let scrollback = $state(5000);
interface CustomThemeMeta { id: string; name: string; }
let customThemes = $state<CustomThemeMeta[]>([]);
@ -67,8 +71,7 @@
// ── Dropdown items for CustomDropdown ──────────────────────────────────────
let themeItems = $derived(allThemes.map(t => ({ value: t.id, label: t.label, group: t.group })));
let uiFontItems = UI_FONTS.map(f => ({ value: f.value, label: f.label }));
let termFontItems = TERM_FONTS.map(f => ({ value: f.value, label: f.label }));
// uiFontItems and termFontItems are $state — populated by system.fonts on mount
let langItems = AVAILABLE_LOCALES.map(l => ({ value: l.tag, label: l.nativeLabel }));
// ── Actions ────────────────────────────────────────────────────────────────
@ -135,10 +138,35 @@
const { settings } = await appRpc.request['settings.getAll']({}).catch(() => ({ settings: {} }));
if (settings['cursor_style']) cursorStyle = settings['cursor_style'];
if (settings['cursor_blink']) cursorBlink = settings['cursor_blink'] !== 'false';
if (settings['scrollback']) scrollback = parseInt(settings['scrollback'], 10) || 1000;
if (settings['scrollback']) scrollback = parseInt(settings['scrollback'], 10) || 5000;
const res = await appRpc.request['themes.getCustom']({}).catch(() => ({ themes: [] }));
customThemes = res.themes.map(t => ({ id: t.id, name: t.name }));
// Detect system fonts
try {
const fontRes = await appRpc.request['system.fonts']({});
if (fontRes.uiFonts.length > 0) {
uiFontItems = [
{ value: '', label: 'System Default' },
...fontRes.uiFonts.map(f => ({
value: f.family,
label: f.preferred ? `\u2605 ${f.family}` : f.family,
})),
];
}
if (fontRes.monoFonts.length > 0) {
termFontItems = [
{ value: '', label: 'Default (JetBrains Mono)' },
...fontRes.monoFonts.map(f => ({
value: f.family,
label: f.isNerdFont ? `\u2B50 ${f.family}` : f.family,
})),
];
}
} catch {
// Keep fallback font lists
}
});
</script>
@ -216,8 +244,8 @@
<h3 class="sh">{t('settings.scrollback')}</h3>
<div class="field row">
<input type="number" class="num-in" min="100" max="100000" step="100" value={scrollback}
onchange={e => persistScrollback(parseInt((e.target as HTMLInputElement).value, 10) || 1000)}
<input type="number" class="num-in" min="1000" max="100000" step="500" value={scrollback}
onchange={e => persistScrollback(parseInt((e.target as HTMLInputElement).value, 10) || 5000)}
aria-label="Scrollback lines" />
<span class="hint">{t('settings.scrollbackHint')}</span>
</div>