feat(appearance): font size setting with Small/Default/Large toggle (closes #833)

* feat(appearance): font size setting with Small/Default/Large toggle

Add a font size preference to the Appearance settings pane.
Three options (12px/14px/16px) follow the same three-button visual
pattern as the Theme picker. Closes #833.

- static/style.css: :root[data-font-size=small|large] CSS overrides
- static/index.html: boot script applies from localStorage before CSS
  renders (no FOUC); fontSizePickerGrid HTML in Appearance pane
- static/boot.js: _applyFontSize(), _pickFontSize(), _syncFontSizePicker()
- static/panels.js: loadSettingsPanel syncs picker on open;
  _revertSettingsPreview restores on discard
- static/i18n.js: settings_label_font_size + font_size_{small,default,large}
  keys in all 6 locales (en, ru, es, de, zh, zh-Hant)
- tests/test_font_size_setting.py: 14 new tests

* fix(ui): remove duplicate font-size picker + correct CHANGELOG issue ref

Two small fixes on the font size feature:

1. Duplicate HTML IDs — the picker block was injected into BOTH
   settingsPaneAppearance (correct, next to Theme/Skin) AND
   settingsPanePreferences (accidental copy-paste).  Duplicate IDs
   #fontSizePickerGrid and #settingsFontSize violate HTML spec and
   break the _syncFontSizePicker visual sync which reads via
   document.querySelectorAll('#fontSizePickerGrid .font-size-pick-btn')
   — only the first grid would update its highlight, leaving the second
   stale.  $('settingsFontSize') via getElementById also always returns
   the first match, so the second hidden input never reflected the
   user's choice.

   Removed the Preferences-pane copy.  The Appearance-pane copy is the
   one the PR description describes and is the correct home for it
   (next to Theme and Skin).

2. CHANGELOG trailer said `Closes #830.` but #830 is the session-search
   autocomplete PR — this feature closes #833.  Fixed.

Added two regression tests:
- test_font_size_picker_not_duplicated: asserts each ID appears exactly
  once in index.html.
- test_font_size_picker_lives_in_appearance_pane: asserts the picker
  sits inside settingsPaneAppearance and not any other pane.

Full suite: 1754 passed, 0 failures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nesquena-hermes
2026-04-21 22:52:45 -07:00
committed by GitHub
parent 1239129ae2
commit 24fc9d4155
7 changed files with 270 additions and 0 deletions

View File

@@ -1105,6 +1105,7 @@ document.addEventListener('drop',e=>{e.preventDefault();dragCounter=0;wrap.class
let _settingsDirty = false;
let _settingsThemeOnOpen = null; // track theme at open time for discard revert
let _settingsSkinOnOpen = null; // track skin at open time for discard revert
let _settingsFontSizeOnOpen = null; // track font size at open time for discard revert
let _settingsHermesDefaultModelOnOpen = '';
let _settingsSection = 'conversation';
@@ -1152,6 +1153,7 @@ function toggleSettings(){
_settingsDirty = false;
_settingsThemeOnOpen = localStorage.getItem('hermes-theme') || 'dark';
_settingsSkinOnOpen = localStorage.getItem('hermes-skin') || 'default';
_settingsFontSizeOnOpen = localStorage.getItem('hermes-font-size') || 'default';
_settingsSection = 'conversation';
overlay.style.display='';
loadSettingsPanel();
@@ -1196,6 +1198,10 @@ function _revertSettingsPreview(){
localStorage.setItem('hermes-skin', _settingsSkinOnOpen);
if(typeof _applySkin==='function') _applySkin(_settingsSkinOnOpen);
}
if(_settingsFontSizeOnOpen){
localStorage.setItem('hermes-font-size', _settingsFontSizeOnOpen);
if(typeof _applyFontSize==='function') _applyFontSize(_settingsFontSizeOnOpen);
}
}
// Show the "Unsaved changes" bar inside the settings panel
@@ -1243,6 +1249,10 @@ async function loadSettingsPanel(){
const skinSel=$('settingsSkin');
if(skinSel) skinSel.value=skinVal;
if(typeof _buildSkinPicker==='function') _buildSkinPicker(skinVal);
const fontSizeVal=localStorage.getItem('hermes-font-size')||'default';
const fontSizeSel=$('settingsFontSize');
if(fontSizeSel) fontSizeSel.value=fontSizeVal;
if(typeof _syncFontSizePicker==='function') _syncFontSizePicker(fontSizeVal);
const resolvedLanguage=(typeof resolvePreferredLocale==='function')
? resolvePreferredLocale(settings.language, localStorage.getItem('hermes-lang'))
: (settings.language || localStorage.getItem('hermes-lang') || 'en');