From ec7186430b39712331d4aca84637b73aec67330c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 7 Jul 2026 01:39:26 +0000 Subject: [PATCH] Add a "Chat text size" setting (Small / Default / Large) to Appearance 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 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 --- .../components/app-shell/ChatDisplay.tsx | 2 +- .../settings/SettingsSegmentedControl.tsx | 7 +- .../renderer/context/ChatTextSizeContext.tsx | 90 +++++++++ apps/electron/src/renderer/index.css | 20 ++ .../src/renderer/lib/local-storage.ts | 1 + apps/electron/src/renderer/main.tsx | 9 +- .../pages/settings/AppearanceSettingsPage.tsx | 18 ++ docs/loop/feature-ledger.md | 1 + e2e/assertions/chat-text-size.assert.ts | 185 ++++++++++++++++++ packages/shared/src/i18n/locales/de.json | 9 +- packages/shared/src/i18n/locales/en.json | 9 +- packages/shared/src/i18n/locales/es.json | 9 +- packages/shared/src/i18n/locales/hu.json | 9 +- packages/shared/src/i18n/locales/ja.json | 9 +- packages/shared/src/i18n/locales/pl.json | 9 +- packages/shared/src/i18n/locales/zh-Hans.json | 9 +- 16 files changed, 377 insertions(+), 19 deletions(-) create mode 100644 apps/electron/src/renderer/context/ChatTextSizeContext.tsx create mode 100644 e2e/assertions/chat-text-size.assert.ts diff --git a/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx b/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx index 9ee13a3c1..487dfc210 100644 --- a/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx +++ b/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx @@ -1717,7 +1717,7 @@ export const ChatDisplay = React.forwardRef style={compactMode ? undefined : { maxWidth: 'var(--chat-content-max-width, 840px)' }} className={cn( compactMode && CHAT_LAYOUT.maxWidth, - "mx-auto min-w-0", + "chat-text-scope mx-auto min-w-0", compactMode ? "px-3 py-4 space-y-2" : [CHAT_LAYOUT.containerPadding, CHAT_LAYOUT.messageSpacing] )}> {/* Session-level AnimatePresence: Prevents layout jump when switching sessions */} diff --git a/apps/electron/src/renderer/components/settings/SettingsSegmentedControl.tsx b/apps/electron/src/renderer/components/settings/SettingsSegmentedControl.tsx index 075fbda77..153cf570f 100644 --- a/apps/electron/src/renderer/components/settings/SettingsSegmentedControl.tsx +++ b/apps/electron/src/renderer/components/settings/SettingsSegmentedControl.tsx @@ -28,7 +28,12 @@ export interface SettingsSegmentedControlProps { size?: 'sm' | 'md' /** Additional className */ className?: string - /** Optional test id — applied to the group and, suffixed with `-`, to each option */ + /** + * Optional test id. When set, `data-testid` is applied to the group and, + * suffixed with `-`, to each option. Every option button also + * carries a `data-value` attribute, so tests can address a specific + * segment without depending on its (localized) label. + */ testId?: string } diff --git a/apps/electron/src/renderer/context/ChatTextSizeContext.tsx b/apps/electron/src/renderer/context/ChatTextSizeContext.tsx new file mode 100644 index 000000000..1a16896b8 --- /dev/null +++ b/apps/electron/src/renderer/context/ChatTextSizeContext.tsx @@ -0,0 +1,90 @@ +/** + * ChatTextSizeContext + * + * App-wide "Chat text size" preference — scales the typography of the + * conversation transcript without resizing the rest of the app. + * + * Unlike the OS-level window zoom wired to the View menu + * (`webContents.setZoomFactor`), which scales the entire UI (sidebar, toolbar, + * composer, icons, spacing), this preference only affects the reading text in + * chat. It works by reflecting the choice onto `` as + * `data-chat-text-size="small|medium|large"`; the global CSS in `index.css` + * maps that to a `--chat-font-scale` custom property, which the chat transcript + * container (`.chat-text-scope` in `ChatDisplay`) consumes via + * `font-size: calc(1em * var(--chat-font-scale))`. Because the scale is + * `em`-relative it is exactly neutral at `medium` (1×) and scales the inherited + * message text up/down at `small` (0.9×) / `large` (1.15×). + * + * The preference is persisted in `localStorage` (renderer-only, no backend), + * mirroring the other lightweight UI prefs in `lib/local-storage.ts` and the + * sibling `ReduceMotionContext`. + */ + +import React, { + createContext, + useContext, + useState, + useEffect, + useCallback, + type ReactNode, +} from 'react' +import * as storage from '@/lib/local-storage' + +export type ChatTextSize = 'small' | 'medium' | 'large' + +const CHAT_TEXT_SIZES: readonly ChatTextSize[] = ['small', 'medium', 'large'] + +const DEFAULT_CHAT_TEXT_SIZE: ChatTextSize = 'medium' + +interface ChatTextSizeContextType { + chatTextSize: ChatTextSize + setChatTextSize: (value: ChatTextSize) => void +} + +const ChatTextSizeContext = createContext(null) + +const CHAT_TEXT_SIZE_ATTR = 'data-chat-text-size' + +/** Guard against malformed persisted values. */ +function normalize(value: unknown): ChatTextSize { + return CHAT_TEXT_SIZES.includes(value as ChatTextSize) + ? (value as ChatTextSize) + : DEFAULT_CHAT_TEXT_SIZE +} + +/** Reflect the preference onto so the global CSS var can react. */ +function applyChatTextSizeAttribute(size: ChatTextSize): void { + document.documentElement.setAttribute(CHAT_TEXT_SIZE_ATTR, size) +} + +export function ChatTextSizeProvider({ children }: { children: ReactNode }) { + const [chatTextSize, setChatTextSizeState] = useState(() => + normalize(storage.get(storage.KEYS.chatTextSize, DEFAULT_CHAT_TEXT_SIZE)), + ) + + // Keep the DOM attribute in sync (also covers the initial value on mount). + useEffect(() => { + applyChatTextSizeAttribute(chatTextSize) + }, [chatTextSize]) + + const setChatTextSize = useCallback((value: ChatTextSize) => { + const next = normalize(value) + setChatTextSizeState(next) + storage.set(storage.KEYS.chatTextSize, next) + applyChatTextSizeAttribute(next) + }, []) + + return ( + + {children} + + ) +} + +export function useChatTextSize(): ChatTextSizeContextType { + const ctx = useContext(ChatTextSizeContext) + if (!ctx) { + throw new Error('useChatTextSize must be used within a ChatTextSizeProvider') + } + return ctx +} diff --git a/apps/electron/src/renderer/index.css b/apps/electron/src/renderer/index.css index 5efb958b0..8d2eeb0a5 100644 --- a/apps/electron/src/renderer/index.css +++ b/apps/electron/src/renderer/index.css @@ -1386,3 +1386,23 @@ html.dark[data-scenic] .fullscreen-overlay-background { transition-delay: 0ms !important; scroll-behavior: auto !important; } + +/* Chat text size: the "Chat text size" control in Appearance settings reflects + its choice onto as data-chat-text-size. That maps to the + --chat-font-scale custom property, which the chat transcript container + (.chat-text-scope) consumes as `font-size: calc(1em * var(--chat-font-scale))`. + Being em-relative keeps the default (medium) exactly neutral and scales only + the conversation's reading text — the sidebar, top bar, composer, and icons + keep their native size. */ +:root { + --chat-font-scale: 1; +} +[data-chat-text-size='small'] { + --chat-font-scale: 0.9; +} +[data-chat-text-size='large'] { + --chat-font-scale: 1.15; +} +.chat-text-scope { + font-size: calc(1em * var(--chat-font-scale)); +} diff --git a/apps/electron/src/renderer/lib/local-storage.ts b/apps/electron/src/renderer/lib/local-storage.ts index c50b2f911..4d8be941c 100644 --- a/apps/electron/src/renderer/lib/local-storage.ts +++ b/apps/electron/src/renderer/lib/local-storage.ts @@ -63,6 +63,7 @@ export const KEYS = { showConnectionIcons: 'show-connection-icons', reduceMotion: 'reduce-motion', // Minimize animations/transitions app-wide conversationWidth: 'conversation-width', // Chat reading-column width: comfortable | wide | full + chatTextSize: 'chat-text-size', // Scale conversation text without resizing the app chrome // What's New whatsNewLastSeenVersion: 'whats-new-last-seen-version', diff --git a/apps/electron/src/renderer/main.tsx b/apps/electron/src/renderer/main.tsx index 768032fb8..a7aeddd2d 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -8,6 +8,7 @@ import App from './App' import { ThemeProvider } from './context/ThemeContext' import { ReduceMotionProvider } from './context/ReduceMotionContext' import { ConversationWidthProvider } from './context/ConversationWidthContext' +import { ChatTextSizeProvider } from './context/ChatTextSizeContext' import { windowWorkspaceIdAtom } from './atoms/sessions' import { Toaster } from '@/components/ui/sonner' import { PetWindowController } from '@/components/pet/PetWindowController' @@ -110,9 +111,11 @@ function Root() { - - - + + + + + diff --git a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx index cde36c00c..e99e37d22 100644 --- a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx +++ b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx @@ -16,6 +16,7 @@ import { EditPopover, EditButton, getEditConfig } from '@/components/ui/EditPopo import { useTheme } from '@/context/ThemeContext' import { useReduceMotion } from '@/context/ReduceMotionContext' import { useConversationWidth } from '@/context/ConversationWidthContext' +import { useChatTextSize, type ChatTextSize } from '@/context/ChatTextSizeContext' import { useAppShellContext } from '@/context/AppShellContext' import { routes } from '@/lib/navigate' import { FolderOpen, Monitor, RefreshCw, Sun, Moon } from 'lucide-react' @@ -146,6 +147,8 @@ export default function AppearanceSettingsPage() { // Conversation width (renderer-only preference, persisted in localStorage) const { conversationWidth, setConversationWidth } = useConversationWidth() + // Chat text size (renderer-only preference, persisted in localStorage) + const { chatTextSize, setChatTextSize } = useChatTextSize() // Pet companion settings + custom pets (synced via shared Jotai atoms) const { @@ -406,6 +409,21 @@ export default function AppearanceSettingsPage() { ]} /> + + setChatTextSize(value as ChatTextSize)} + testId="chat-text-size-control" + options={[ + { value: 'small', label: t("settings.appearance.chatTextSizeSmall") }, + { value: 'medium', label: t("settings.appearance.chatTextSizeDefault") }, + { value: 'large', label: t("settings.appearance.chatTextSizeLarge") }, + ]} + /> + diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md index 2d2e73c0b..3c8ff0a08 100644 --- a/docs/loop/feature-ledger.md +++ b/docs/loop/feature-ledger.md @@ -33,6 +33,7 @@ log, not the system of record. | slug | title | source | feasibility | status | issue | pr | branch | updated | notes | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| chat-text-size | "Chat text size" setting (Small / Default / Large) in Appearance | Claude Desktop "Chat font" + anthropics/claude-code #50543/#48887; ChatGPT desktop font-size requests | frontend-only | pr-open | [#64](https://github.com/modelstudioai/openwork/issues/64) | [#65](https://github.com/modelstudioai/openwork/pull/65) | loop/chat-text-size | 2026-07-07 | Renderer-only pref (localStorage `craft-chat-text-size`) reflected onto `` as `data-chat-text-size` + `--chat-font-scale` CSS var (0.9/1/1.15). Transcript container gets `.chat-text-scope` with `font-size: calc(1em * var(--chat-font-scale))` — em-relative ⇒ neutral at Default, scales only conversation text (chrome untouched). New `ChatTextSizeProvider`; segmented control in Appearance→Interface; `SettingsSegmentedControl` gained optional `testId`/`data-value`; 5 new i18n keys ×7 locales. typecheck/`bun test` **zero-delta vs main** (11 pre-existing tsc errors + 56 pre-existing fail-lines byte-identical); renderer build ✅; i18n parity ✅. CDP assertion authored (drives control, asserts attr/CSS-var/localStorage + probe computed font-size ratio ~1.15/0.9); **could not execute** — Electron runtime binary download is org-egress-policy 403 (same block as #51). | | conversation-width | "Conversation width" (Comfortable / Wide / Full) setting in Appearance | Claude Desktop wider-chat / ChatGPT desktop width / Codex & VS Code content width | frontend-only | pr-open | [#62](https://github.com/modelstudioai/openwork/issues/62) | [#63](https://github.com/modelstudioai/openwork/pull/63) | loop/conversation-width | 2026-07-06 | Renderer-only pref (localStorage `craft-conversation-width`). New `ConversationWidthProvider` in `main.tsx` sets `data-conversation-width` on `` + drives CSS var `--chat-content-max-width` (840px/1100px/none). Transcript container (`ChatDisplay`) + composer (`ChatInputZone`) read it via `max-width: var(--chat-content-max-width, 840px)`; fallback keeps shared web viewer unchanged. Segmented control in Appearance→Interface; added `testId` to `SettingsSegmentedControl`; 5 new i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; i18n parity ✅. CDP assertion `e2e/assertions/conversation-width.assert.ts` included; **could not run locally** (egress 403 blocks Electron binary + `libsignal-node`/`eslint-config` git-tarball deps — same env blocker as #51). | | composer-word-count | Live word/character/line count indicator in the chat composer | Codex/Claude desktop composer counters + editor status bars | frontend-only | pr-open | [#60](https://github.com/modelstudioai/openwork/issues/60) | [#61](https://github.com/modelstudioai/openwork/pull/61) | loop/composer-word-count | 2026-07-06 | Pure `computeComposerCounts` module (+unit tests) → `ComposerCountIndicator` in the composer toolbar, derived from the existing `input` string; hidden when empty / in compact mode. 3 new plural-neutral i18n keys (`chat.composer{Words,Characters,Lines}`) in all 7 locales. typecheck:all zero-delta (11 pre-existing baseline errors, none in touched files); unit tests 10/10; i18n parity OK; renderer build ✅; assertion transpiles. **CDP could not run locally**: the e2e build 403s on the Electron binary download and the `libsignal` WhatsApp-worker dep (same egress block as prior loop PRs); `composer-count.assert.ts` included for CI/reviewer. | | shortcuts-search | Search box on the Settings → Keyboard Shortcuts page | Codex "keypress search" + VS Code / Claude keybindings search; mirrors OpenWork's own settings-navigator search (#40) | frontend-only | pr-open | [#58](https://github.com/modelstudioai/openwork/issues/58) | [#59](https://github.com/modelstudioai/openwork/pull/59) | loop/shortcuts-search | 2026-07-05 | Pure renderer view over the action registry (`actionsByCategory` + `getHotkeyDisplay`); filters rows by action label **and** rendered key tokens ("keypress search"), hides empty sections, shows empty state. Reuses `common.search`/`noResultsFound`/`clear` (**zero** new i18n keys). Added `data-testid="settings-item-"` to settings-nav items for e2e navigation. typecheck/renderer-build/i18n-parity zero-delta vs main; touched-area tests pass. CDP assertion written (app launch egress-blocked locally, same as prior rounds). | diff --git a/e2e/assertions/chat-text-size.assert.ts b/e2e/assertions/chat-text-size.assert.ts new file mode 100644 index 000000000..e3f72af9b --- /dev/null +++ b/e2e/assertions/chat-text-size.assert.ts @@ -0,0 +1,185 @@ +/** + * Feature assertion: the "Chat text size" segmented control in + * Settings → Appearance actually applies and persists an app-wide preference + * that scales conversation typography. + * + * Drives the real UI over CDP in the draft/no-session state (no seeded + * conversation, no backend): opens Settings → Appearance and clicks through the + * Small / Default / Large segments, asserting the observable effects at each + * step: + * - the selected segment's `aria-checked`, + * - the `data-chat-text-size` attribute on , + * - the resolved `--chat-font-scale` CSS custom property, + * - the persisted `localStorage` value, and + * - crucially, the *rendered* computed `font-size` of a probe element that + * carries the real `.chat-text-scope` class the transcript uses — proving + * the mechanism produces a real pixel-level text-size change, not merely an + * attribute flip. + * + * Cycling Default → Large → Small → Default proves it applies, changes, and + * reverts. + */ + +import type { Assertion } from '../runner'; + +const SETTINGS_NAV = '[data-testid="nav:settings"]'; +const APPEARANCE_NAV = '[data-testid="settings-nav-appearance"]'; +const CONTROL = '[data-testid="chat-text-size-control"]'; +const STORAGE_KEY = 'craft-chat-text-size'; + +/** Selector for a specific segment button. */ +function segment(value: string): string { + return `${CONTROL} [data-value="${value}"]`; +} + +/** aria-checked ("true" | "false" | null) for a specific segment. */ +function ariaCheckedExpr(value: string): string { + return `(() => { + const el = document.querySelector(${JSON.stringify(segment(value))}); + return el ? el.getAttribute('aria-checked') : null; + })()`; +} + +/** The current data-chat-text-size marker on . */ +const htmlAttrExpr = `document.documentElement.getAttribute('data-chat-text-size')`; + +/** The resolved --chat-font-scale custom property (trimmed string). */ +const cssScaleExpr = `getComputedStyle(document.documentElement).getPropertyValue('--chat-font-scale').trim()`; + +/** The persisted localStorage value for the preference. */ +const storedValueExpr = `window.localStorage.getItem(${JSON.stringify(STORAGE_KEY)})`; + +/** + * Computed font-size (px, number) of a hidden probe that carries the real + * `.chat-text-scope` class — i.e. the exact CSS the transcript applies. This + * reflects the live `--chat-font-scale` end-to-end (html attr → CSS var → + * rendered pixels). + */ +const probeFontSizeExpr = `(() => { + let p = document.getElementById('__cts_probe'); + if (!p) { + p = document.createElement('div'); + p.id = '__cts_probe'; + p.className = 'chat-text-scope'; + p.style.position = 'fixed'; + p.style.left = '-9999px'; + p.style.top = '0'; + p.textContent = 'probe'; + document.body.appendChild(p); + } + return parseFloat(getComputedStyle(p).fontSize); +})()`; + +const assertion: Assertion = { + name: 'chat text size control scales conversation typography and persists', + async run(app) { + const { session } = app; + + // App fully mounted. + await session.waitForFunction( + '!document.getElementById("_loader") && (document.getElementById("root")?.childElementCount ?? 0) > 0', + { timeoutMs: 30000, message: 'app did not mount' }, + ); + + // Open Settings → Appearance (real user path). + await session.click(SETTINGS_NAV, { timeoutMs: 15000 }); + await session.click(APPEARANCE_NAV, { timeoutMs: 15000 }); + + // The control is the feature under test — its presence is the first signal. + await session.waitForSelector(CONTROL, { + timeoutMs: 15000, + message: 'chat-text-size control did not render', + }); + + // Initial state: Default selected, marked "medium", scale 1. + const initialChecked = await session.evaluate(ariaCheckedExpr('medium')); + if (initialChecked !== 'true') { + throw new Error(`expected Default selected initially, saw medium aria-checked=${initialChecked}`); + } + if (await session.evaluate(htmlAttrExpr) !== 'medium') { + throw new Error('expected data-chat-text-size="medium" initially'); + } + + // Baseline rendered size at Default (scale 1). + const mediumScale = await session.evaluate(cssScaleExpr); + if (mediumScale !== '1') { + throw new Error(`expected --chat-font-scale "1" at Default, saw ${JSON.stringify(mediumScale)}`); + } + const mediumPx = await session.evaluate(probeFontSizeExpr); + if (!(mediumPx > 0)) { + throw new Error(`probe font-size at Default was not positive: ${mediumPx}`); + } + + // ----- Large ----- + await session.click(segment('large')); + await session.waitForFunction(`${ariaCheckedExpr('large')} === 'true'`, { + timeoutMs: 5000, + message: 'Large segment did not become selected', + }); + await session.waitForFunction(`${htmlAttrExpr} === 'large'`, { + timeoutMs: 5000, + message: 'data-chat-text-size was not set to "large"', + }); + const largeScale = await session.evaluate(cssScaleExpr); + if (largeScale !== '1.15') { + throw new Error(`expected --chat-font-scale "1.15" at Large, saw ${JSON.stringify(largeScale)}`); + } + const storedLarge = await session.evaluate(storedValueExpr); + if (storedLarge !== '"large"') { + throw new Error(`expected persisted "large", saw ${JSON.stringify(storedLarge)}`); + } + const largePx = await session.evaluate(probeFontSizeExpr); + // Real rendered effect: Large must be measurably bigger than Default. + if (!(largePx > mediumPx)) { + throw new Error(`expected Large probe font-size (${largePx}px) > Default (${mediumPx}px)`); + } + const largeRatio = largePx / mediumPx; + if (Math.abs(largeRatio - 1.15) > 0.02) { + throw new Error(`expected Large/Default font-size ratio ~1.15, saw ${largeRatio.toFixed(3)}`); + } + + // ----- Small ----- + await session.click(segment('small')); + await session.waitForFunction(`${ariaCheckedExpr('small')} === 'true'`, { + timeoutMs: 5000, + message: 'Small segment did not become selected', + }); + await session.waitForFunction(`${htmlAttrExpr} === 'small'`, { + timeoutMs: 5000, + message: 'data-chat-text-size was not set to "small"', + }); + const smallScale = await session.evaluate(cssScaleExpr); + if (smallScale !== '0.9') { + throw new Error(`expected --chat-font-scale "0.9" at Small, saw ${JSON.stringify(smallScale)}`); + } + const smallPx = await session.evaluate(probeFontSizeExpr); + if (!(smallPx < mediumPx)) { + throw new Error(`expected Small probe font-size (${smallPx}px) < Default (${mediumPx}px)`); + } + const smallRatio = smallPx / mediumPx; + if (Math.abs(smallRatio - 0.9) > 0.02) { + throw new Error(`expected Small/Default font-size ratio ~0.9, saw ${smallRatio.toFixed(3)}`); + } + + // ----- Revert to Default ----- + await session.click(segment('medium')); + await session.waitForFunction(`${ariaCheckedExpr('medium')} === 'true'`, { + timeoutMs: 5000, + message: 'Default segment did not become re-selected', + }); + await session.waitForFunction(`${htmlAttrExpr} === 'medium'`, { + timeoutMs: 5000, + message: 'data-chat-text-size did not revert to "medium"', + }); + const revertedPx = await session.evaluate(probeFontSizeExpr); + if (Math.abs(revertedPx - mediumPx) > 0.5) { + throw new Error(`expected probe font-size to revert to Default (${mediumPx}px), saw ${revertedPx}px`); + } + const storedReverted = await session.evaluate(storedValueExpr); + if (storedReverted !== '"medium"') { + throw new Error(`expected persisted "medium" after revert, saw ${JSON.stringify(storedReverted)}`); + } + }, +}; + +export default assertion; diff --git a/packages/shared/src/i18n/locales/de.json b/packages/shared/src/i18n/locales/de.json index 60c31ee43..29055aed2 100644 --- a/packages/shared/src/i18n/locales/de.json +++ b/packages/shared/src/i18n/locales/de.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "Keine Werkzeugsymbol-Zuordnungen gefunden", "settings.appearance.reduceMotion": "Bewegung reduzieren", "settings.appearance.reduceMotionDesc": "Animationen und Übergänge in der gesamten App minimieren.", + "settings.appearance.chatTextSize": "Chat-Textgröße", + "settings.appearance.chatTextSizeDefault": "Standard", + "settings.appearance.chatTextSizeDesc": "Skaliert den Text in Unterhaltungen, ohne die restliche App zu vergrößern.", + "settings.appearance.chatTextSizeLarge": "Groß", + "settings.appearance.chatTextSizeSmall": "Klein", "settings.appearance.conversationWidth": "Unterhaltungsbreite", - "settings.appearance.conversationWidthDesc": "Legt fest, wie breit der Chatverlauf und das Eingabefeld sind.", "settings.appearance.conversationWidthComfortable": "Komfortabel", - "settings.appearance.conversationWidthWide": "Breit", + "settings.appearance.conversationWidthDesc": "Legt fest, wie breit der Chatverlauf und das Eingabefeld sind.", "settings.appearance.conversationWidthFull": "Voll", + "settings.appearance.conversationWidthWide": "Breit", "settings.appearance.pet": "Begleiter", "settings.appearance.petCustom": "Eigene Begleiter", "settings.appearance.petCustomHint": "Füge einen Ordner mit pet.json und einem 8x9-Spritesheet hinzu und aktualisiere dann.", diff --git a/packages/shared/src/i18n/locales/en.json b/packages/shared/src/i18n/locales/en.json index e336760eb..b47b14006 100644 --- a/packages/shared/src/i18n/locales/en.json +++ b/packages/shared/src/i18n/locales/en.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "No tool icon mappings found", "settings.appearance.reduceMotion": "Reduce motion", "settings.appearance.reduceMotionDesc": "Minimize animations and transitions throughout the app.", + "settings.appearance.chatTextSize": "Chat text size", + "settings.appearance.chatTextSizeDefault": "Default", + "settings.appearance.chatTextSizeDesc": "Scale the text in conversations without resizing the rest of the app.", + "settings.appearance.chatTextSizeLarge": "Large", + "settings.appearance.chatTextSizeSmall": "Small", "settings.appearance.conversationWidth": "Conversation width", - "settings.appearance.conversationWidthDesc": "Set how wide the chat transcript and composer are.", "settings.appearance.conversationWidthComfortable": "Comfortable", - "settings.appearance.conversationWidthWide": "Wide", + "settings.appearance.conversationWidthDesc": "Set how wide the chat transcript and composer are.", "settings.appearance.conversationWidthFull": "Full", + "settings.appearance.conversationWidthWide": "Wide", "settings.appearance.pet": "Pet", "settings.appearance.petCustom": "Custom pets", "settings.appearance.petCustomHint": "Add a pet folder with pet.json and an 8x9 spritesheet, then refresh.", diff --git a/packages/shared/src/i18n/locales/es.json b/packages/shared/src/i18n/locales/es.json index e9c70c84c..6f1be1780 100644 --- a/packages/shared/src/i18n/locales/es.json +++ b/packages/shared/src/i18n/locales/es.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "No se encontraron asignaciones de iconos de herramientas", "settings.appearance.reduceMotion": "Reducir movimiento", "settings.appearance.reduceMotionDesc": "Minimiza las animaciones y transiciones en toda la aplicación.", + "settings.appearance.chatTextSize": "Tamaño del texto del chat", + "settings.appearance.chatTextSizeDefault": "Predeterminado", + "settings.appearance.chatTextSizeDesc": "Ajusta el tamaño del texto en las conversaciones sin cambiar el resto de la aplicación.", + "settings.appearance.chatTextSizeLarge": "Grande", + "settings.appearance.chatTextSizeSmall": "Pequeño", "settings.appearance.conversationWidth": "Ancho de la conversación", - "settings.appearance.conversationWidthDesc": "Define el ancho de la transcripción del chat y del compositor.", "settings.appearance.conversationWidthComfortable": "Cómodo", - "settings.appearance.conversationWidthWide": "Ancho", + "settings.appearance.conversationWidthDesc": "Define el ancho de la transcripción del chat y del compositor.", "settings.appearance.conversationWidthFull": "Completo", + "settings.appearance.conversationWidthWide": "Ancho", "settings.appearance.pet": "Mascota", "settings.appearance.petCustom": "Mascotas personalizadas", "settings.appearance.petCustomHint": "Añade una carpeta con pet.json y un spritesheet de 8x9, luego actualiza.", diff --git a/packages/shared/src/i18n/locales/hu.json b/packages/shared/src/i18n/locales/hu.json index e9e8f3ce3..8a5318df0 100644 --- a/packages/shared/src/i18n/locales/hu.json +++ b/packages/shared/src/i18n/locales/hu.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "Nem található eszközikon-hozzárendelés", "settings.appearance.reduceMotion": "Mozgás csökkentése", "settings.appearance.reduceMotionDesc": "Az animációk és átmenetek minimalizálása az egész alkalmazásban.", + "settings.appearance.chatTextSize": "Csevegés szövegmérete", + "settings.appearance.chatTextSizeDefault": "Alapértelmezett", + "settings.appearance.chatTextSizeDesc": "A beszélgetések szövegének méretezése az alkalmazás többi részének átméretezése nélkül.", + "settings.appearance.chatTextSizeLarge": "Nagy", + "settings.appearance.chatTextSizeSmall": "Kicsi", "settings.appearance.conversationWidth": "Beszélgetés szélessége", - "settings.appearance.conversationWidthDesc": "Beállítja a csevegés és a szerkesztő szélességét.", "settings.appearance.conversationWidthComfortable": "Kényelmes", - "settings.appearance.conversationWidthWide": "Széles", + "settings.appearance.conversationWidthDesc": "Beállítja a csevegés és a szerkesztő szélességét.", "settings.appearance.conversationWidthFull": "Teljes", + "settings.appearance.conversationWidthWide": "Széles", "settings.appearance.pet": "Kabala", "settings.appearance.petCustom": "Egyéni kabalák", "settings.appearance.petCustomHint": "Adj hozzá egy mappát pet.json fájllal és 8x9-es spritesheettel, majd frissíts.", diff --git a/packages/shared/src/i18n/locales/ja.json b/packages/shared/src/i18n/locales/ja.json index 7d6960b6d..593972d14 100644 --- a/packages/shared/src/i18n/locales/ja.json +++ b/packages/shared/src/i18n/locales/ja.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "ツールアイコンのマッピングが見つかりません", "settings.appearance.reduceMotion": "モーションを減らす", "settings.appearance.reduceMotionDesc": "アプリ全体のアニメーションとトランジションを最小限にします。", + "settings.appearance.chatTextSize": "チャットの文字サイズ", + "settings.appearance.chatTextSizeDefault": "標準", + "settings.appearance.chatTextSizeDesc": "アプリの他の部分のサイズを変えずに、会話のテキストだけを拡大・縮小します。", + "settings.appearance.chatTextSizeLarge": "大", + "settings.appearance.chatTextSizeSmall": "小", "settings.appearance.conversationWidth": "会話の幅", - "settings.appearance.conversationWidthDesc": "チャット履歴と入力欄の表示幅を設定します。", "settings.appearance.conversationWidthComfortable": "標準", - "settings.appearance.conversationWidthWide": "ワイド", + "settings.appearance.conversationWidthDesc": "チャット履歴と入力欄の表示幅を設定します。", "settings.appearance.conversationWidthFull": "全幅", + "settings.appearance.conversationWidthWide": "ワイド", "settings.appearance.pet": "ペット", "settings.appearance.petCustom": "カスタムペット", "settings.appearance.petCustomHint": "pet.json と 8x9 のスプライトシートを含むフォルダーを追加してから更新します。", diff --git a/packages/shared/src/i18n/locales/pl.json b/packages/shared/src/i18n/locales/pl.json index 838193359..7731c6efc 100644 --- a/packages/shared/src/i18n/locales/pl.json +++ b/packages/shared/src/i18n/locales/pl.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "Nie znaleziono mapowań ikon narzędzi", "settings.appearance.reduceMotion": "Ogranicz ruch", "settings.appearance.reduceMotionDesc": "Ogranicz animacje i przejścia w całej aplikacji.", + "settings.appearance.chatTextSize": "Rozmiar tekstu czatu", + "settings.appearance.chatTextSizeDefault": "Domyślny", + "settings.appearance.chatTextSizeDesc": "Skaluje tekst w rozmowach bez zmiany rozmiaru reszty aplikacji.", + "settings.appearance.chatTextSizeLarge": "Duży", + "settings.appearance.chatTextSizeSmall": "Mały", "settings.appearance.conversationWidth": "Szerokość rozmowy", - "settings.appearance.conversationWidthDesc": "Określa szerokość transkrypcji czatu i pola tekstowego.", "settings.appearance.conversationWidthComfortable": "Komfortowa", - "settings.appearance.conversationWidthWide": "Szeroka", + "settings.appearance.conversationWidthDesc": "Określa szerokość transkrypcji czatu i pola tekstowego.", "settings.appearance.conversationWidthFull": "Pełna", + "settings.appearance.conversationWidthWide": "Szeroka", "settings.appearance.pet": "Maskotka", "settings.appearance.petCustom": "Własne maskotki", "settings.appearance.petCustomHint": "Dodaj folder z pet.json i arkuszem sprite'ów 8x9, a potem odśwież.", diff --git a/packages/shared/src/i18n/locales/zh-Hans.json b/packages/shared/src/i18n/locales/zh-Hans.json index 11f469a97..11e15195e 100644 --- a/packages/shared/src/i18n/locales/zh-Hans.json +++ b/packages/shared/src/i18n/locales/zh-Hans.json @@ -786,11 +786,16 @@ "settings.appearance.noToolIcons": "未找到工具图标映射", "settings.appearance.reduceMotion": "减少动态效果", "settings.appearance.reduceMotionDesc": "在整个应用中尽量减少动画和过渡效果。", + "settings.appearance.chatTextSize": "对话文字大小", + "settings.appearance.chatTextSizeDefault": "默认", + "settings.appearance.chatTextSizeDesc": "缩放对话中的文字,而不改变应用其余部分的大小。", + "settings.appearance.chatTextSizeLarge": "大", + "settings.appearance.chatTextSizeSmall": "小", "settings.appearance.conversationWidth": "对话宽度", - "settings.appearance.conversationWidthDesc": "设置聊天记录和输入框的显示宽度。", "settings.appearance.conversationWidthComfortable": "舒适", - "settings.appearance.conversationWidthWide": "宽", + "settings.appearance.conversationWidthDesc": "设置聊天记录和输入框的显示宽度。", "settings.appearance.conversationWidthFull": "全宽", + "settings.appearance.conversationWidthWide": "宽", "settings.appearance.pet": "宠物", "settings.appearance.petCustom": "自定义宠物", "settings.appearance.petCustomHint": "每只宠物一个文件夹,包含 pet.json 和 8x9 spritesheet,放好后刷新。",