Merge zoom slider drag stability fix into local main

The Settings zoom slider follow-up stops app-wide zoom from being applied continuously while the pointer is dragging, then commits the chosen value on release. This keeps the local main checkout aligned with the verified worktree fix.

Constraint: The slider was resizing the UI under the pointer when live zoom applied on every drag event
Confidence: high
Scope-risk: narrow
Directive: Keep pointer-drag zoom as local preview plus release commit; keyboard zoom remains live through the shared app zoom state
Tested: bun run check:desktop
Tested: agent-browser Settings smoke confirms drag preview keeps cssZoom at 1 until release, then commits 1.23
This commit is contained in:
程序员阿江(Relakkes) 2026-05-15 20:30:00 +08:00
commit 571f16135f
3 changed files with 29 additions and 13 deletions

View File

@ -268,7 +268,7 @@ describe('Settings > General tab', () => {
expect((uiZoomHeading.compareDocumentPosition(webFetchHeading) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0).toBe(true)
})
it('applies UI zoom while dragging so the slider and app zoom stay synchronized', async () => {
it('previews UI zoom while dragging and applies it once on release', async () => {
render(<Settings />)
fireEvent.click(screen.getByText('General'))
@ -278,6 +278,7 @@ describe('Settings > General tab', () => {
expect(screen.getByText('0 resets zoom to 100%.')).toBeInTheDocument()
const slider = screen.getByLabelText('UI Zoom')
expect(slider).toHaveAttribute('step', '0.01')
fireEvent.pointerDown(slider, { pointerId: 1 })
await act(async () => {
@ -287,8 +288,8 @@ describe('Settings > General tab', () => {
})
expect(screen.getAllByText('125%')).toHaveLength(2)
expect(useSettingsStore.getState().setUiZoom).toHaveBeenCalledWith(1.25)
expect(useSettingsStore.getState().uiZoom).toBe(1.25)
expect(useSettingsStore.getState().setUiZoom).not.toHaveBeenCalledWith(1.25)
expect(useSettingsStore.getState().uiZoom).toBe(1)
expect(slider).toHaveValue('1.25')
expect(slider).toHaveClass('settings-zoom-range')
expect(slider.closest('.settings-zoom-control')).toHaveClass('is-dragging')

View File

@ -4,7 +4,7 @@ export const DEFAULT_APP_ZOOM = 1
export const MIN_APP_ZOOM = 0.5
export const MAX_APP_ZOOM = 2
export const APP_ZOOM_STEP = 0.1
export const APP_ZOOM_CONTROL_STEP = 0.05
export const APP_ZOOM_CONTROL_STEP = 0.01
export type AppZoomAction = 'in' | 'out' | 'reset'

View File

@ -1394,6 +1394,7 @@ function GeneralSettings() {
const [notificationActionRunning, setNotificationActionRunning] = useState(false)
const [uiZoomDraft, setUiZoomDraft] = useState(uiZoom)
const [isUiZoomDragging, setIsUiZoomDragging] = useState(false)
const isUiZoomDraggingRef = useRef(false)
const webSearchDirty = JSON.stringify(webSearchDraft) !== JSON.stringify(webSearch)
const uiZoomPercent = Math.round(uiZoomDraft * 100)
const uiZoomRangeProgress = `${Math.round(((uiZoomDraft - UI_ZOOM_MIN) / (UI_ZOOM_MAX - UI_ZOOM_MIN)) * 1000) / 10}%`
@ -1523,11 +1524,14 @@ function GeneralSettings() {
}
}
const commitUiZoom = (value: number, options: { keepDragging?: boolean } = {}) => {
const setUiZoomDraggingState = (dragging: boolean) => {
isUiZoomDraggingRef.current = dragging
setIsUiZoomDragging(dragging)
}
const commitUiZoom = (value: number) => {
const nextZoom = Number.isFinite(value) ? value : UI_ZOOM_DEFAULT
if (!options.keepDragging) {
setIsUiZoomDragging(false)
}
setUiZoomDraggingState(false)
setUiZoomDraft(nextZoom)
setUiZoom(nextZoom)
}
@ -1602,16 +1606,27 @@ function GeneralSettings() {
step={UI_ZOOM_STEP}
value={uiZoomDraft}
onPointerDown={() => {
setIsUiZoomDragging(true)
setUiZoomDraggingState(true)
}}
onPointerUp={(e) => commitUiZoom(e.currentTarget.valueAsNumber)}
onPointerCancel={() => {
setUiZoomDraggingState(false)
setUiZoomDraft(uiZoom)
}}
onChange={(e) => {
const nextZoom = Number.isFinite(e.currentTarget.valueAsNumber)
? e.currentTarget.valueAsNumber
: UI_ZOOM_DEFAULT
setUiZoomDraft(nextZoom)
if (!isUiZoomDraggingRef.current) {
setUiZoom(nextZoom)
}
}}
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)
setUiZoomDraggingState(false)
}
}}
className="settings-zoom-range w-full"