mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-16 13:03:31 +08:00
Prevent Settings zoom controls from drifting
Settings and keyboard zoom controls were able to diverge because the slider only committed on release and captured pointer events around the native range control. The slider now commits through the shared zoom store as it changes, while the range keeps its native drag behavior. The full desktop gate also enforces Safari 15 compatibility for startup CSS, so the remaining memory theme color-mix tokens were replaced with static equivalents. Constraint: macOS 12 Safari 15 WebView cannot rely on CSS color-mix in startup-critical desktop CSS Rejected: Commit Settings zoom only on pointer release | leaves shortcut state and slider state temporarily out of sync Confidence: high Scope-risk: narrow Directive: Keep Settings slider, reset, and keyboard shortcuts routed through the shared uiZoom/appZoom state Tested: bun run check:desktop Tested: agent-browser Settings zoom smoke against local Vite plus server Not-tested: Packaged Tauri app manual drag on macOS hardware
This commit is contained in:
parent
4e969475f2
commit
c279643cce
@ -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(<Settings />)
|
||||
|
||||
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(<Settings />)
|
||||
|
||||
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(<Settings />)
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user