Skip to content

Add a "Chat text size" setting (Small / Default / Large) to Appearance#65

Open
DragonnZhang wants to merge 1 commit into
mainfrom
loop/chat-text-size
Open

Add a "Chat text size" setting (Small / Default / Large) to Appearance#65
DragonnZhang wants to merge 1 commit into
mainfrom
loop/chat-text-size

Conversation

@DragonnZhang

Copy link
Copy Markdown
Collaborator

What & why

Adds a "Chat text size" segmented control to Settings → Appearance →
Interface
(below "Reduce motion") with Small / Default / Large. It scales
the conversation transcript's typography without resizing the rest of the app
— the sidebar, top bar, composer, and icons keep their native size.

This is distinct from the OS-level window zoom already wired to the View menu
(webContents.setZoomFactor), which scales the entire UI and is a transient
window zoom rather than a persisted appearance preference. A text-size control
that scales only the reading text is a recurring, unmet ask in comparable
desktop clients (Claude Desktop's "Chat font" + anthropics/claude-code #50543 /
#48887; ChatGPT desktop font-size requests).

Closes #64

How it works

Renderer-only, no qwen-code backend involvement — mirrors the merged
ReduceMotionContext idiom:

  • ChatTextSizeProvider (context/ChatTextSizeContext.tsx) persists the
    choice in localStorage (craft-chat-text-size) and reflects it onto
    <html> as data-chat-text-size="small|medium|large". Wired in main.tsx.
  • Global CSS (index.css) maps that attribute to a --chat-font-scale
    custom property (0.9 / 1 / 1.15).
  • The transcript container in ChatDisplay.tsx carries a .chat-text-scope
    class with font-size: calc(1em * var(--chat-font-scale)). Because it is
    em-relative, Default (medium) is exactly neutral — no visual change
    for existing users — and only the conversation's body copy scales up/down.
  • SettingsSegmentedControl gained an optional testId prop (adds
    data-testid on the group and data-value on each option) so the control can
    be targeted independently of its localized label.
  • 5 new i18n keys added across all 7 locales.

Verification

All checks were run against the branch and compared to a clean main baseline:

Check Result
bun run typecheck:all Zero delta — identical 11 pre-existing tsc errors (all in files this PR doesn't touch); none reference any changed file
bun test Zero delta — the 56 pre-existing failing-test lines are byte-identical between main and this branch
bun run lint:i18n:parity i18n parity OK (6 locales, 1549 keys each)
bun run electron:build:renderer ✅ builds successfully (contains all of this PR's code)
bun run electron:build:main ⚠️ fails identically on main and this branch — pre-existing broken libsignal/@whiskeysockets/baileys install in the WhatsApp worker, unrelated to this renderer-only change

CDP assertion

e2e/assertions/chat-text-size.assert.ts drives the real control over CDP and
asserts, at each of Default → Large → Small → Default:

  1. the selected segment's aria-checked,
  2. data-chat-text-size on <html>,
  3. the resolved --chat-font-scale (1 / 1.15 / 0.9),
  4. the persisted localStorage value, and
  5. the computed font-size of a probe element carrying the real
    .chat-text-scope class — asserting the Large/Default ratio ≈ 1.15 and
    Small/Default ≈ 0.9 (a real pixel-level effect, not just an attribute flip).

The assertion could not be executed in this environment: launching Electron
requires downloading the Electron runtime binary, and that host is blocked by
the sandbox's egress policy (HTTP 403) — the same infrastructure limitation
documented on the merged #51. xvfb is present; only the binary fetch is
blocked. The assertion is written to run under the existing bun run e2e
harness once an environment with the Electron binary is available.

Scope

Frontend only: apps/electron (renderer) + packages/shared locale JSON. No
backend / runtime changes.


Generated by Claude Code

Introduces a renderer-only preference that scales the conversation
transcript's typography without resizing the rest of the app (sidebar,
top bar, composer, icons stay at their native size). This differs from
the OS-level window zoom wired to the View menu, which scales the whole
UI and is not a persisted appearance preference.

Mechanism mirrors the existing ReduceMotionContext idiom:
- ChatTextSizeProvider persists the choice in localStorage
  (craft-chat-text-size) and reflects it onto <html> as
  data-chat-text-size="small|medium|large".
- Global CSS maps that attribute to a --chat-font-scale custom property
  (0.9 / 1 / 1.15). The transcript container in ChatDisplay carries a
  .chat-text-scope class whose font-size: calc(1em * var(--chat-font-scale))
  is em-relative, so Default is exactly neutral and only the conversation
  body text scales.
- Segmented control added to Settings -> Appearance -> Interface.
- SettingsSegmentedControl gained an optional testId (adds data-testid on
  the group and data-value on each option) for stable, label-independent
  targeting.
- 5 new i18n keys across all 7 locales.

Adds a CDP assertion (e2e/assertions/chat-text-size.assert.ts) that drives
the real control and verifies the html attribute, the resolved CSS
variable, the persisted value, and the computed font-size of a probe
using the real .chat-text-scope class (ratio ~1.15 at Large, ~0.9 at
Small), covering apply / change / revert.

Closes #64
@DragonnZhang DragonnZhang added enhancement New feature or request loop-bot labels Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request loop-bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a "Chat text size" setting (Small / Default / Large) to Appearance

2 participants