diff --git a/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx b/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx index 1ee9b7973..da444af3a 100644 --- a/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx +++ b/apps/electron/src/renderer/components/app-shell/ChatDisplay.tsx @@ -1697,8 +1697,11 @@ export const ChatDisplay = React.forwardRef }} > -
diff --git a/apps/electron/src/renderer/components/app-shell/input/ChatInputZone.tsx b/apps/electron/src/renderer/components/app-shell/input/ChatInputZone.tsx index 02a021f06..0450f74f1 100644 --- a/apps/electron/src/renderer/components/app-shell/input/ChatInputZone.tsx +++ b/apps/electron/src/renderer/components/app-shell/input/ChatInputZone.tsx @@ -83,8 +83,9 @@ export function ChatInputZone({ return (
{ size?: 'sm' | 'md' /** Additional className */ className?: string + /** Optional test id — applied to the group and, suffixed with `-`, to each option */ + testId?: string } /** @@ -50,10 +52,12 @@ export function SettingsSegmentedControl({ options, size = 'md', className, + testId, }: SettingsSegmentedControlProps) { return (
{options.map((option) => { @@ -65,6 +69,8 @@ export function SettingsSegmentedControl({ type="button" role="radio" aria-checked={isSelected} + data-testid={testId ? `${testId}-${option.value}` : undefined} + data-value={option.value} onClick={() => onValueChange(option.value)} className={cn( 'flex items-center gap-1.5 rounded-lg transition-all', diff --git a/apps/electron/src/renderer/context/ConversationWidthContext.tsx b/apps/electron/src/renderer/context/ConversationWidthContext.tsx new file mode 100644 index 000000000..61160172c --- /dev/null +++ b/apps/electron/src/renderer/context/ConversationWidthContext.tsx @@ -0,0 +1,96 @@ +/** + * ConversationWidthContext + * + * App-wide "Conversation width" preference — controls the reading-column width + * of the chat transcript and composer, mirroring the width controls in + * comparable desktop clients (Claude Desktop, ChatGPT desktop, Codex / VS Code). + * + * Three modes: + * - `comfortable` (default) — the classic ~840px reading column; + * - `wide` — a roomier 1100px column for code-heavy conversations; + * - `full` — no max-width, uses the full available panel width. + * + * When applied it: + * - sets `data-conversation-width=""` on `` (for CSS hooks + tests); + * - drives a CSS custom property `--chat-content-max-width` on `` + * (`840px` / `1100px` / `none`). The transcript container and composer read + * it via `max-width: var(--chat-content-max-width, 840px)`, so the fallback + * keeps the shared web viewer (`packages/ui`) unchanged at 840px. + * + * The preference is persisted in `localStorage` (renderer-only, no backend), + * mirroring the other lightweight UI prefs in `lib/local-storage.ts`. + */ + +import React, { + createContext, + useContext, + useState, + useEffect, + useCallback, + type ReactNode, +} from 'react' +import * as storage from '@/lib/local-storage' + +export type ConversationWidth = 'comfortable' | 'wide' | 'full' + +/** Resolved CSS `max-width` value for each mode. */ +const MAX_WIDTH_BY_MODE: Record = { + comfortable: '840px', + wide: '1100px', + full: 'none', +} + +const CONVERSATION_WIDTH_ATTR = 'data-conversation-width' +const MAX_WIDTH_VAR = '--chat-content-max-width' + +const DEFAULT_WIDTH: ConversationWidth = 'comfortable' + +function isConversationWidth(value: unknown): value is ConversationWidth { + return value === 'comfortable' || value === 'wide' || value === 'full' +} + +interface ConversationWidthContextType { + conversationWidth: ConversationWidth + setConversationWidth: (value: ConversationWidth) => void +} + +const ConversationWidthContext = createContext(null) + +/** Reflect the preference onto so CSS + the transcript/composer can react. */ +function applyConversationWidth(mode: ConversationWidth): void { + const root = document.documentElement + root.setAttribute(CONVERSATION_WIDTH_ATTR, mode) + root.style.setProperty(MAX_WIDTH_VAR, MAX_WIDTH_BY_MODE[mode]) +} + +export function ConversationWidthProvider({ children }: { children: ReactNode }) { + const [conversationWidth, setConversationWidthState] = useState(() => { + const stored = storage.get(storage.KEYS.conversationWidth, DEFAULT_WIDTH) + return isConversationWidth(stored) ? stored : DEFAULT_WIDTH + }) + + // Keep the DOM in sync (also covers the initial value on mount). + useEffect(() => { + applyConversationWidth(conversationWidth) + }, [conversationWidth]) + + const setConversationWidth = useCallback((value: ConversationWidth) => { + setConversationWidthState(value) + storage.set(storage.KEYS.conversationWidth, value) + applyConversationWidth(value) + }, []) + + return ( + + {children} + + ) +} + +export function useConversationWidth(): ConversationWidthContextType { + const ctx = useContext(ConversationWidthContext) + if (!ctx) { + throw new Error('useConversationWidth must be used within a ConversationWidthProvider') + } + return ctx +} diff --git a/apps/electron/src/renderer/lib/local-storage.ts b/apps/electron/src/renderer/lib/local-storage.ts index 4d6446afd..2b087f447 100644 --- a/apps/electron/src/renderer/lib/local-storage.ts +++ b/apps/electron/src/renderer/lib/local-storage.ts @@ -53,6 +53,7 @@ export const KEYS = { // Appearance showConnectionIcons: 'show-connection-icons', reduceMotion: 'reduce-motion', // Minimize animations/transitions app-wide + conversationWidth: 'conversation-width', // Chat reading-column width: comfortable | wide | full // 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 14729366d..768032fb8 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -7,6 +7,7 @@ import { Provider as JotaiProvider, useAtomValue } from 'jotai' import App from './App' import { ThemeProvider } from './context/ThemeContext' import { ReduceMotionProvider } from './context/ReduceMotionContext' +import { ConversationWidthProvider } from './context/ConversationWidthContext' import { windowWorkspaceIdAtom } from './atoms/sessions' import { Toaster } from '@/components/ui/sonner' import { PetWindowController } from '@/components/pet/PetWindowController' @@ -108,9 +109,11 @@ function Root() { return ( - - - + + + + + ) diff --git a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx index 4da9ba24c..cde36c00c 100644 --- a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx +++ b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx @@ -15,6 +15,7 @@ import { HeaderMenu } from '@/components/ui/HeaderMenu' import { EditPopover, EditButton, getEditConfig } from '@/components/ui/EditPopover' import { useTheme } from '@/context/ThemeContext' import { useReduceMotion } from '@/context/ReduceMotionContext' +import { useConversationWidth } from '@/context/ConversationWidthContext' import { useAppShellContext } from '@/context/AppShellContext' import { routes } from '@/lib/navigate' import { FolderOpen, Monitor, RefreshCw, Sun, Moon } from 'lucide-react' @@ -143,6 +144,9 @@ export default function AppearanceSettingsPage() { // Reduce motion toggle (renderer-only preference, persisted in localStorage) const { reduceMotion, setReduceMotion } = useReduceMotion() + // Conversation width (renderer-only preference, persisted in localStorage) + const { conversationWidth, setConversationWidth } = useConversationWidth() + // Pet companion settings + custom pets (synced via shared Jotai atoms) const { pets, @@ -387,6 +391,21 @@ export default function AppearanceSettingsPage() { onCheckedChange={setReduceMotion} testId="reduce-motion-toggle" /> + + + diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md index 51235562a..399c3a8aa 100644 --- a/docs/loop/feature-ledger.md +++ b/docs/loop/feature-ledger.md @@ -32,7 +32,13 @@ log, not the system of record. | slug | title | source | feasibility | status | issue | pr | branch | updated | notes | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | pr-open | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-03 | Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; i18n parity ✅. CDP assertion included; **could not run locally** (egress 403s Electron binary download). | +| 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-count | Live word / character count indicator in the chat composer | Codex desktop / VS Code / Google Docs status bars | frontend-only | pr-open | [#60](https://github.com/modelstudioai/openwork/issues/60) | [#61](https://github.com/modelstudioai/openwork/pull/61) | loop/composer-count | 2026-07-05 | Opened by a prior run. Draft word count + tooltip (words/chars/lines) in composer toolbar. Awaiting review. | +| shortcuts-search | Search box on the Settings → Keyboard Shortcuts page | Claude Code Desktop / VS Code keybinding search | 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-04 | Opened by a prior run. Awaiting review. | +| recent-commands | Surface recently-used commands in the Command Palette (⌘K) | Claude Code Desktop / VS Code recently-used | frontend-only | pr-open | [#56](https://github.com/modelstudioai/openwork/issues/56) | [#57](https://github.com/modelstudioai/openwork/pull/57) | loop/recent-commands | 2026-07-04 | Opened by a prior run. Awaiting review. | +| thinking-shortcut | Keyboard shortcut (⌘⇧E) to open the composer's thinking menu | Claude Code Desktop effort menu ⌘⇧E | frontend-only | pr-open | [#54](https://github.com/modelstudioai/openwork/issues/54) | [#55](https://github.com/modelstudioai/openwork/pull/55) | loop/thinking-shortcut | 2026-07-03 | Opened by a prior run. Awaiting review. | +| prompt-history | Recall previously-sent prompts in the composer with Up / Down arrows | Claude Code / shell / ChatGPT prompt history | frontend-only | pr-open | [#52](https://github.com/modelstudioai/openwork/issues/52) | [#53](https://github.com/modelstudioai/openwork/pull/53) | loop/prompt-history | 2026-07-03 | Opened by a prior run. Awaiting review. | +| reduce-motion | "Reduce motion" accessibility setting in Appearance | Claude desktop / macOS / Windows reduce-motion + `prefers-reduced-motion` | frontend-only | merged | [#50](https://github.com/modelstudioai/openwork/issues/50) | [#51](https://github.com/modelstudioai/openwork/pull/51) | loop/reduce-motion | 2026-07-06 | **Merged** into `main`. Renderer-only pref (localStorage) applied app-wide via `` + `data-reduce-motion` on `` + global CSS guard. Off ⇒ `reducedMotion="user"` (still honors OS). New `ReduceMotionProvider` in `main.tsx`; toggle in Appearance→Interface; 2 new i18n keys ×7 locales. | | composer-expand | Expand / collapse (maximize) toggle for the chat composer | Claude/ChatGPT/Codex desktop composer maximize | frontend-only | pr-open | [#48](https://github.com/modelstudioai/openwork/issues/48) | [#49](https://github.com/modelstudioai/openwork/pull/49) | loop/composer-expand | 2026-07-03 | Opened by a prior run. Adds `isComposerExpanded` toggle in `FreeFormInput`; 2 new i18n keys. Awaiting review. | | scroll-to-bottom | "Jump to latest" (scroll-to-bottom) button in the chat transcript | Claude Code / ChatGPT / Codex desktop | frontend-only | pr-open | [#46](https://github.com/modelstudioai/openwork/issues/46) | [#47](https://github.com/modelstudioai/openwork/pull/47) | loop/scroll-to-bottom | 2026-07-02 | Opened by a prior run. Floating jump button in `ChatDisplay` + `seed()` harness hook. Awaiting review. | | thinking-level-picker | Thinking-level (reasoning effort) picker in the chat composer | Claude Code Desktop effort menu (⌘⇧E) + OpenWork's own model picker | frontend-only | merged | [#44](https://github.com/modelstudioai/openwork/issues/44) | [#45](https://github.com/modelstudioai/openwork/pull/45) | loop/thinking-level-picker | 2026-07-03 | **Merged** into `main` (2026-07-02). `thinkingLevel`/`onThinkingLevelChange` already plumbed to `FreeFormInput`; only the UI trigger was missing. Reuses `thinking.*` + `settings.ai.thinking` i18n keys (zero new keys). | diff --git a/e2e/assertions/conversation-width.assert.ts b/e2e/assertions/conversation-width.assert.ts new file mode 100644 index 000000000..2e0b8c1ce --- /dev/null +++ b/e2e/assertions/conversation-width.assert.ts @@ -0,0 +1,123 @@ +/** + * Feature assertion: the "Conversation width" segmented control in + * Settings → Appearance actually applies and persists an app-wide reading-column + * width preference. + * + * Drives the real UI over CDP entirely in the draft/no-session state (no seeded + * conversation, no backend connection): opens Settings → Appearance, selects each + * width option, and asserts the observable effects that the transcript + composer + * consume — the selected radio state, the `data-conversation-width` attribute on + * , the computed `--chat-content-max-width` CSS custom property (the value + * the chat containers read via `max-width: var(--chat-content-max-width, 840px)`), + * and the persisted localStorage value. + * + * Cycling through all three options proves it both applies and reverts, not merely + * renders. + */ + +import type { Assertion } from '../runner'; + +const SETTINGS_NAV = '[data-testid="nav:settings"]'; +const APPEARANCE_NAV = '[data-testid="settings-nav-appearance"]'; +const CONTROL = '[data-testid="conversation-width-control"]'; +const STORAGE_KEY = 'craft-conversation-width'; + +type Mode = 'comfortable' | 'wide' | 'full'; + +const EXPECTED_MAX_WIDTH: Record = { + comfortable: '840px', + wide: '1100px', + full: 'none', +}; + +/** aria-checked ("true" | "false" | null) for a given option button. */ +function optionCheckedExpr(mode: Mode): string { + return `(() => { + const el = document.querySelector('[data-testid="conversation-width-control-${mode}"]'); + return el ? el.getAttribute('aria-checked') : null; + })()`; +} + +/** The `data-conversation-width` marker value on . */ +function htmlModeExpr(): string { + return `document.documentElement.getAttribute('data-conversation-width')`; +} + +/** The computed `--chat-content-max-width` custom property on . */ +function cssVarExpr(): string { + return `getComputedStyle(document.documentElement).getPropertyValue('--chat-content-max-width').trim()`; +} + +/** The persisted localStorage value (JSON-encoded string) for the preference. */ +function storedValueExpr(): string { + return `window.localStorage.getItem(${JSON.stringify(STORAGE_KEY)})`; +} + +const assertion: Assertion = { + name: 'conversation-width control applies and persists an app-wide width preference', + 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: 'conversation-width control did not render', + }); + + // Initial state: comfortable selected, marked comfortable, 840px column. + const initialChecked = await session.evaluate(optionCheckedExpr('comfortable')); + if (initialChecked !== 'true') { + throw new Error(`expected "comfortable" selected initially, saw aria-checked=${initialChecked}`); + } + const initialMode = await session.evaluate(htmlModeExpr()); + if (initialMode !== 'comfortable') { + throw new Error(`expected data-conversation-width="comfortable" initially, saw ${JSON.stringify(initialMode)}`); + } + const initialVar = await session.evaluate(cssVarExpr()); + if (initialVar !== EXPECTED_MAX_WIDTH.comfortable) { + throw new Error(`expected --chat-content-max-width=840px initially, saw ${JSON.stringify(initialVar)}`); + } + + // Selecting each mode applies + persists; the final "comfortable" proves revert. + const sequence: Mode[] = ['full', 'wide', 'comfortable']; + for (const mode of sequence) { + await session.click(`[data-testid="conversation-width-control-${mode}"]`); + + // Radio state flips on. + await session.waitForFunction( + `${optionCheckedExpr(mode)} === 'true'`, + { timeoutMs: 5000, message: `"${mode}" option did not become selected` }, + ); + + // marker updates. + await session.waitForFunction( + `${htmlModeExpr()} === ${JSON.stringify(mode)}`, + { timeoutMs: 5000, message: `data-conversation-width did not update to "${mode}"` }, + ); + + // CSS custom property (what the chat containers actually read) updates. + await session.waitForFunction( + `${cssVarExpr()} === ${JSON.stringify(EXPECTED_MAX_WIDTH[mode])}`, + { timeoutMs: 5000, message: `--chat-content-max-width did not become ${EXPECTED_MAX_WIDTH[mode]} for "${mode}"` }, + ); + + // Persisted (JSON-encoded string). + const stored = await session.evaluate(storedValueExpr()); + if (stored !== JSON.stringify(mode)) { + throw new Error(`expected persisted ${JSON.stringify(JSON.stringify(mode))} after selecting "${mode}", saw ${JSON.stringify(stored)}`); + } + } + }, +}; + +export default assertion; diff --git a/packages/shared/src/i18n/locales/de.json b/packages/shared/src/i18n/locales/de.json index d407e5dbb..1d729f67b 100644 --- a/packages/shared/src/i18n/locales/de.json +++ b/packages/shared/src/i18n/locales/de.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "Ausführliche Werkzeugbeschreibungen", "settings.appearance.reduceMotion": "Bewegung reduzieren", "settings.appearance.reduceMotionDesc": "Animationen und Übergänge in der gesamten App minimieren.", + "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.conversationWidthFull": "Voll", "settings.appearance.pet": "Begleiter", "settings.appearance.petDesc": "Ein Begleiter, der auf die Aktivität des Agents reagiert.", "settings.appearance.petEnabled": "Begleiter anzeigen", diff --git a/packages/shared/src/i18n/locales/en.json b/packages/shared/src/i18n/locales/en.json index 6a06e456e..70d0a258b 100644 --- a/packages/shared/src/i18n/locales/en.json +++ b/packages/shared/src/i18n/locales/en.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "Rich tool descriptions", "settings.appearance.reduceMotion": "Reduce motion", "settings.appearance.reduceMotionDesc": "Minimize animations and transitions throughout the app.", + "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.conversationWidthFull": "Full", "settings.appearance.pet": "Pet", "settings.appearance.petDesc": "A companion that reacts to what the agent is doing.", "settings.appearance.petEnabled": "Show pet companion", diff --git a/packages/shared/src/i18n/locales/es.json b/packages/shared/src/i18n/locales/es.json index 6461320a6..456a3d5e5 100644 --- a/packages/shared/src/i18n/locales/es.json +++ b/packages/shared/src/i18n/locales/es.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "Descripciones detalladas de herramientas", "settings.appearance.reduceMotion": "Reducir movimiento", "settings.appearance.reduceMotionDesc": "Minimiza las animaciones y transiciones en toda la aplicación.", + "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.conversationWidthFull": "Completo", "settings.appearance.pet": "Mascota", "settings.appearance.petDesc": "Un compañero que reacciona a lo que hace el agente.", "settings.appearance.petEnabled": "Mostrar mascota", diff --git a/packages/shared/src/i18n/locales/hu.json b/packages/shared/src/i18n/locales/hu.json index 641c6d16f..675ecba85 100644 --- a/packages/shared/src/i18n/locales/hu.json +++ b/packages/shared/src/i18n/locales/hu.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "Részletes eszközleírások", "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.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.conversationWidthFull": "Teljes", "settings.appearance.pet": "Kabala", "settings.appearance.petDesc": "Egy társ, aki reagál arra, amit az ügynök csinál.", "settings.appearance.petEnabled": "Kabala megjelenítése", diff --git a/packages/shared/src/i18n/locales/ja.json b/packages/shared/src/i18n/locales/ja.json index 5e2191990..13a15574d 100644 --- a/packages/shared/src/i18n/locales/ja.json +++ b/packages/shared/src/i18n/locales/ja.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "リッチなツール説明", "settings.appearance.reduceMotion": "モーションを減らす", "settings.appearance.reduceMotionDesc": "アプリ全体のアニメーションとトランジションを最小限にします。", + "settings.appearance.conversationWidth": "会話の幅", + "settings.appearance.conversationWidthDesc": "チャット履歴と入力欄の表示幅を設定します。", + "settings.appearance.conversationWidthComfortable": "標準", + "settings.appearance.conversationWidthWide": "ワイド", + "settings.appearance.conversationWidthFull": "全幅", "settings.appearance.pet": "ペット", "settings.appearance.petDesc": "エージェントの動きに反応するコンパニオン。", "settings.appearance.petEnabled": "ペットを表示", diff --git a/packages/shared/src/i18n/locales/pl.json b/packages/shared/src/i18n/locales/pl.json index f85aa9c9f..c55768b19 100644 --- a/packages/shared/src/i18n/locales/pl.json +++ b/packages/shared/src/i18n/locales/pl.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "Rozbudowane opisy narzędzi", "settings.appearance.reduceMotion": "Ogranicz ruch", "settings.appearance.reduceMotionDesc": "Ogranicz animacje i przejścia w całej aplikacji.", + "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.conversationWidthFull": "Pełna", "settings.appearance.pet": "Maskotka", "settings.appearance.petDesc": "Towarzysz reagujący na to, co robi agent.", "settings.appearance.petEnabled": "Pokaż maskotkę", diff --git a/packages/shared/src/i18n/locales/zh-Hans.json b/packages/shared/src/i18n/locales/zh-Hans.json index 845231913..968acf17a 100644 --- a/packages/shared/src/i18n/locales/zh-Hans.json +++ b/packages/shared/src/i18n/locales/zh-Hans.json @@ -787,6 +787,11 @@ "settings.appearance.richToolDescriptions": "丰富的工具描述", "settings.appearance.reduceMotion": "减少动态效果", "settings.appearance.reduceMotionDesc": "在整个应用中尽量减少动画和过渡效果。", + "settings.appearance.conversationWidth": "对话宽度", + "settings.appearance.conversationWidthDesc": "设置聊天记录和输入框的显示宽度。", + "settings.appearance.conversationWidthComfortable": "舒适", + "settings.appearance.conversationWidthWide": "宽", + "settings.appearance.conversationWidthFull": "全宽", "settings.appearance.pet": "宠物", "settings.appearance.petDesc": "一个会根据 agent 当前状态做出反应的小伙伴。", "settings.appearance.petEnabled": "显示宠物伙伴",