Skip to content

feat(code): add Visual Studio Code style UI zoom#2515

Open
daniektj wants to merge 1 commit into
PostHog:mainfrom
daniektj:posthog-code/ui-zoom
Open

feat(code): add Visual Studio Code style UI zoom#2515
daniektj wants to merge 1 commit into
PostHog:mainfrom
daniektj:posthog-code/ui-zoom

Conversation

@daniektj

@daniektj daniektj commented Jun 7, 2026

Copy link
Copy Markdown

Adds window-level zoom like VS Code: Ctrl/⌘ +, -, and 0, the View menu (Zoom In/Out/Reset), and a control in Settings → Appearance with a live percentage.

The zoom level is owned by a main-process ZoomService, persists across restarts (stored alongside window state), and is re-applied after web-contents reloads (zoom resets on every load). Keyboard handling lives in a pure, unit-tested resolveZoomIntent helper instead of react-hotkeys-hook, which mis-parses the =/-/0 symbol keys.

There is no OS-specific code, so the behavior is portable to macOS and Linux (the native window controls intentionally stay fixed, matching VS Code).

Testing

  • 26 unit tests: key-intent mapping, clamp/percent math, and ZoomService behavior (zoom in/out/reset, clamping, persistence, restore)
  • typecheck and biome pass clean
  • Manually verified in a packaged Windows build: keyboard shortcuts, View menu, Settings control with live %, and persistence across a restart

Created with PostHog Code

Add window-level zoom controllable via Ctrl/Cmd +, -, and 0, the View
menu (Zoom In/Out/Reset), and a control in Settings -> Appearance with a
live percentage. The level is owned by a main-process ZoomService,
persists across restarts (alongside window state), and is re-applied
after web-contents reloads. Key handling lives in a pure, unit-tested
resolveZoomIntent helper. No OS-specific code, so it is portable to
macOS and Linux.

Generated-By: PostHog Code
Task-Id: 99e347cb-9d2a-4225-8425-1fff9daab0ad
@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
packages/platform/src/main-window.ts:5-6
`getZoomLevel()` is never called by any consumer in this PR. `ZoomService` initialises its state from `persistence.getZoomLevel()` and tracks it in `currentLevel`, writing to the window via `setZoomLevel` only. The method is also mocked in the service tests but never asserted against. Under the "no superfluous parts" rule it (and its implementation in `ElectronMainWindow`) can be removed.

### Issue 2 of 2
apps/code/src/main/services/zoom/schemas.test.ts:9-51
The `clampZoomLevel` and `zoomLevelToPercent` tests bundle multiple related cases into single `it` blocks. The team rule is to prefer parameterised tests (`it.each`). Converting these to `it.each` tables would make it easier to add cases and immediately see which input caused a failure. The same pattern applies to `zoomKeybinding.test.ts` (e.g. the "maps zoom-in keys" and "ignores unrelated keys" blocks).

Reviews (1): Last reviewed commit: "feat(code): add VS Code-style UI zoom" | Re-trigger Greptile

Comment on lines 5 to 6
restore(): void;
onFocus(handler: () => void): () => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 getZoomLevel() is never called by any consumer in this PR. ZoomService initialises its state from persistence.getZoomLevel() and tracks it in currentLevel, writing to the window via setZoomLevel only. The method is also mocked in the service tests but never asserted against. Under the "no superfluous parts" rule it (and its implementation in ElectronMainWindow) can be removed.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/platform/src/main-window.ts
Line: 5-6

Comment:
`getZoomLevel()` is never called by any consumer in this PR. `ZoomService` initialises its state from `persistence.getZoomLevel()` and tracks it in `currentLevel`, writing to the window via `setZoomLevel` only. The method is also mocked in the service tests but never asserted against. Under the "no superfluous parts" rule it (and its implementation in `ElectronMainWindow`) can be removed.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +9 to +51
describe("clampZoomLevel", () => {
it("leaves in-range, on-step values untouched", () => {
expect(clampZoomLevel(0)).toBe(0);
expect(clampZoomLevel(0.5)).toBe(0.5);
expect(clampZoomLevel(-1.5)).toBe(-1.5);
});

it("snaps to the nearest 0.5 step", () => {
expect(clampZoomLevel(1.3)).toBe(1.5);
expect(clampZoomLevel(1.2)).toBe(1.0);
expect(clampZoomLevel(-1.3)).toBe(-1.5);
expect(clampZoomLevel(0.24)).toBe(0);
});

it("clamps above the maximum", () => {
expect(clampZoomLevel(10)).toBe(MAX_ZOOM_LEVEL);
expect(clampZoomLevel(MAX_ZOOM_LEVEL + 0.5)).toBe(MAX_ZOOM_LEVEL);
});

it("clamps below the minimum", () => {
expect(clampZoomLevel(-10)).toBe(MIN_ZOOM_LEVEL);
expect(clampZoomLevel(MIN_ZOOM_LEVEL - 0.5)).toBe(MIN_ZOOM_LEVEL);
});

it("handles boundary values exactly", () => {
expect(clampZoomLevel(MAX_ZOOM_LEVEL)).toBe(MAX_ZOOM_LEVEL);
expect(clampZoomLevel(MIN_ZOOM_LEVEL)).toBe(MIN_ZOOM_LEVEL);
});
});

describe("zoomLevelToPercent", () => {
it("returns 100% at level 0", () => {
expect(zoomLevelToPercent(0)).toBe(100);
});

it("computes the 1.2^level factor as a rounded percentage", () => {
expect(zoomLevelToPercent(1)).toBe(120);
expect(zoomLevelToPercent(2)).toBe(144);
expect(zoomLevelToPercent(0.5)).toBe(110);
expect(zoomLevelToPercent(-1)).toBe(83);
expect(zoomLevelToPercent(MAX_ZOOM_LEVEL)).toBe(249);
expect(zoomLevelToPercent(MIN_ZOOM_LEVEL)).toBe(58);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The clampZoomLevel and zoomLevelToPercent tests bundle multiple related cases into single it blocks. The team rule is to prefer parameterised tests (it.each). Converting these to it.each tables would make it easier to add cases and immediately see which input caused a failure. The same pattern applies to zoomKeybinding.test.ts (e.g. the "maps zoom-in keys" and "ignores unrelated keys" blocks).

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/main/services/zoom/schemas.test.ts
Line: 9-51

Comment:
The `clampZoomLevel` and `zoomLevelToPercent` tests bundle multiple related cases into single `it` blocks. The team rule is to prefer parameterised tests (`it.each`). Converting these to `it.each` tables would make it easier to add cases and immediately see which input caused a failure. The same pattern applies to `zoomKeybinding.test.ts` (e.g. the "maps zoom-in keys" and "ignores unrelated keys" blocks).

**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@charlesvien

Copy link
Copy Markdown
Member

hey @daniektj, I can already do Ctrl/⌘ +, -, and 0, the View menu (Zoom In/Out/Reset) from the native electron renderer? I'm confused on if this is solving a specific issue you were having?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants