diff --git a/apps/electron/src/renderer/App.tsx b/apps/electron/src/renderer/App.tsx index 382f6d337..e53cf1ab5 100644 --- a/apps/electron/src/renderer/App.tsx +++ b/apps/electron/src/renderer/App.tsx @@ -18,6 +18,7 @@ import { OnboardingWizard, ReauthScreen } from '@/components/onboarding' import { WorkspacePicker } from '@/components/workspace' import { ResetConfirmationDialog } from '@/components/ResetConfirmationDialog' import { CommandPalette } from '@/components/CommandPalette' +import { ZoomHotkeys } from '@/components/ZoomHotkeys' import { SplashScreen } from '@/components/SplashScreen' import { TooltipProvider } from '@craft-agent/ui' import { FocusProvider } from '@/context/FocusContext' @@ -2501,6 +2502,9 @@ export default function App() { {/* Global command palette (⌘K / Ctrl+K) — search and run any action */} + {/* Interface zoom shortcuts (⌘+ / ⌘- / ⌘0) wired to the action registry */} + + {/* Splash screen overlay - fades out when fully ready */} {showSplash && ( > = { 'nav.goForwardAlt': 'shortcuts.action.goForward', 'view.toggleSidebar': 'shortcuts.action.toggleSidebar', 'view.toggleFocusMode': 'shortcuts.action.toggleFocusMode', + 'view.zoomIn': 'shortcuts.action.zoomIn', + 'view.zoomOut': 'shortcuts.action.zoomOut', + 'view.zoomReset': 'shortcuts.action.zoomReset', 'navigator.selectAll': 'shortcuts.action.selectAll', 'navigator.clearSelection': 'shortcuts.action.clearSelection', 'panel.focusNext': 'shortcuts.action.focusNextPanel', diff --git a/apps/electron/src/renderer/actions/definitions.ts b/apps/electron/src/renderer/actions/definitions.ts index f0fd6b792..16df3e502 100644 --- a/apps/electron/src/renderer/actions/definitions.ts +++ b/apps/electron/src/renderer/actions/definitions.ts @@ -143,6 +143,27 @@ export const actions = { defaultHotkey: 'mod+.', category: 'View', }, + 'view.zoomIn': { + id: 'view.zoomIn', + label: 'Zoom In', + description: 'Scale the whole interface up', + defaultHotkey: 'mod+=', + category: 'View', + }, + 'view.zoomOut': { + id: 'view.zoomOut', + label: 'Zoom Out', + description: 'Scale the whole interface down', + defaultHotkey: 'mod+-', + category: 'View', + }, + 'view.zoomReset': { + id: 'view.zoomReset', + label: 'Reset Zoom', + description: 'Reset the interface zoom to 100%', + defaultHotkey: 'mod+0', + category: 'View', + }, // ═══════════════════════════════════════════ // Navigator (scoped — active entity list in middle panel) diff --git a/apps/electron/src/renderer/components/ZoomHotkeys.tsx b/apps/electron/src/renderer/components/ZoomHotkeys.tsx new file mode 100644 index 000000000..27121fe60 --- /dev/null +++ b/apps/electron/src/renderer/components/ZoomHotkeys.tsx @@ -0,0 +1,23 @@ +/** + * ZoomHotkeys + * + * Bridges the interface-zoom preference to the action registry so the standard + * desktop zoom shortcuts (⌘+ / ⌘- / ⌘0) and their Command Palette entries drive + * `ZoomProvider`. Renders nothing — it only registers handlers. + * + * Must be mounted inside both `ZoomProvider` (for `useZoom`) and + * `ActionRegistryProvider` (for `useAction`). + */ + +import { useZoom } from '@/context/ZoomContext' +import { useAction } from '@/actions' + +export function ZoomHotkeys() { + const { zoomIn, zoomOut, resetZoom } = useZoom() + + useAction('view.zoomIn', zoomIn) + useAction('view.zoomOut', zoomOut) + useAction('view.zoomReset', resetZoom) + + return null +} diff --git a/apps/electron/src/renderer/context/ZoomContext.tsx b/apps/electron/src/renderer/context/ZoomContext.tsx new file mode 100644 index 000000000..9b04ec19a --- /dev/null +++ b/apps/electron/src/renderer/context/ZoomContext.tsx @@ -0,0 +1,140 @@ +/** + * ZoomContext + * + * App-wide "interface zoom" preference — scales the whole renderer (sidebar, + * navigator, chat, settings, dialogs) up or down, like the ⌘+ / ⌘- / ⌘0 zoom + * in Claude Desktop, VS Code, and other desktop apps. + * + * The zoom factor is one of a fixed set of Chrome-like steps and is applied by + * setting `document.documentElement.style.zoom` — a Chromium-native property + * that scales the entire tree, including portalled dialogs rendered into + * ``. At 100% the inline style is cleared so the DOM stays clean. + * + * The preference is persisted in `localStorage` (renderer-only, no backend), + * mirroring the other lightweight UI prefs in `lib/local-storage.ts` and the + * merged `ReduceMotionProvider`. + */ + +import React, { + createContext, + useContext, + useState, + useEffect, + useCallback, + useMemo, + type ReactNode, +} from 'react' +import * as storage from '@/lib/local-storage' + +/** Discrete zoom steps (factors), mirroring a browser's zoom ladder. */ +export const ZOOM_STEPS: readonly number[] = [0.5, 0.67, 0.75, 0.8, 0.9, 1, 1.1, 1.25, 1.5, 1.75, 2] + +/** The neutral / "actual size" factor. */ +export const DEFAULT_ZOOM = 1 + +interface ZoomContextType { + /** Current zoom factor (e.g. 1 = 100%, 1.1 = 110%). */ + zoom: number + /** Whole-number percentage for display (e.g. 110). */ + zoomPercent: number + /** Step up to the next larger factor (no-op at the max). */ + zoomIn: () => void + /** Step down to the next smaller factor (no-op at the min). */ + zoomOut: () => void + /** Return to 100%. */ + resetZoom: () => void + /** Whether a larger step is available. */ + canZoomIn: boolean + /** Whether a smaller step is available. */ + canZoomOut: boolean +} + +const ZoomContext = createContext(null) + +/** Clamp an arbitrary stored value onto the nearest valid step. */ +function normalizeZoom(value: number): number { + if (!Number.isFinite(value)) return DEFAULT_ZOOM + let nearest = ZOOM_STEPS[0] + let bestDelta = Math.abs(value - nearest) + for (const step of ZOOM_STEPS) { + const delta = Math.abs(value - step) + if (delta < bestDelta) { + nearest = step + bestDelta = delta + } + } + return nearest +} + +/** Reflect the factor onto so the whole renderer scales. */ +function applyZoom(factor: number): void { + const root = document.documentElement + if (factor === DEFAULT_ZOOM) { + root.style.removeProperty('zoom') + } else { + root.style.zoom = String(factor) + } +} + +export function ZoomProvider({ children }: { children: ReactNode }) { + const [zoom, setZoomState] = useState(() => + normalizeZoom(storage.get(storage.KEYS.zoomLevel, DEFAULT_ZOOM)), + ) + + // Keep the DOM in sync (also covers the initial value on mount). + useEffect(() => { + applyZoom(zoom) + }, [zoom]) + + const setZoom = useCallback((factor: number) => { + const next = normalizeZoom(factor) + setZoomState(next) + storage.set(storage.KEYS.zoomLevel, next) + applyZoom(next) + }, []) + + const zoomIn = useCallback(() => { + setZoomState((current) => { + const idx = ZOOM_STEPS.indexOf(normalizeZoom(current)) + const next = ZOOM_STEPS[Math.min(idx + 1, ZOOM_STEPS.length - 1)] + storage.set(storage.KEYS.zoomLevel, next) + applyZoom(next) + return next + }) + }, []) + + const zoomOut = useCallback(() => { + setZoomState((current) => { + const idx = ZOOM_STEPS.indexOf(normalizeZoom(current)) + const next = ZOOM_STEPS[Math.max(idx - 1, 0)] + storage.set(storage.KEYS.zoomLevel, next) + applyZoom(next) + return next + }) + }, []) + + const resetZoom = useCallback(() => setZoom(DEFAULT_ZOOM), [setZoom]) + + const value = useMemo(() => { + const idx = ZOOM_STEPS.indexOf(zoom) + return { + zoom, + zoomPercent: Math.round(zoom * 100), + zoomIn, + zoomOut, + resetZoom, + canZoomIn: idx < ZOOM_STEPS.length - 1, + canZoomOut: idx > 0, + } + }, [zoom, zoomIn, zoomOut, resetZoom]) + + return {children} +} + +export function useZoom(): ZoomContextType { + const ctx = useContext(ZoomContext) + if (!ctx) { + throw new Error('useZoom must be used within a ZoomProvider') + } + return ctx +} diff --git a/apps/electron/src/renderer/lib/local-storage.ts b/apps/electron/src/renderer/lib/local-storage.ts index fc688771e..f885915eb 100644 --- a/apps/electron/src/renderer/lib/local-storage.ts +++ b/apps/electron/src/renderer/lib/local-storage.ts @@ -65,6 +65,7 @@ export const KEYS = { conversationWidth: 'conversation-width', // Chat reading-column width: comfortable | wide | full chatTextSize: 'chat-text-size', // Scale conversation text without resizing the app chrome highContrast: 'high-contrast', // Raise contrast of borders, dividers, and muted text app-wide + zoomLevel: 'zoom-level', // Interface zoom factor (⌘+/⌘-/⌘0), scales the whole renderer // 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 f3a56c5c2..aaba97ce8 100644 --- a/apps/electron/src/renderer/main.tsx +++ b/apps/electron/src/renderer/main.tsx @@ -10,6 +10,7 @@ import { ReduceMotionProvider } from './context/ReduceMotionContext' import { ConversationWidthProvider } from './context/ConversationWidthContext' import { ChatTextSizeProvider } from './context/ChatTextSizeContext' import { HighContrastProvider } from './context/HighContrastContext' +import { ZoomProvider } from './context/ZoomContext' import { windowWorkspaceIdAtom } from './atoms/sessions' import { Toaster } from '@/components/ui/sonner' import { PetWindowController } from '@/components/pet/PetWindowController' @@ -114,9 +115,11 @@ function Root() { - - - + + + + + diff --git a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx index 83df1c6fd..1a365f2ac 100644 --- a/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx +++ b/apps/electron/src/renderer/pages/settings/AppearanceSettingsPage.tsx @@ -18,9 +18,11 @@ import { useReduceMotion } from '@/context/ReduceMotionContext' import { useConversationWidth } from '@/context/ConversationWidthContext' import { useChatTextSize, type ChatTextSize } from '@/context/ChatTextSizeContext' import { useHighContrast } from '@/context/HighContrastContext' +import { useZoom } from '@/context/ZoomContext' +import { Button } from '@/components/ui/button' import { useAppShellContext } from '@/context/AppShellContext' import { routes } from '@/lib/navigate' -import { FolderOpen, Monitor, RefreshCw, Sun, Moon } from 'lucide-react' +import { FolderOpen, Monitor, RefreshCw, Sun, Moon, Minus, Plus, RotateCcw } from 'lucide-react' import type { DetailsPageMeta } from '@/lib/navigation-registry' import type { ToolIconMapping } from '../../../shared/types' @@ -145,6 +147,7 @@ export default function AppearanceSettingsPage() { // Reduce motion toggle (renderer-only preference, persisted in localStorage) const { reduceMotion, setReduceMotion } = useReduceMotion() + const { zoomPercent, zoomIn, zoomOut, resetZoom, canZoomIn, canZoomOut } = useZoom() // Conversation width (renderer-only preference, persisted in localStorage) const { conversationWidth, setConversationWidth } = useConversationWidth() @@ -434,6 +437,58 @@ export default function AppearanceSettingsPage() { onCheckedChange={setHighContrast} testId="high-contrast-toggle" /> + +
+ + + {zoomPercent}% + + + +
+
diff --git a/docs/loop/feature-ledger.md b/docs/loop/feature-ledger.md index 9e86ed81a..d9d86cc61 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 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| interface-zoom | Interface zoom (⌘+/⌘-/⌘0) to scale the whole app | Claude Desktop / VS Code / Codex desktop View→Zoom In/Out/Actual Size | frontend-only | pr-open | [#68](https://github.com/modelstudioai/openwork/issues/68) | [#69](https://github.com/modelstudioai/openwork/pull/69) | loop/interface-zoom | 2026-07-08 | New `ZoomProvider` (mirrors `ReduceMotionProvider`) scales whole renderer via `document.documentElement.style.zoom`, discrete 50–200% steps, persisted in localStorage (`craft-zoom-level`). 3 View actions `view.zoomIn/Out/Reset` (`mod+=`/`mod+-`/`mod+0`) → auto in Command Palette + Shortcuts ref, wired via `ZoomHotkeys` bridge. Stepper in Appearance→Interface. 5 i18n keys ×7 locales. typecheck/`bun test` zero-delta vs main (56-failure set byte-identical); renderer build ✅; lint 0 errors; i18n parity ✅ (1549 keys). CDP assertion included; **could not run locally** (org egress 403 on Electron binary download — same block as #51). Distinct from chat-text-size (#64)/conversation-width (#62): scales entire UI, not just chat text. | | high-contrast | "Increase contrast" accessibility setting in Appearance | macOS Accessibility "Increase contrast" / Windows contrast themes / VS Code High Contrast | frontend-only | pr-open | [#66](https://github.com/modelstudioai/openwork/issues/66) | [#67](https://github.com/modelstudioai/openwork/pull/67) | loop/high-contrast | 2026-07-07 | Companion to merged reduce-motion. Renderer-only pref (`craft-high-contrast` localStorage) → `HighContrastProvider` context → `data-high-contrast` on `` → CSS overrides of theme tokens (`--border`/`--input`/`--muted-foreground`/`--ring`) gated on `:root[data-high-contrast='true']` (higher specificity beats theme `:root` injection; works light+dark since tokens derive from `--foreground`). Toggle in Appearance→Interface below Reduce motion; 2 i18n keys ×7 locales. typecheck:all/`bun test` zero-delta vs main (11 electron / 56 test pre-existing failures byte-identical); i18n parity OK; renderer build ✅ (verified rule in bundled CSS). CDP assertion written (`high-contrast.assert.ts`); **not run locally** — egress 403 blocks Electron binary download (same env block as #51). | | 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). | diff --git a/e2e/assertions/zoom.assert.ts b/e2e/assertions/zoom.assert.ts new file mode 100644 index 000000000..9abed1b5e --- /dev/null +++ b/e2e/assertions/zoom.assert.ts @@ -0,0 +1,104 @@ +/** + * Feature assertion: the interface-zoom control in Settings → Appearance + * actually scales the whole renderer and persists the preference. + * + * Drives the real UI over CDP entirely in the draft/no-session state (no seeded + * conversation, no backend): opens Settings → Appearance → Interface, uses the + * Zoom stepper, and asserts the observable effects — the displayed percentage, + * the inline `zoom` style on (which is what scales the app), and the + * persisted localStorage value. Zooming in then resetting 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 ZOOM_CONTROL = '[data-testid="zoom-control"]'; +const ZOOM_IN = '[data-testid="zoom-in"]'; +const ZOOM_RESET = '[data-testid="zoom-reset"]'; +const ZOOM_VALUE = '[data-testid="zoom-value"]'; +const STORAGE_KEY = 'craft-zoom-level'; + +/** The stepper's displayed percentage text (e.g. "100%"). */ +function valueTextExpr(): string { + return `(() => { + const el = document.querySelector(${JSON.stringify(ZOOM_VALUE)}); + return el ? el.textContent : null; + })()`; +} + +/** The inline zoom factor applied to ("" when unset). */ +function htmlZoomExpr(): string { + return `document.documentElement.style.zoom`; +} + +/** The persisted localStorage value for the preference. */ +function storedValueExpr(): string { + return `window.localStorage.getItem(${JSON.stringify(STORAGE_KEY)})`; +} + +const assertion: Assertion = { + name: 'interface zoom scales and persists the 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 zoom control is the feature under test — its presence is the first signal. + await session.waitForSelector(ZOOM_CONTROL, { + timeoutMs: 15000, + message: 'zoom control did not render', + }); + + // Initial state: 100%, no inline zoom on . + const initialText = await session.evaluate(valueTextExpr()); + if (initialText !== '100%') { + throw new Error(`expected "100%" initially, saw ${JSON.stringify(initialText)}`); + } + const initialZoom = await session.evaluate(htmlZoomExpr()); + if (initialZoom !== '') { + throw new Error(`expected no inline zoom on initially, saw ${JSON.stringify(initialZoom)}`); + } + + // Zoom in → percentage rises, scaled, persisted. + await session.click(ZOOM_IN); + await session.waitForFunction( + `${valueTextExpr()} === '110%'`, + { timeoutMs: 5000, message: 'zoom-in did not raise the displayed percentage to 110%' }, + ); + await session.waitForFunction( + `${htmlZoomExpr()} === '1.1'`, + { timeoutMs: 5000, message: 'zoom-in did not apply zoom:1.1 to ' }, + ); + const storedOn = await session.evaluate(storedValueExpr()); + if (storedOn !== '1.1') { + throw new Error(`expected persisted "1.1" after zoom-in, saw ${JSON.stringify(storedOn)}`); + } + + // Reset → back to 100%, inline zoom cleared, persisted default restored. + await session.click(ZOOM_RESET); + await session.waitForFunction( + `${valueTextExpr()} === '100%'`, + { timeoutMs: 5000, message: 'reset did not return the displayed percentage to 100%' }, + ); + await session.waitForFunction( + `${htmlZoomExpr()} === ''`, + { timeoutMs: 5000, message: 'reset did not clear the inline zoom on ' }, + ); + const storedOff = await session.evaluate(storedValueExpr()); + if (storedOff !== '1') { + throw new Error(`expected persisted "1" after reset, saw ${JSON.stringify(storedOff)}`); + } + }, +}; + +export default assertion; diff --git a/packages/shared/src/i18n/locales/de.json b/packages/shared/src/i18n/locales/de.json index 67e4ebea5..17c5a3667 100644 --- a/packages/shared/src/i18n/locales/de.json +++ b/packages/shared/src/i18n/locales/de.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "Breit", "settings.appearance.increaseContrast": "Kontrast erhöhen", "settings.appearance.increaseContrastDesc": "Ränder, Trennlinien und sekundären Text für bessere Lesbarkeit verstärken.", + "settings.appearance.zoom": "Zoom", + "settings.appearance.zoomDesc": "Die gesamte Benutzeroberfläche vergrößern oder verkleinern.", "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.", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "Verarbeitung stoppen", "shortcuts.action.toggleFocusMode": "Fokusmodus umschalten", "shortcuts.action.toggleSidebar": "Seitenleiste umschalten", + "shortcuts.action.zoomIn": "Vergrößern", + "shortcuts.action.zoomOut": "Verkleinern", + "shortcuts.action.zoomReset": "Zoom zurücksetzen", "shortcuts.action.toggleTheme": "Design umschalten", "shortcuts.addFilterExcluded": "Filter als ausgeschlossen hinzufügen", "shortcuts.agentTree": "Agentenbaum", diff --git a/packages/shared/src/i18n/locales/en.json b/packages/shared/src/i18n/locales/en.json index 1d8ebc619..9387fb9f6 100644 --- a/packages/shared/src/i18n/locales/en.json +++ b/packages/shared/src/i18n/locales/en.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "Wide", "settings.appearance.increaseContrast": "Increase contrast", "settings.appearance.increaseContrastDesc": "Strengthen borders, dividers, and secondary text for better readability.", + "settings.appearance.zoom": "Zoom", + "settings.appearance.zoomDesc": "Scale the entire interface up or down.", "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.", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "Stop Processing", "shortcuts.action.toggleFocusMode": "Toggle Focus Mode", "shortcuts.action.toggleSidebar": "Toggle Sidebar", + "shortcuts.action.zoomIn": "Zoom In", + "shortcuts.action.zoomOut": "Zoom Out", + "shortcuts.action.zoomReset": "Reset Zoom", "shortcuts.action.toggleTheme": "Toggle Theme", "shortcuts.addFilterExcluded": "Add filter as excluded", "shortcuts.agentTree": "Agent Tree", diff --git a/packages/shared/src/i18n/locales/es.json b/packages/shared/src/i18n/locales/es.json index 17f2b9353..d7e5bbef6 100644 --- a/packages/shared/src/i18n/locales/es.json +++ b/packages/shared/src/i18n/locales/es.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "Ancho", "settings.appearance.increaseContrast": "Aumentar contraste", "settings.appearance.increaseContrastDesc": "Refuerza los bordes, separadores y el texto secundario para mejorar la legibilidad.", + "settings.appearance.zoom": "Zoom", + "settings.appearance.zoomDesc": "Escala toda la interfaz para hacerla más grande o más pequeña.", "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.", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "Detener procesamiento", "shortcuts.action.toggleFocusMode": "Alternar modo enfoque", "shortcuts.action.toggleSidebar": "Alternar barra lateral", + "shortcuts.action.zoomIn": "Acercar", + "shortcuts.action.zoomOut": "Alejar", + "shortcuts.action.zoomReset": "Restablecer zoom", "shortcuts.action.toggleTheme": "Cambiar tema", "shortcuts.addFilterExcluded": "Agregar filtro como excluido", "shortcuts.agentTree": "Árbol de agentes", diff --git a/packages/shared/src/i18n/locales/hu.json b/packages/shared/src/i18n/locales/hu.json index fac186a4f..6fc2c48f8 100644 --- a/packages/shared/src/i18n/locales/hu.json +++ b/packages/shared/src/i18n/locales/hu.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "Széles", "settings.appearance.increaseContrast": "Kontraszt növelése", "settings.appearance.increaseContrastDesc": "A szegélyek, elválasztók és másodlagos szöveg erősítése a jobb olvashatóságért.", + "settings.appearance.zoom": "Nagyítás", + "settings.appearance.zoomDesc": "A teljes felület nagyítása vagy kicsinyítése.", "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.", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "Feldolgozás leállítása", "shortcuts.action.toggleFocusMode": "Fókusz mód be/ki", "shortcuts.action.toggleSidebar": "Oldalsáv be/ki", + "shortcuts.action.zoomIn": "Nagyítás", + "shortcuts.action.zoomOut": "Kicsinyítés", + "shortcuts.action.zoomReset": "Nagyítás visszaállítása", "shortcuts.action.toggleTheme": "Téma váltása", "shortcuts.addFilterExcluded": "Szűrő hozzáadása kizártként", "shortcuts.agentTree": "Ügynökfa", diff --git a/packages/shared/src/i18n/locales/ja.json b/packages/shared/src/i18n/locales/ja.json index 7953f270a..efd83d76f 100644 --- a/packages/shared/src/i18n/locales/ja.json +++ b/packages/shared/src/i18n/locales/ja.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "ワイド", "settings.appearance.increaseContrast": "コントラストを上げる", "settings.appearance.increaseContrastDesc": "境界線・区切り線・補助テキストを強調して読みやすくします。", + "settings.appearance.zoom": "ズーム", + "settings.appearance.zoomDesc": "インターフェース全体を拡大または縮小します。", "settings.appearance.pet": "ペット", "settings.appearance.petCustom": "カスタムペット", "settings.appearance.petCustomHint": "pet.json と 8x9 のスプライトシートを含むフォルダーを追加してから更新します。", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "処理を停止", "shortcuts.action.toggleFocusMode": "フォーカスモードの切り替え", "shortcuts.action.toggleSidebar": "サイドバーの切り替え", + "shortcuts.action.zoomIn": "拡大", + "shortcuts.action.zoomOut": "縮小", + "shortcuts.action.zoomReset": "ズームをリセット", "shortcuts.action.toggleTheme": "テーマの切り替え", "shortcuts.addFilterExcluded": "除外フィルターとして追加", "shortcuts.agentTree": "エージェントツリー", diff --git a/packages/shared/src/i18n/locales/pl.json b/packages/shared/src/i18n/locales/pl.json index b9190fe9c..d792904ad 100644 --- a/packages/shared/src/i18n/locales/pl.json +++ b/packages/shared/src/i18n/locales/pl.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "Szeroka", "settings.appearance.increaseContrast": "Zwiększ kontrast", "settings.appearance.increaseContrastDesc": "Wzmocnij obramowania, separatory i tekst pomocniczy dla lepszej czytelności.", + "settings.appearance.zoom": "Powiększenie", + "settings.appearance.zoomDesc": "Skaluj cały interfejs w górę lub w dół.", "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ż.", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "Zatrzymaj przetwarzanie", "shortcuts.action.toggleFocusMode": "Przełącz tryb skupienia", "shortcuts.action.toggleSidebar": "Przełącz pasek boczny", + "shortcuts.action.zoomIn": "Powiększ", + "shortcuts.action.zoomOut": "Pomniejsz", + "shortcuts.action.zoomReset": "Resetuj powiększenie", "shortcuts.action.toggleTheme": "Przełącz motyw", "shortcuts.addFilterExcluded": "Dodaj filtr jako wykluczony", "shortcuts.agentTree": "Drzewo agentów", diff --git a/packages/shared/src/i18n/locales/zh-Hans.json b/packages/shared/src/i18n/locales/zh-Hans.json index b9b55f30f..127b6c2c8 100644 --- a/packages/shared/src/i18n/locales/zh-Hans.json +++ b/packages/shared/src/i18n/locales/zh-Hans.json @@ -798,6 +798,8 @@ "settings.appearance.conversationWidthWide": "宽", "settings.appearance.increaseContrast": "提高对比度", "settings.appearance.increaseContrastDesc": "增强边框、分隔线和次要文字,提升可读性。", + "settings.appearance.zoom": "缩放", + "settings.appearance.zoomDesc": "放大或缩小整个界面。", "settings.appearance.pet": "宠物", "settings.appearance.petCustom": "自定义宠物", "settings.appearance.petCustomHint": "每只宠物一个文件夹,包含 pet.json 和 8x9 spritesheet,放好后刷新。", @@ -1198,6 +1200,9 @@ "shortcuts.action.stopProcessing": "停止处理", "shortcuts.action.toggleFocusMode": "切换专注模式", "shortcuts.action.toggleSidebar": "切换侧栏", + "shortcuts.action.zoomIn": "放大", + "shortcuts.action.zoomOut": "缩小", + "shortcuts.action.zoomReset": "重置缩放", "shortcuts.action.toggleTheme": "切换主题", "shortcuts.addFilterExcluded": "添加为排除筛选", "shortcuts.agentTree": "智能体树",