diff --git a/desktop/src/__tests__/generalSettings.test.tsx b/desktop/src/__tests__/generalSettings.test.tsx index dc6e0018..3e36eafb 100644 --- a/desktop/src/__tests__/generalSettings.test.tsx +++ b/desktop/src/__tests__/generalSettings.test.tsx @@ -268,7 +268,7 @@ describe('Settings > General tab', () => { expect((uiZoomHeading.compareDocumentPosition(webFetchHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true) }) - it('previews UI zoom while dragging and applies it only when the user releases the slider', async () => { + it('applies UI zoom while dragging so the slider and app zoom stay synchronized', async () => { render() fireEvent.click(screen.getByText('General')) @@ -287,7 +287,9 @@ describe('Settings > General tab', () => { }) expect(screen.getAllByText('125%')).toHaveLength(2) - expect(useSettingsStore.getState().setUiZoom).not.toHaveBeenCalledWith(1.25) + expect(useSettingsStore.getState().setUiZoom).toHaveBeenCalledWith(1.25) + expect(useSettingsStore.getState().uiZoom).toBe(1.25) + expect(slider).toHaveValue('1.25') expect(slider).toHaveClass('settings-zoom-range') expect(slider.closest('.settings-zoom-control')).toHaveClass('is-dragging') expect(slider.closest('.settings-zoom-control')).toHaveStyle({ '--settings-zoom-range-progress': '50%' }) @@ -297,6 +299,7 @@ describe('Settings > General tab', () => { }) expect(useSettingsStore.getState().setUiZoom).toHaveBeenCalledWith(1.25) + expect(slider.closest('.settings-zoom-control')).not.toHaveClass('is-dragging') await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'Reset UI zoom to 100%' })) @@ -305,6 +308,22 @@ describe('Settings > General tab', () => { expect(useSettingsStore.getState().setUiZoom).toHaveBeenLastCalledWith(1) }) + it('updates the UI zoom slider when shortcut zoom changes the shared setting while Settings is open', async () => { + render() + + fireEvent.click(screen.getByText('General')) + + const slider = screen.getByLabelText('UI Zoom') + + await act(async () => { + useSettingsStore.setState({ uiZoom: 1.1 }) + }) + + expect(slider).toHaveValue('1.1') + expect(screen.getAllByText('110%')).toHaveLength(2) + expect(slider.closest('.settings-zoom-control')).toHaveStyle({ '--settings-zoom-range-progress': '40%' }) + }) + it('opens the Token usage tab from Settings navigation above Diagnostics', () => { render() diff --git a/desktop/src/pages/Settings.tsx b/desktop/src/pages/Settings.tsx index fc34c1ae..01d07690 100644 --- a/desktop/src/pages/Settings.tsx +++ b/desktop/src/pages/Settings.tsx @@ -1396,7 +1396,7 @@ function GeneralSettings() { const [isUiZoomDragging, setIsUiZoomDragging] = useState(false) const webSearchDirty = JSON.stringify(webSearchDraft) !== JSON.stringify(webSearch) const uiZoomPercent = Math.round(uiZoomDraft * 100) - const uiZoomRangeProgress = `${((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 100}%` + const uiZoomRangeProgress = `${Math.round(((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 1000) / 10}%` useEffect(() => { setWebSearchDraft(webSearch) @@ -1523,9 +1523,11 @@ function GeneralSettings() { } } - const commitUiZoom = (value: number) => { + const commitUiZoom = (value: number, options: { keepDragging?: boolean } = {}) => { const nextZoom = Number.isFinite(value) ? value : UI_ZOOM_DEFAULT - setIsUiZoomDragging(false) + if (!options.keepDragging) { + setIsUiZoomDragging(false) + } setUiZoomDraft(nextZoom) setUiZoom(nextZoom) } @@ -1599,21 +1601,17 @@ function GeneralSettings() { max={UI_ZOOM_MAX} step={UI_ZOOM_STEP} value={uiZoomDraft} - onPointerDown={(e) => { + onPointerDown={() => { setIsUiZoomDragging(true) - e.currentTarget.setPointerCapture?.(e.pointerId) - }} - onPointerUp={(e) => commitUiZoom(e.currentTarget.valueAsNumber)} - onPointerCancel={(e) => commitUiZoom(e.currentTarget.valueAsNumber)} - onChange={(e) => setUiZoomDraft(e.currentTarget.valueAsNumber)} - onKeyUp={(e) => { - if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'].includes(e.key)) { - commitUiZoom(e.currentTarget.valueAsNumber) - } }} + onPointerUp={() => setIsUiZoomDragging(false)} + onPointerCancel={() => setIsUiZoomDragging(false)} + onChange={(e) => commitUiZoom(e.currentTarget.valueAsNumber, { keepDragging: isUiZoomDragging })} onBlur={(e) => { if (uiZoomDraft !== uiZoom) { commitUiZoom(e.currentTarget.valueAsNumber) + } else { + setIsUiZoomDragging(false) } }} className="settings-zoom-range w-full" diff --git a/desktop/src/theme/globals.css b/desktop/src/theme/globals.css index e51fb52e..1f787e85 100644 --- a/desktop/src/theme/globals.css +++ b/desktop/src/theme/globals.css @@ -199,6 +199,7 @@ cursor: pointer; background: transparent; accent-color: var(--color-brand); + touch-action: none; } .settings-zoom-range:focus { @@ -366,9 +367,9 @@ --color-surface-hover: var(--color-surface-container-high); --color-surface-selected: var(--color-surface-container); --color-memory-accent: #3B7068; - --color-memory-surface: color-mix(in srgb, var(--color-memory-accent) 7%, var(--color-surface-container-lowest)); - --color-memory-border: color-mix(in srgb, var(--color-memory-accent) 26%, var(--color-border)); - --color-memory-icon-bg: color-mix(in srgb, var(--color-memory-accent) 10%, var(--color-surface-container-lowest)); + --color-memory-surface: #F1F5F4; + --color-memory-border: #B1ACA5; + --color-memory-icon-bg: #EBF1F0; --color-model-option-selected-bg: var(--color-primary-fixed); --color-model-option-selected-border: rgba(143, 72, 47, 0.2); --color-activity-heat-0: var(--color-surface-container); @@ -567,9 +568,9 @@ --color-surface-hover: #F2F5F8; --color-surface-selected: #E9EEF3; --color-memory-accent: #0F766E; - --color-memory-surface: color-mix(in srgb, var(--color-memory-accent) 5%, #FFFFFF); - --color-memory-border: color-mix(in srgb, var(--color-memory-accent) 24%, var(--color-border)); - --color-memory-icon-bg: color-mix(in srgb, var(--color-memory-accent) 8%, #FFFFFF); + --color-memory-surface: #F3F8F8; + --color-memory-border: #ACC9CC; + --color-memory-icon-bg: #ECF4F3; --color-model-option-selected-bg: #FFF0EA; --color-model-option-selected-border: rgba(143, 72, 47, 0.2); --color-activity-heat-0: #EEF2F6; @@ -745,9 +746,9 @@ --color-surface-hover: var(--color-surface-container-highest); --color-surface-selected: var(--color-surface-container); --color-memory-accent: #7DD3C7; - --color-memory-surface: color-mix(in srgb, var(--color-memory-accent) 9%, var(--color-surface-container-lowest)); - --color-memory-border: color-mix(in srgb, var(--color-memory-accent) 26%, var(--color-border)); - --color-memory-icon-bg: color-mix(in srgb, var(--color-memory-accent) 12%, var(--color-surface-container-lowest)); + --color-memory-surface: #18201F; + --color-memory-border: rgba(113, 162, 151, 0.39); + --color-memory-icon-bg: #1B2624; --color-model-option-selected-bg: rgba(255, 181, 159, 0.13); --color-model-option-selected-border: rgba(255, 181, 159, 0.34); --color-activity-heat-0: #242322;