feat(settings): editor font size for chat input and code editor (#1325) (#2065)

* feat(settings): add editor font size setting for chat input and code editor

Adds an 'Editor font size' control in Settings > Appearance that sets an
absolute px font size for the chat input textarea and the in-app
CodeMirror editor. Mirrors the existing terminalFontSize lifecycle.

- New store field editorFontSize (default 13, clamp 9-32, step 1) in
  useUIStore with narrow selectors at each consumer.
- Persistence wired through appearanceAutoSave, desktop + runtime API
  types, and persistence.ts read/normalize.
- Settings UI row (NumberInput) with reset to 13, VisibleSetting union
  entry, OpenChamberPage registration, and search index entry
  appearance.editor-font-size.
- Applied as a post-zoom absolute override on the chat input textarea
  and on the CodeMirror theme's content rule, leaving gutter/line-number
  chrome at its existing hardcoded sizes (matches terminal scope).
- All 10 locales translated (en, es, fr, ja, ko, pl, pt-BR, uk, zh-CN,
  zh-TW); no English placeholders in non-English dictionaries.

Refs #1325

* fix(codemirror): use unitless lineHeight so it scales with editor font size

The & rule in the CodeMirror theme set lineHeight to 1.5rem (~24px),
which does not scale when editorFontSize is increased (e.g., 28-32px).
This causes overlapping lines at larger font sizes.

Change to unitless 1.5, which scales proportionally with whatever fontSize
resolves to (dynamic prop or --text-code fallback). Matches browser best
practice for proportional leading.

Review comment: https://github.com/openchamber/openchamber/pull/2065

---------

Co-authored-by: bashrusakh <bashrusakh@users.noreply.github.com>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Leonid
2026-07-11 14:45:27 +03:00
committed by GitHub
co-authored by bashrusakh Bohdan Triapitsyn
parent bfaf62e222
commit e0229917f8
23 changed files with 108 additions and 15 deletions
@@ -2885,17 +2885,19 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
return () => window.removeEventListener('keydown', handleKeyDown);
}, [canEdit, isMobile, shortcutOverrides, textViewMode]);
const editorFontSize = useUIStore((state) => state.editorFontSize);
const editorExtensions = React.useMemo(() => {
if (!selectedFile?.path) {
return [createFlexokiCodeMirrorTheme(currentTheme)];
return [createFlexokiCodeMirrorTheme(currentTheme, { fontSize: editorFontSize })];
}
// Shiki token colors (worker-backed) match the Shiki file view exactly.
// Same language resolver as the view, so both agree on the language. When
// Shiki is the color source, drop the lezer token colors to avoid a
// competing highlighter (keep the lezer language for indentation/folding).
// competing highlighter (Keep the lezer language for indentation/folding).
const shikiLanguage = getLanguageFromExtension(selectedFile.path);
const extensions = [createFlexokiCodeMirrorTheme(currentTheme, shikiLanguage ? { syntaxColors: false } : undefined)];
const extensions = [createFlexokiCodeMirrorTheme(currentTheme, shikiLanguage ? { syntaxColors: false, fontSize: editorFontSize } : { fontSize: editorFontSize })];
const language = staticLanguageExtension ?? dynamicLanguageExtension;
if (language) {
extensions.push(language);
@@ -2925,7 +2927,7 @@ export const FilesView: React.FC<FilesViewProps> = ({ mode = 'full' }) => {
}));
}
return extensions;
}, [currentTheme, selectedFile?.path, staticLanguageExtension, dynamicLanguageExtension, wrapLines, isMobile, nudgeEditorSelectionAboveKeyboard]);
}, [currentTheme, selectedFile?.path, staticLanguageExtension, dynamicLanguageExtension, wrapLines, isMobile, nudgeEditorSelectionAboveKeyboard, editorFontSize]);
const pierreTheme = React.useMemo(
() => ({ light: lightTheme.metadata.id, dark: darkTheme.metadata.id }),
@@ -345,12 +345,14 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
};
}, [cancel, commentText, editingDraftId, isMobile, lineSelection]);
const editorFontSize = useUIStore((state) => state.editorFontSize);
const editorExtensions = React.useMemo(() => {
// Shiki token colors only for code files; markdown keeps the lezer
// highlighter (markdown-aware bold headings etc., and no Shiki view to match).
const shikiLanguage = resolvedPath ? getLanguageFromExtension(resolvedPath) : null;
const useShiki = Boolean(shikiLanguage) && shikiLanguage !== 'markdown';
const extensions = [createFlexokiCodeMirrorTheme(currentTheme, useShiki ? { syntaxColors: false } : undefined)];
const extensions = [createFlexokiCodeMirrorTheme(currentTheme, useShiki ? { syntaxColors: false, fontSize: editorFontSize } : { fontSize: editorFontSize })];
const language = languageByExtension(resolvedPath || 'plan.md');
if (language) {
extensions.push(language);
@@ -364,7 +366,7 @@ export const PlanView: React.FC<PlanViewProps> = ({ targetPath = null }) => {
}
extensions.push(EditorView.lineWrapping);
return extensions;
}, [currentTheme, resolvedPath]);
}, [currentTheme, resolvedPath, editorFontSize]);
React.useEffect(() => {
// Saved project plans opened via context panel should work even when session plan mode is off.