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;