diff --git a/docs/architecture/workspace-shell.md b/docs/architecture/workspace-shell.md index aa8cbaee6..35565be2d 100644 --- a/docs/architecture/workspace-shell.md +++ b/docs/architecture/workspace-shell.md @@ -473,7 +473,10 @@ Zoom level (Cmd/Ctrl and +/−/0, issue #1109): (`stepZoomLevel`): 0.5 per press, Electron's own `zoomIn`/`zoomOut` role step (≈10 %), clamped to levels −4…6 (≈48 %…299 %, inside Chromium's 25–500 %), off-grid levels snapping to the next grid point in the pressed - direction; `Ctrl/Cmd+0` returns to level 0. Persistence needs nothing + direction; `Ctrl/Cmd+0` returns to level 0. A stored level already + outside the limits (the macOS menu roles never clamped) is never moved + against the request: a press further out leaves it, a press back in + lands on the limit. Persistence needs nothing extra: the main process reads the live level back (point 3). On macOS the default application menu still carries the `zoomIn`/`zoomOut`/`resetZoom` roles, but Chromium hands a key equivalent to the web contents first and diff --git a/libs/shared/interfaces/src/lib/zoom-level.util.spec.ts b/libs/shared/interfaces/src/lib/zoom-level.util.spec.ts index a565d4861..c0ab17123 100644 --- a/libs/shared/interfaces/src/lib/zoom-level.util.spec.ts +++ b/libs/shared/interfaces/src/lib/zoom-level.util.spec.ts @@ -23,13 +23,25 @@ describe('zoom level shortcuts contract', () => { it('clamps at the app limits, not at Chromium limits', () => { expect(stepZoomLevel(ZOOM_LEVEL_MAX, 'in')).toBe(ZOOM_LEVEL_MAX); expect(stepZoomLevel(ZOOM_LEVEL_MIN, 'out')).toBe(ZOOM_LEVEL_MIN); - expect(stepZoomLevel(ZOOM_LEVEL_MAX + 3, 'in')).toBe(ZOOM_LEVEL_MAX); - expect(stepZoomLevel(ZOOM_LEVEL_MAX + 3, 'out')).toBe( - ZOOM_LEVEL_MAX - ZOOM_LEVEL_STEP - ); + expect(stepZoomLevel(ZOOM_LEVEL_MAX - 0.5, 'in')).toBe(ZOOM_LEVEL_MAX); expect(clampZoomLevel(ZOOM_LEVEL_MIN - 1)).toBe(ZOOM_LEVEL_MIN); }); + it('never moves a stored out-of-range level against the request', () => { + // The macOS menu roles never clamped and the store restores any + // finite level, so an upgrade can start beyond the limits. + expect(stepZoomLevel(ZOOM_LEVEL_MAX + 1, 'in')).toBe( + ZOOM_LEVEL_MAX + 1 + ); + expect(stepZoomLevel(ZOOM_LEVEL_MAX + 1, 'out')).toBe(ZOOM_LEVEL_MAX); + expect(stepZoomLevel(ZOOM_LEVEL_MAX + 0.3, 'out')).toBe(ZOOM_LEVEL_MAX); + expect(stepZoomLevel(ZOOM_LEVEL_MIN - 1, 'out')).toBe( + ZOOM_LEVEL_MIN - 1 + ); + expect(stepZoomLevel(ZOOM_LEVEL_MIN - 1, 'in')).toBe(ZOOM_LEVEL_MIN); + expect(stepZoomLevel(ZOOM_LEVEL_MIN - 1, 'reset')).toBe(0); + }); + it('snaps an off-grid level to the next grid point in the requested direction', () => { // A level the macOS menu roles or an older store left between steps. expect(stepZoomLevel(0.3, 'in')).toBe(0.5); diff --git a/libs/shared/interfaces/src/lib/zoom-level.util.ts b/libs/shared/interfaces/src/lib/zoom-level.util.ts index 6846e86e8..afde846ae 100644 --- a/libs/shared/interfaces/src/lib/zoom-level.util.ts +++ b/libs/shared/interfaces/src/lib/zoom-level.util.ts @@ -33,7 +33,10 @@ export function clampZoomLevel(level: number): number { * The level `action` produces from `current`. Steps are snapped to the step * grid so a level restored from an older store (or set by the macOS menu * before the shortcuts existed) does not carry a fractional offset forever. - * A non-finite `current` is treated as the default. + * A level already outside the limits — the macOS menu roles never clamped, + * and the store restores any finite level — is never moved AGAINST the + * request: a press further out leaves it where it is, a press back in lands + * on the limit. A non-finite `current` is treated as the default. */ export function stepZoomLevel( current: number, @@ -43,8 +46,13 @@ export function stepZoomLevel( return ZOOM_LEVEL_DEFAULT; } - const base = clampZoomLevel(current); + const base = Number.isFinite(current) ? current : ZOOM_LEVEL_DEFAULT; const direction = action === 'in' ? 1 : -1; + + if (direction > 0 ? base >= ZOOM_LEVEL_MAX : base <= ZOOM_LEVEL_MIN) { + return base; + } + const steps = base / ZOOM_LEVEL_STEP; // A level between grid points steps to the next grid point in the // requested direction instead of skipping past it. @@ -53,7 +61,6 @@ export function stepZoomLevel( : direction > 0 ? Math.ceil(steps) : Math.floor(steps); - const next = nextSteps * ZOOM_LEVEL_STEP; - return clampZoomLevel(next); + return clampZoomLevel(nextSteps * ZOOM_LEVEL_STEP); }