Scale typography without resizing layout

Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
This commit is contained in:
Princess Donut
2026-08-12 13:58:50 +01:00
parent c81bdce7a4
commit 1a9fa4f299
12 changed files with 165 additions and 122 deletions
@@ -52,7 +52,7 @@
}
.buzz-diff-theme .diff {
font-size: 0.75rem;
font-size: calc(var(--buzz-type-rem) * 0.75);
}
.buzz-diff-theme .diff td {
@@ -68,7 +68,7 @@
padding: 0.125rem 0.5rem;
border-right: 1px solid hsl(var(--border) / 0.65);
color: hsl(var(--muted-foreground));
font-size: 0.6875rem;
font-size: calc(var(--buzz-type-rem) * 0.6875);
}
.buzz-diff-theme .buzz-diff-code {
@@ -86,7 +86,7 @@
padding: 0.2rem 0.75rem;
background: hsl(var(--muted) / 0.35);
color: hsl(var(--muted-foreground));
font-size: 0.6875rem;
font-size: calc(var(--buzz-type-rem) * 0.6875);
}
.buzz-diff-theme .diff-gutter-omit::before {
@@ -3,7 +3,10 @@ import test from "node:test";
const values = new Map();
const attributes = new Map();
const style = { fontSize: "" };
const styleValues = new Map();
const style = {
setProperty: (name, value) => styleValues.set(name, value),
};
globalThis.localStorage = {
getItem: (key) => values.get(key) ?? null,
@@ -32,7 +35,7 @@ test("persists and applies the selected font size across the app", () => {
assert.equal(preference.getFontSize(), "smaller");
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
assert.equal(attributes.get("data-font-size"), "smaller");
assert.equal(style.fontSize, "16px");
assert.equal(styleValues.get("--buzz-type-rem"), "16px");
});
test("previews a font size without changing the saved preference", () => {
@@ -42,11 +45,11 @@ test("previews a font size without changing the saved preference", () => {
assert.equal(preference.getFontSize(), "smaller");
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
assert.equal(attributes.get("data-font-size"), "larger");
assert.equal(style.fontSize, "20.114286px");
assert.equal(styleValues.get("--buzz-type-rem"), "20.114286px");
preference.previewFontSize(null);
assert.equal(attributes.get("data-font-size"), "smaller");
assert.equal(style.fontSize, "17.6px");
assert.equal(styleValues.get("--buzz-type-rem"), "17.6px");
});
test("initializes from the stored font size", () => {
@@ -55,5 +58,5 @@ test("initializes from the stored font size", () => {
preference.initializeFontSizePreference();
assert.equal(preference.getFontSize(), "larger");
assert.equal(attributes.get("data-font-size"), "larger");
assert.equal(style.fontSize, "18.285714px");
assert.equal(styleValues.get("--buzz-type-rem"), "18.285714px");
});
+8 -6
View File
@@ -7,15 +7,17 @@ export const FONT_SIZE_STORAGE_KEY = "buzz.appearance.fontSize";
export const DEFAULT_FONT_SIZE: FontSize = "default";
/**
* Root sizes that map Tailwind's production `text-sm` size (0.875rem) to the
* three app-wide 14px, 15px, and 16px steps.
* Virtual rem sizes used by typography tokens. Keeping the real root at 16px
* prevents a text preference from also resizing rem-based layout geometry.
*/
const BASE_FONT_SIZE_PX: Record<FontSize, number> = {
const TYPE_REM_SIZE_PX: Record<FontSize, number> = {
smaller: 14 / 0.875,
default: 15 / 0.875,
larger: 16 / 0.875,
};
const TYPE_REM_PROPERTY = "--buzz-type-rem";
const listeners = new Set<() => void>();
let fontSize: FontSize = DEFAULT_FONT_SIZE;
let textZoomFactor = 1;
@@ -36,16 +38,16 @@ function readStoredFontSize(): FontSize {
}
}
function rootFontSizePx(size: FontSize): number {
function typeRemSizePx(size: FontSize): number {
return (
Math.round(BASE_FONT_SIZE_PX[size] * textZoomFactor * 1_000_000) / 1_000_000
Math.round(TYPE_REM_SIZE_PX[size] * textZoomFactor * 1_000_000) / 1_000_000
);
}
function applyFontSize(size: FontSize): void {
const root = globalThis.document?.documentElement;
root?.setAttribute("data-font-size", size);
if (root) root.style.fontSize = `${rootFontSizePx(size)}px`;
root?.style.setProperty(TYPE_REM_PROPERTY, `${typeRemSizePx(size)}px`);
}
function notifyListeners(): void {
@@ -132,8 +132,8 @@
width: 100%;
max-width: none;
color: hsl(var(--muted-foreground) / 0.72);
font-size: 0.875rem;
line-height: 1.25rem;
font-size: calc(var(--buzz-type-rem) * 0.875);
line-height: calc(var(--buzz-type-rem) * 1.25);
text-align: center;
opacity: 0;
pointer-events: none;
@@ -548,7 +548,7 @@
.buzz-onboarding-runtime-pill {
animation: buzz-onboarding-runtime-pill-in 180ms
cubic-bezier(0.22, 1, 0.36, 1) both;
font-size: 0.625rem;
font-size: calc(var(--buzz-type-rem) * 0.625);
letter-spacing: 0;
line-height: 1;
text-transform: uppercase;
@@ -45,7 +45,7 @@
color: hsl(var(--muted-foreground));
display: flex;
font: inherit;
font-size: 0.75rem;
font-size: calc(var(--buzz-type-rem) * 0.75);
font-weight: 500;
gap: 6px;
height: 30px;
@@ -130,7 +130,7 @@
.buzz-terminal-designator {
align-items: center;
display: inline-flex;
font-size: 0.75rem;
font-size: calc(var(--buzz-type-rem) * 0.75);
gap: 2px;
letter-spacing: 0;
}
+24 -8
View File
@@ -3,19 +3,35 @@
/* Catppuccin Latte (mauve accent) */
--radius: 0.625rem;
/*
* Default conversation type and comfy spacing for channels, DMs, threads,
* Inbox, and the composer. The app-wide Font size preference and Cmd +/-
* scale these rem values; Conversation density overrides only spacing.
* A virtual typography rem. Font preferences and Cmd +/- change this
* token while the browser root remains 16px, so text scales without also
* resizing rem-based widths, gaps, radii, and controls.
*/
--conversation-message-font-size: 0.875rem;
--conversation-message-line-height: 1.25rem;
--conversation-author-line-height: 1rem;
--buzz-type-rem: 1rem;
--text-xs: calc(var(--buzz-type-rem) * 0.75);
--text-sm: calc(var(--buzz-type-rem) * 0.875);
--text-base: var(--buzz-type-rem);
--text-lg: calc(var(--buzz-type-rem) * 1.125);
--text-xl: calc(var(--buzz-type-rem) * 1.25);
--text-2xl: calc(var(--buzz-type-rem) * 1.5);
--text-3xl: calc(var(--buzz-type-rem) * 1.875);
--text-4xl: calc(var(--buzz-type-rem) * 2.25);
--text-5xl: calc(var(--buzz-type-rem) * 3);
--text-6xl: calc(var(--buzz-type-rem) * 3.75);
/*
* Default conversation type and comfy spacing for channels, DMs, threads,
* Inbox, and the composer. Font size changes the type tokens only;
* Conversation density overrides only spacing.
*/
--conversation-message-font-size: calc(var(--buzz-type-rem) * 0.875);
--conversation-message-line-height: calc(var(--buzz-type-rem) * 1.25);
--conversation-author-line-height: var(--buzz-type-rem);
--conversation-body-gap: 0.125rem;
--conversation-row-padding-block: 0.375rem;
--conversation-paragraph-gap: 0.5rem;
--conversation-list-item-gap: 0.375rem;
--conversation-timestamp-font-size: 0.75rem;
--conversation-timestamp-line-height: 1rem;
--conversation-timestamp-font-size: calc(var(--buzz-type-rem) * 0.75);
--conversation-timestamp-line-height: var(--buzz-type-rem);
--background: 220 23.08% 94.9%;
--foreground: 234 16.02% 35.49%;
--card: 220 23.08% 94.9%;
+19 -14
View File
@@ -3,17 +3,16 @@ export default {
theme: {
extend: {
// Sub-`text-xs` ramp for meta text (timestamps, count badges, tracking
// labels) and tiny glyphs. Defined in rem so Cmd +/- zoom — which scales
// the root <html> font-size — keeps scaling them. Do NOT reintroduce
// arbitrary `text-[…rem]` / `text-[…px]` literals; the px-text guard
// rejects them. Stock scale picks up from here: xs (12px), sm (14px)…
// labels) and tiny glyphs. These follow the virtual typography rem so
// preferences and Cmd +/- scale text without changing layout geometry.
// Do NOT reintroduce arbitrary `text-[…rem]` / `text-[…px]` literals;
// the px-text guard rejects them. Stock scale picks up from xs.
fontSize: {
"2xs": "0.6875rem", // 11px — meta-text workhorse (timestamps, badges)
"3xs": "0.5rem", // 8px — tiny glyphs / micro labels
badge: "0.625rem", // 10px — compact status badges
// Shared channel, DM, thread, and composer type. Rem-backed variables
// keep app-wide font size and keyboard zoom consistent without
// branching individual components.
"2xs": "calc(var(--buzz-type-rem) * 0.6875)", // 11px at 16px type rem
"3xs": "calc(var(--buzz-type-rem) * 0.5)", // 8px at 16px type rem
badge: "calc(var(--buzz-type-rem) * 0.625)", // 10px at 16px type rem
// Shared channel, DM, thread, and composer type. Variables keep app-wide
// font size and keyboard zoom consistent without branching components.
message: [
"var(--conversation-message-font-size)",
{ lineHeight: "var(--conversation-message-line-height)" },
@@ -22,10 +21,16 @@ export default {
"var(--conversation-timestamp-font-size)",
{ lineHeight: "var(--conversation-timestamp-line-height)" },
],
// 40px — onboarding page titles (tightened tracking for large display type)
title: ["2.5rem", { lineHeight: "1.15", letterSpacing: "-0.02em" }],
// 36px — the backup-step private key, shown large in monospace
"nsec-key": ["2.25rem", { lineHeight: "1.3" }],
// 40px at the 16px type rem — onboarding page titles.
title: [
"calc(var(--buzz-type-rem) * 2.5)",
{ lineHeight: "1.15", letterSpacing: "-0.02em" },
],
// 36px at the 16px type rem — backup-step private key.
"nsec-key": [
"calc(var(--buzz-type-rem) * 2.25)",
{ lineHeight: "1.3" },
],
},
lineHeight: {
"message-author": "var(--conversation-author-line-height)",
@@ -663,11 +663,9 @@ test("app font size and conversation density apply independently", async ({
bodyGap: Number.parseFloat(
style.getPropertyValue("--conversation-body-gap"),
),
fontSize: Number.parseFloat(
style.getPropertyValue("--conversation-message-font-size"),
),
lineHeight: Number.parseFloat(
style.getPropertyValue("--conversation-message-line-height"),
fontSize: style.getPropertyValue("--conversation-message-font-size"),
lineHeight: style.getPropertyValue(
"--conversation-message-line-height",
),
paragraphGap: Number.parseFloat(
style.getPropertyValue("--conversation-paragraph-gap"),
@@ -675,8 +673,8 @@ test("app font size and conversation density apply independently", async ({
rowPadding: Number.parseFloat(
style.getPropertyValue("--conversation-row-padding-block"),
),
timestampFontSize: Number.parseFloat(
style.getPropertyValue("--conversation-timestamp-font-size"),
timestampFontSize: style.getPropertyValue(
"--conversation-timestamp-font-size",
),
timestampLineHeight: Number.parseFloat(
style.getPropertyValue("--conversation-timestamp-line-height"),
@@ -731,14 +729,14 @@ test("app font size and conversation density apply independently", async ({
);
await expect(fontSizeDescription).toHaveText("Adjust text throughout Buzz.");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 17.142857,
bodyGap: 0.125,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(17.142857px * .875)",
lineHeight: "calc(17.142857px * 1.25)",
paragraphGap: 0.5,
rowPadding: 0.375,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(17.142857px * .75)",
timestampLineHeight: 17.142857,
});
await expect
.poll(() =>
@@ -754,7 +752,7 @@ test("app font size and conversation density apply independently", async ({
.evaluate((element) => element.getBoundingClientRect().width),
]),
)
.toEqual([308.5625, 308.5625, 257.140625]);
.toEqual([288, 288, 240]);
await expect
.poll(() =>
previewMessage.evaluate((element) => {
@@ -766,8 +764,8 @@ test("app font size and conversation density apply independently", async ({
await expect.poll(readSettingsScale).toEqual({
fontSize: "15px",
lineHeight: "21.4286px",
minHeight: "68.5714px",
paddingBlock: "12.8571px",
minHeight: "64px",
paddingBlock: "12px",
});
await expect
.poll(readPreviewTimestampScale)
@@ -820,24 +818,24 @@ test("app font size and conversation density apply independently", async ({
const previewChipTopInset = previewChipBox.y - previewSurfaceBox.y;
expect(previewChipTopInset).toBeGreaterThanOrEqual(13);
expect(previewChipTopInset).toBeLessThanOrEqual(15);
await expect(previewContent).toHaveCSS("padding-top", "17.1429px");
await expect(previewContent).toHaveCSS("padding-right", "17.1429px");
await expect(previewContent).toHaveCSS("padding-bottom", "17.1429px");
await expect(previewContent).toHaveCSS("padding-left", "17.1429px");
await expect(previewContent).toHaveCSS("padding-top", "16px");
await expect(previewContent).toHaveCSS("padding-right", "16px");
await expect(previewContent).toHaveCSS("padding-bottom", "16px");
await expect(previewContent).toHaveCSS("padding-left", "16px");
expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeGreaterThanOrEqual(
17,
15,
);
expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeLessThanOrEqual(
19,
);
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeGreaterThanOrEqual(
17,
);
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeLessThanOrEqual(
19,
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeGreaterThanOrEqual(
15,
);
await expect(previewChip).toHaveCSS("padding-top", "4.28571px");
await expect(previewChip).toHaveCSS("padding-bottom", "4.28571px");
expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeLessThanOrEqual(
17,
);
await expect(previewChip).toHaveCSS("padding-top", "4px");
await expect(previewChip).toHaveCSS("padding-bottom", "4px");
await densityIndicator.evaluate((element) => {
element.addEventListener(
@@ -861,20 +859,20 @@ test("app font size and conversation density apply independently", async ({
)
.toBe("compact");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 17.142857,
bodyGap: 0,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(17.142857px * .875)",
lineHeight: "calc(17.142857px * 1.25)",
paragraphGap: 0.375,
rowPadding: 0.25,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(17.142857px * .75)",
timestampLineHeight: 17.142857,
});
await expect.poll(readSettingsScale).toEqual({
fontSize: "15px",
lineHeight: "21.4286px",
minHeight: "68.5714px",
paddingBlock: "12.8571px",
minHeight: "64px",
paddingBlock: "12px",
});
await expect
.poll(readPreviewTimestampScale)
@@ -896,14 +894,14 @@ test("app font size and conversation density apply independently", async ({
)
.toBe("larger");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 18.285714,
bodyGap: 0,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(18.285714px * .875)",
lineHeight: "calc(18.285714px * 1.25)",
paragraphGap: 0.375,
rowPadding: 0.25,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(18.285714px * .75)",
timestampLineHeight: 18.285714,
});
await expect
.poll(() =>
@@ -916,8 +914,8 @@ test("app font size and conversation density apply independently", async ({
await expect.poll(readSettingsScale).toEqual({
fontSize: "16px",
lineHeight: "22.8571px",
minHeight: "73.1429px",
paddingBlock: "13.7143px",
minHeight: "64px",
paddingBlock: "12px",
});
await expect
.poll(readPreviewTimestampScale)
@@ -935,20 +933,20 @@ test("app font size and conversation density apply independently", async ({
await expect(root).toHaveAttribute("data-font-size", "larger");
await expect(spacious).toHaveAttribute("aria-pressed", "true");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 18.285714,
bodyGap: 0.25,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(18.285714px * .875)",
lineHeight: "calc(18.285714px * 1.25)",
paragraphGap: 0.625,
rowPadding: 0.5,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(18.285714px * .75)",
timestampLineHeight: 18.285714,
});
await expect.poll(readSettingsScale).toEqual({
fontSize: "16px",
lineHeight: "22.8571px",
minHeight: "73.1429px",
paddingBlock: "13.7143px",
minHeight: "64px",
paddingBlock: "12px",
});
await expect
.poll(readPreviewTimestampScale)
@@ -962,14 +960,14 @@ test("app font size and conversation density apply independently", async ({
await expect(root).toHaveAttribute("data-font-size", "smaller");
await expect(smaller).toHaveAttribute("aria-pressed", "true");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 16,
bodyGap: 0.25,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(16px * .875)",
lineHeight: "calc(16px * 1.25)",
paragraphGap: 0.625,
rowPadding: 0.5,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(16px * .75)",
timestampLineHeight: 16,
});
await expect
.poll(() =>
@@ -1029,20 +1027,20 @@ test("app font size and conversation density apply independently", async ({
)
.toBe("comfortable");
await expect.poll(readScale).toEqual({
authorLineHeight: 1,
authorLineHeight: 17.142857,
bodyGap: 0.25,
fontSize: 0.875,
lineHeight: 1.25,
fontSize: "calc(17.142857px * .875)",
lineHeight: "calc(17.142857px * 1.25)",
paragraphGap: 0.625,
rowPadding: 0.5,
timestampFontSize: 0.75,
timestampLineHeight: 1,
timestampFontSize: "calc(17.142857px * .75)",
timestampLineHeight: 17.142857,
});
await expect.poll(readSettingsScale).toEqual({
fontSize: "15px",
lineHeight: "21.4286px",
minHeight: "68.5714px",
paddingBlock: "12.8571px",
minHeight: "64px",
paddingBlock: "12px",
});
await page.mouse.up();
await expect(spacious).toHaveAttribute("aria-pressed", "true");
@@ -414,7 +414,7 @@ test.describe("inbox refactor screenshots", () => {
]);
await expect.poll(readConversationMetrics).toEqual([
{ fontSize: "15px", lineHeight: "21.4286px" },
{ paddingBottom: "6.42857px", paddingTop: "6.42857px" },
{ paddingBottom: "6px", paddingTop: "6px" },
{ fontSize: "15px", lineHeight: "17.1429px" },
{ fontSize: "15px", lineHeight: "21.4286px" },
{ fontSize: "12.8571px", lineHeight: "17.1429px" },
@@ -429,7 +429,7 @@ test.describe("inbox refactor screenshots", () => {
});
await expect.poll(readConversationMetrics).toEqual([
{ fontSize: "15px", lineHeight: "21.4286px" },
{ paddingBottom: "4.28571px", paddingTop: "4.28571px" },
{ paddingBottom: "4px", paddingTop: "4px" },
{ fontSize: "15px", lineHeight: "17.1429px" },
{ fontSize: "15px", lineHeight: "21.4286px" },
{ fontSize: "12.8571px", lineHeight: "17.1429px" },
@@ -438,7 +438,7 @@ test.describe("inbox refactor screenshots", () => {
await page.locator("html").evaluate((root) => {
root.setAttribute("data-font-size", "smaller");
root.style.fontSize = "16px";
root.style.setProperty("--buzz-type-rem", "16px");
});
await expect.poll(readConversationMetrics).toEqual([
{ fontSize: "14px", lineHeight: "20px" },
@@ -454,11 +454,11 @@ test.describe("inbox refactor screenshots", () => {
await page.locator("html").evaluate((root) => {
root.setAttribute("data-conversation-density", "spacious");
root.setAttribute("data-font-size", "larger");
root.style.fontSize = "18.285714px";
root.style.setProperty("--buzz-type-rem", "18.285714px");
});
await expect.poll(readConversationMetrics).toEqual([
{ fontSize: "16px", lineHeight: "22.8571px" },
{ paddingBottom: "9.14286px", paddingTop: "9.14286px" },
{ paddingBottom: "8px", paddingTop: "8px" },
{ fontSize: "16px", lineHeight: "18.2857px" },
{ fontSize: "16px", lineHeight: "22.8571px" },
{ fontSize: "13.7143px", lineHeight: "18.2857px" },
@@ -470,7 +470,7 @@ test.describe("inbox refactor screenshots", () => {
await page.locator("html").evaluate((root) => {
root.setAttribute("data-conversation-density", "comfortable");
root.setAttribute("data-font-size", "default");
root.style.fontSize = "17.142857px";
root.style.setProperty("--buzz-type-rem", "17.142857px");
});
await page.evaluate(() => {
@@ -490,15 +490,18 @@ test.describe("inbox refactor screenshots", () => {
await expect
.poll(async () => [
await page.evaluate(
() => window.getComputedStyle(document.documentElement).fontSize,
await page.evaluate(() =>
window
.getComputedStyle(document.documentElement)
.getPropertyValue("--buzz-type-rem")
.trim(),
),
...(await readConversationMetrics()),
])
.toEqual([
"18.8571px",
"18.857143px",
{ fontSize: "16.5px", lineHeight: "23.5714px" },
{ paddingBottom: "7.07143px", paddingTop: "7.07143px" },
{ paddingBottom: "6px", paddingTop: "6px" },
{ fontSize: "16.5px", lineHeight: "18.8571px" },
{ fontSize: "16.5px", lineHeight: "23.5714px" },
{ fontSize: "14.1429px", lineHeight: "18.8571px" },
+12 -5
View File
@@ -1556,7 +1556,10 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
const getTextScaleState = () =>
page.evaluate(() => ({
fontSize: getComputedStyle(document.documentElement).fontSize,
rootFontSize: getComputedStyle(document.documentElement).fontSize,
textRemSize: getComputedStyle(document.documentElement)
.getPropertyValue("--buzz-type-rem")
.trim(),
storedScale: localStorage.getItem("buzz:text-scale"),
webviewZoom: (window as Window & { __BUZZ_E2E_WEBVIEW_ZOOM__?: number })
.__BUZZ_E2E_WEBVIEW_ZOOM__,
@@ -1587,7 +1590,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("+", "Equal", true);
await expect.poll(getTextScaleState).toEqual({
fontSize: "17.6px",
rootFontSize: "16px",
textRemSize: "18.857143px",
storedScale: "1.1",
webviewZoom: 1,
});
@@ -1595,7 +1599,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("-", "Minus");
await expect.poll(getTextScaleState).toEqual({
fontSize: "16px",
rootFontSize: "16px",
textRemSize: "17.142857px",
storedScale: null,
webviewZoom: 1,
});
@@ -1604,7 +1609,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("+", "Equal", true);
await expect.poll(getTextScaleState).toEqual({
fontSize: "19.2px",
rootFontSize: "16px",
textRemSize: "20.571429px",
storedScale: "1.2",
webviewZoom: 1,
});
@@ -1612,7 +1618,8 @@ test("supports webview zoom keyboard shortcuts", async ({ page }) => {
await dispatchPrimaryShortcut("0", "Digit0");
await expect.poll(getTextScaleState).toEqual({
fontSize: "16px",
rootFontSize: "16px",
textRemSize: "17.142857px",
storedScale: null,
webviewZoom: 1,
});
@@ -23,7 +23,7 @@ const EXPECTED_NAV_CENTER_Y = 23;
// The macOS traffic lights are native chrome: with `trafficLightPosition`
// x:16 they occupy roughly x 1668 regardless of the app's Cmd +/- text
// zoom. The top-chrome nav row must clear that band in fixed px, so the
// clearance cannot shrink when the root font size scales down.
// clearance cannot change when text scales.
const TRAFFIC_LIGHT_RIGHT_EDGE = 72;
async function spoofMacPlatform(page: import("@playwright/test").Page) {
@@ -90,15 +90,24 @@ async function seedTextScale(
}, scale);
}
async function expectRootFontSize(
async function expectTextRemSize(
page: import("@playwright/test").Page,
fontSize: string,
) {
await expect
.poll(() =>
page.evaluate(() => getComputedStyle(document.documentElement).fontSize),
page.evaluate(() =>
getComputedStyle(document.documentElement)
.getPropertyValue("--buzz-type-rem")
.trim(),
),
)
.toBe(fontSize);
await expect
.poll(() =>
page.evaluate(() => getComputedStyle(document.documentElement).fontSize),
)
.toBe("16px");
}
test.describe("top chrome macOS traffic-light clearance under text zoom", () => {
@@ -143,8 +152,8 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () =>
await installMockBridge(page);
await page.goto("/");
// Confirm the zoomed-out scale actually applied to the root font size.
await expectRootFontSize(page, "12px");
// Confirm the zoomed-out text scale applied without changing the root.
await expectTextRemSize(page, "12.857143px");
expect(await firstNavButtonX(page)).toBeGreaterThanOrEqual(
TRAFFIC_LIGHT_RIGHT_EDGE,
@@ -161,7 +170,7 @@ test.describe("top chrome macOS traffic-light clearance under text zoom", () =>
await installMockBridge(page);
await page.goto("/");
await expectRootFontSize(page, "24px");
await expectTextRemSize(page, "25.714286px");
expect(await firstNavButtonX(page)).toBeGreaterThanOrEqual(
TRAFFIC_LIGHT_RIGHT_EDGE,