fix(components): pick the conversation font size from a fixed scale - #718
Merged
Merged
Conversation
The conversation font size was a number field whose every keystroke was clamped into 9-32px and written straight back, so the field rewrote what the user was typing: aiming for 24 turned the first `2` into `9`, the next keystroke read `94`, and that landed on 32. Emptying the field to start over did nothing, because a blank value parsed as NaN and left the previous number in place. Settings now offers eight sizes - 8, 12, 14, 16, 20, 24, 28, 32 - through the theme row's picker on desktop and the inline picker on mobile, with one shared label source so the two surfaces cannot describe the same stored value differently. `normalizeConversationFontSize` keeps only its persistence job and snaps to the nearest offered size (ties round up), so a value persisted by an older build keeps the closest size the user chose instead of being clamped to a bound that matches no option. A number is not a size, so each surface renders a sample sentence at the selected size below the row, in the shape the Terminal section already uses. Desktop feeds the picker's hover callback into local state rather than the setting, so hovering a size redraws the sample while the saved value stays put and closing the menu without choosing drops back. Model: claude-opus-5[1m] Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issue
Problem / pressure
The conversation font size in Settings > Appearance was a number field bounded by 9-32px, and every keystroke went through a clamp whose result was written straight back into the controlled input. The field therefore rewrote what the user was typing: aiming for
24, the first2became9, the next keystroke read94, and that landed on32. Emptying the field to start over did nothing either, because a blank value parses asNaNand left the previous number in place. A number on its own also says nothing about how large the text will actually be.Summary
CONVERSATION_FONT_SIZES(8, 12, 14, 16, 20, 24, 28, 32) replacesCONVERSATION_FONT_SIZE_MIN/_MAXas the one source for both the offered options and the snap targets.normalizeConversationFontSizekeeps only its persistence job: it snaps to the nearest offered size (ties round up) instead of clamping into a range. Legacysmall/default/largemigration is unchanged.PreviewSelect; mobile reuses the inline picker. Both label options through onebuildConversationFontSizeChoiceshelper, so they cannot describe the same stored value differently (14 px · Default/14 px · 默认).conversationTextFontSizeStyle, the helper the conversation renderers use, so the preview cannot drift from the real thing.Mobile/MobileAppearanceSettingsstory (default / largest / smallest).The terminal font size in the same panel deliberately keeps its number field: its range tops out at 24px and its default (13px) is not on this scale, so giving it presets means choosing a second scale — a separate decision.
Decision record:
.agents/notes/implemented/simplification/2026-09-14-conversation-font-size-scale.md(+.zh.md).Visual explanation
Where the size comes from, and where it is shown:
The old path had no step between the keystroke and storage:
onChange -> clamp -> setState -> value, which is exactly what made the field fight the user.Before / after
min=9 max=32 step=1; each keystroke clamped and written back, so typing24walked9 -> 94 -> 32Screenshots (desktop light/dark/zh, menu open with a hovered size; mobile default/open/8px/32px/dark/zh) were attached in the authoring session; the mobile ones come from the new story, the desktop ones from a temporary Vite page rendering
AppearanceSettingsView(see Test plan).Test plan
corepack pnpm --filter @lody/components run typecheck— passes.corepack pnpm lint(type-aware oxlint) — 0 errors.corepack pnpm lint:i18n— all keys present in both languages.corepack pnpm run docs check—"errors": []; remaining warnings are pre-existing.NODE_ENV=development corepack pnpm exec vitest run tests/appearance-settings.test.tsx tests/terminal-settings.test.ts— 11 passed. Coverage added: the exact eight offered options, hover preview without commit, Escape restoring the saved size, committing 24px, snapping (13 -> 14, 9 -> 8, 30 -> 32, 400 -> 32, 14.7 -> 14) and legacy preset migration, and the mobile row having no number field while rendering its sample at the stored size.tests/local-terminal-panel.test.tsx,tests/interface-font-controller.test.tsx— 21 passed.Settings/AppearanceSettingscannot be read in a built Storybook — it hangs in Storybook's "preparing" state with no error, as doesSettings/BillingSettings. Both reachsettings/index.tsx, which every settings surface imports forsettingContainerClassand which imports them all back; that chunk cycle deadlocks undervite-plugin-top-level-await. It predates this change and was left alone. The desktop layout was verified through a temporary Vite dev page renderingAppearanceSettingsViewdirectly (deleted afterwards); the mobile layout was verified through the new story in the built Storybook.Context handoff
Instructions for reviewing agents
packages/components/src/atoms/settings.ts(the snap replacing the clamp, and thatCONVERSATION_FONT_SIZESis the only list) and the desktop hover-preview wiring inappearance-setting.tsx(previewFontSizemust never reachonConversationFontSizeChange).PreviewSelectinstead ofOptionSelector.Authoring context
lody-conversation-font-sizevalue — any stored number now resolves to one of eight sizes.