From c81bdce7a4005bbf10c2958729d7af470b663fa9 Mon Sep 17 00:00:00 2001 From: kenny lopez Date: Wed, 12 Aug 2026 13:06:42 +0100 Subject: [PATCH] Align font sizes with production baseline Signed-off-by: kenny lopez --- .../shared/lib/fontSizePreference.test.mjs | 8 +- desktop/src/shared/lib/fontSizePreference.ts | 14 +- desktop/src/shared/styles/globals/theme.css | 6 +- .../tests/e2e/buzz-theme-screenshots.spec.ts | 158 ++++++++++-------- .../e2e/inbox-refactor-screenshots.spec.ts | 70 ++++---- 5 files changed, 136 insertions(+), 120 deletions(-) diff --git a/desktop/src/shared/lib/fontSizePreference.test.mjs b/desktop/src/shared/lib/fontSizePreference.test.mjs index 6ca831873..f62b427bd 100644 --- a/desktop/src/shared/lib/fontSizePreference.test.mjs +++ b/desktop/src/shared/lib/fontSizePreference.test.mjs @@ -32,7 +32,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, "15px"); + assert.equal(style.fontSize, "16px"); }); test("previews a font size without changing the saved preference", () => { @@ -42,11 +42,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, "18.7px"); + assert.equal(style.fontSize, "20.114286px"); preference.previewFontSize(null); assert.equal(attributes.get("data-font-size"), "smaller"); - assert.equal(style.fontSize, "16.5px"); + assert.equal(style.fontSize, "17.6px"); }); test("initializes from the stored font size", () => { @@ -55,5 +55,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, "17px"); + assert.equal(style.fontSize, "18.285714px"); }); diff --git a/desktop/src/shared/lib/fontSizePreference.ts b/desktop/src/shared/lib/fontSizePreference.ts index e84f105eb..a55173624 100644 --- a/desktop/src/shared/lib/fontSizePreference.ts +++ b/desktop/src/shared/lib/fontSizePreference.ts @@ -6,10 +6,14 @@ export type FontSize = "smaller" | "default" | "larger"; 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. + */ const BASE_FONT_SIZE_PX: Record = { - smaller: 15, - default: 16, - larger: 17, + smaller: 14 / 0.875, + default: 15 / 0.875, + larger: 16 / 0.875, }; const listeners = new Set<() => void>(); @@ -33,7 +37,9 @@ function readStoredFontSize(): FontSize { } function rootFontSizePx(size: FontSize): number { - return Math.round(BASE_FONT_SIZE_PX[size] * textZoomFactor * 1_000) / 1_000; + return ( + Math.round(BASE_FONT_SIZE_PX[size] * textZoomFactor * 1_000_000) / 1_000_000 + ); } function applyFontSize(size: FontSize): void { diff --git a/desktop/src/shared/styles/globals/theme.css b/desktop/src/shared/styles/globals/theme.css index 8828930fc..ec7fdc67e 100644 --- a/desktop/src/shared/styles/globals/theme.css +++ b/desktop/src/shared/styles/globals/theme.css @@ -7,14 +7,14 @@ * Inbox, and the composer. The app-wide Font size preference and Cmd +/- * scale these rem values; Conversation density overrides only spacing. */ - --conversation-message-font-size: 0.9375rem; + --conversation-message-font-size: 0.875rem; --conversation-message-line-height: 1.25rem; - --conversation-author-line-height: 1.0625rem; + --conversation-author-line-height: 1rem; --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.6875rem; + --conversation-timestamp-font-size: 0.75rem; --conversation-timestamp-line-height: 1rem; --background: 220 23.08% 94.9%; --foreground: 234 16.02% 35.49%; diff --git a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts index c3c622ad8..7ab8c1618 100644 --- a/desktop/tests/e2e/buzz-theme-screenshots.spec.ts +++ b/desktop/tests/e2e/buzz-theme-screenshots.spec.ts @@ -731,13 +731,13 @@ 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.0625, + authorLineHeight: 1, bodyGap: 0.125, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.5, rowPadding: 0.375, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect @@ -754,7 +754,7 @@ test("app font size and conversation density apply independently", async ({ .evaluate((element) => element.getBoundingClientRect().width), ]), ) - .toEqual([288, 288, 240]); + .toEqual([308.5625, 308.5625, 257.140625]); await expect .poll(() => previewMessage.evaluate((element) => { @@ -762,15 +762,19 @@ test("app font size and conversation density apply independently", async ({ return [style.fontSize, style.lineHeight]; }), ) - .toEqual(["15px", "20px"]); + .toEqual(["15px", "21.4286px"]); await expect.poll(readSettingsScale).toEqual({ - fontSize: "14px", - lineHeight: "20px", - minHeight: "64px", - paddingBlock: "12px", + fontSize: "15px", + lineHeight: "21.4286px", + minHeight: "68.5714px", + paddingBlock: "12.8571px", }); - await expect.poll(readPreviewTimestampScale).toEqual(["11px", "16px"]); - await expect.poll(readSettingsChromeScale).toEqual(["24px", "14px", "18px"]); + await expect + .poll(readPreviewTimestampScale) + .toEqual(["12.8571px", "17.1429px"]); + await expect + .poll(readSettingsChromeScale) + .toEqual(["25.7143px", "15px", "19.2857px"]); await expect(densityIndicator).toHaveCSS("transition-duration", "0.2s"); await expect(densityIndicator).toHaveCSS("transition-property", /transform/); await expect(fontSizeIndicator).toHaveCSS("transition-duration", "0.2s"); @@ -811,29 +815,29 @@ test("app font size and conversation density apply independently", async ({ previewSurfaceBox.x + previewSurfaceBox.width - (previewChipBox.x + previewChipBox.width); - expect(previewChipRightInset).toBeGreaterThanOrEqual(12); - expect(previewChipRightInset).toBeLessThanOrEqual(13); + expect(previewChipRightInset).toBeGreaterThanOrEqual(13); + expect(previewChipRightInset).toBeLessThanOrEqual(15); const previewChipTopInset = previewChipBox.y - previewSurfaceBox.y; - expect(previewChipTopInset).toBeGreaterThanOrEqual(12); - expect(previewChipTopInset).toBeLessThanOrEqual(13); - 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(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"); expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeGreaterThanOrEqual( - 16, + 17, ); expect(firstPreviewMessageBox.x - previewSurfaceBox.x).toBeLessThanOrEqual( - 18, + 19, ); expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeGreaterThanOrEqual( - 16, + 17, ); expect(firstPreviewMessageBox.y - previewSurfaceBox.y).toBeLessThanOrEqual( - 18, + 19, ); - await expect(previewChip).toHaveCSS("padding-top", "4px"); - await expect(previewChip).toHaveCSS("padding-bottom", "4px"); + await expect(previewChip).toHaveCSS("padding-top", "4.28571px"); + await expect(previewChip).toHaveCSS("padding-bottom", "4.28571px"); await densityIndicator.evaluate((element) => { element.addEventListener( @@ -857,23 +861,27 @@ test("app font size and conversation density apply independently", async ({ ) .toBe("compact"); await expect.poll(readScale).toEqual({ - authorLineHeight: 1.0625, + authorLineHeight: 1, bodyGap: 0, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.375, rowPadding: 0.25, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect.poll(readSettingsScale).toEqual({ - fontSize: "14px", - lineHeight: "20px", - minHeight: "64px", - paddingBlock: "12px", + fontSize: "15px", + lineHeight: "21.4286px", + minHeight: "68.5714px", + paddingBlock: "12.8571px", }); - await expect.poll(readPreviewTimestampScale).toEqual(["11px", "16px"]); - await expect.poll(readSettingsChromeScale).toEqual(["24px", "14px", "18px"]); + await expect + .poll(readPreviewTimestampScale) + .toEqual(["12.8571px", "17.1429px"]); + await expect + .poll(readSettingsChromeScale) + .toEqual(["25.7143px", "15px", "19.2857px"]); await larger.click(); await expect(root).toHaveAttribute("data-conversation-density", "compact"); @@ -888,13 +896,13 @@ test("app font size and conversation density apply independently", async ({ ) .toBe("larger"); await expect.poll(readScale).toEqual({ - authorLineHeight: 1.0625, + authorLineHeight: 1, bodyGap: 0, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.375, rowPadding: 0.25, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect @@ -904,17 +912,19 @@ test("app font size and conversation density apply independently", async ({ return [style.fontSize, style.lineHeight]; }), ) - .toEqual(["15.9375px", "21.25px"]); + .toEqual(["16px", "22.8571px"]); await expect.poll(readSettingsScale).toEqual({ - fontSize: "14.875px", - lineHeight: "21.25px", - minHeight: "68px", - paddingBlock: "12.75px", + fontSize: "16px", + lineHeight: "22.8571px", + minHeight: "73.1429px", + paddingBlock: "13.7143px", }); - await expect.poll(readPreviewTimestampScale).toEqual(["11.6875px", "17px"]); + await expect + .poll(readPreviewTimestampScale) + .toEqual(["13.7143px", "18.2857px"]); await expect .poll(readSettingsChromeScale) - .toEqual(["25.5px", "14.875px", "19.125px"]); + .toEqual(["27.4286px", "16px", "20.5714px"]); await waitForAnimations(page); await page.getByTestId("appearance-preferences-card").screenshot({ path: `${SHOTS}/15-conversation-compact-larger.png`, @@ -925,38 +935,40 @@ 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.0625, + authorLineHeight: 1, bodyGap: 0.25, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.625, rowPadding: 0.5, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect.poll(readSettingsScale).toEqual({ - fontSize: "14.875px", - lineHeight: "21.25px", - minHeight: "68px", - paddingBlock: "12.75px", + fontSize: "16px", + lineHeight: "22.8571px", + minHeight: "73.1429px", + paddingBlock: "13.7143px", }); - await expect.poll(readPreviewTimestampScale).toEqual(["11.6875px", "17px"]); + await expect + .poll(readPreviewTimestampScale) + .toEqual(["13.7143px", "18.2857px"]); await expect .poll(readSettingsChromeScale) - .toEqual(["25.5px", "14.875px", "19.125px"]); + .toEqual(["27.4286px", "16px", "20.5714px"]); await smaller.click(); await expect(root).toHaveAttribute("data-conversation-density", "spacious"); await expect(root).toHaveAttribute("data-font-size", "smaller"); await expect(smaller).toHaveAttribute("aria-pressed", "true"); await expect.poll(readScale).toEqual({ - authorLineHeight: 1.0625, + authorLineHeight: 1, bodyGap: 0.25, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.625, rowPadding: 0.5, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect @@ -966,17 +978,15 @@ test("app font size and conversation density apply independently", async ({ return [style.fontSize, style.lineHeight]; }), ) - .toEqual(["14.0625px", "18.75px"]); + .toEqual(["14px", "20px"]); await expect.poll(readSettingsScale).toEqual({ - fontSize: "13.125px", - lineHeight: "18.75px", - minHeight: "60px", - paddingBlock: "11.25px", + fontSize: "14px", + lineHeight: "20px", + minHeight: "64px", + paddingBlock: "12px", }); - await expect.poll(readPreviewTimestampScale).toEqual(["10.3125px", "15px"]); - await expect - .poll(readSettingsChromeScale) - .toEqual(["22.5px", "13.125px", "16.875px"]); + await expect.poll(readPreviewTimestampScale).toEqual(["12px", "16px"]); + await expect.poll(readSettingsChromeScale).toEqual(["24px", "14px", "18px"]); await waitForAnimations(page); await page.getByTestId("appearance-preferences-card").screenshot({ path: `${SHOTS}/16-conversation-spacious-smaller.png`, @@ -1019,20 +1029,20 @@ test("app font size and conversation density apply independently", async ({ ) .toBe("comfortable"); await expect.poll(readScale).toEqual({ - authorLineHeight: 1.0625, + authorLineHeight: 1, bodyGap: 0.25, - fontSize: 0.9375, + fontSize: 0.875, lineHeight: 1.25, paragraphGap: 0.625, rowPadding: 0.5, - timestampFontSize: 0.6875, + timestampFontSize: 0.75, timestampLineHeight: 1, }); await expect.poll(readSettingsScale).toEqual({ - fontSize: "14px", - lineHeight: "20px", - minHeight: "64px", - paddingBlock: "12px", + fontSize: "15px", + lineHeight: "21.4286px", + minHeight: "68.5714px", + paddingBlock: "12.8571px", }); await page.mouse.up(); await expect(spacious).toHaveAttribute("aria-pressed", "true"); @@ -1084,10 +1094,10 @@ test("app font size and conversation density apply independently", async ({ return [style.fontSize, style.lineHeight]; }), ) - .toEqual(["15.9375px", "21.25px"]); + .toEqual(["16px", "22.8571px"]); await expect .poll(readSettingsChromeScale) - .toEqual(["25.5px", "14.875px", "19.125px"]); + .toEqual(["27.4286px", "16px", "20.5714px"]); await page.mouse.up(); await expect(larger).toHaveAttribute("aria-pressed", "true"); await expect diff --git a/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts b/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts index 935e1154b..1f5e477f8 100644 --- a/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts +++ b/desktop/tests/e2e/inbox-refactor-screenshots.spec.ts @@ -343,7 +343,7 @@ test.describe("inbox refactor screenshots", () => { await expect(firstUnreadRow).toBeVisible(); const listPreview = firstUnreadRow.locator(".inbox-preview-markdown"); await expect(listPreview).toHaveCSS("font-size", "15px"); - await expect(listPreview).toHaveCSS("line-height", "20px"); + await expect(listPreview).toHaveCSS("line-height", "21.4286px"); await firstUnreadRow.click(); const detail = page.getByTestId("home-inbox-detail"); @@ -413,12 +413,12 @@ test.describe("inbox refactor screenshots", () => { }), ]); await expect.poll(readConversationMetrics).toEqual([ - { fontSize: "15px", lineHeight: "20px" }, - { paddingBottom: "6px", paddingTop: "6px" }, - { fontSize: "15px", lineHeight: "17px" }, - { fontSize: "15px", lineHeight: "20px" }, - { fontSize: "11px", lineHeight: "16px" }, - { fontSize: "15px", lineHeight: "20px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, + { paddingBottom: "6.42857px", paddingTop: "6.42857px" }, + { fontSize: "15px", lineHeight: "17.1429px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, + { fontSize: "12.8571px", lineHeight: "17.1429px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, ]); await waitForAnimations(page); @@ -428,25 +428,25 @@ test.describe("inbox refactor screenshots", () => { root.setAttribute("data-conversation-density", "compact"); }); await expect.poll(readConversationMetrics).toEqual([ - { fontSize: "15px", lineHeight: "20px" }, - { paddingBottom: "4px", paddingTop: "4px" }, - { fontSize: "15px", lineHeight: "17px" }, - { fontSize: "15px", lineHeight: "20px" }, - { fontSize: "11px", lineHeight: "16px" }, - { fontSize: "15px", lineHeight: "20px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, + { paddingBottom: "4.28571px", paddingTop: "4.28571px" }, + { fontSize: "15px", lineHeight: "17.1429px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, + { fontSize: "12.8571px", lineHeight: "17.1429px" }, + { fontSize: "15px", lineHeight: "21.4286px" }, ]); await page.locator("html").evaluate((root) => { root.setAttribute("data-font-size", "smaller"); - root.style.fontSize = "15px"; + root.style.fontSize = "16px"; }); await expect.poll(readConversationMetrics).toEqual([ - { fontSize: "14.0625px", lineHeight: "18.75px" }, - { paddingBottom: "3.75px", paddingTop: "3.75px" }, - { fontSize: "14.0625px", lineHeight: "15.9375px" }, - { fontSize: "14.0625px", lineHeight: "18.75px" }, - { fontSize: "10.3125px", lineHeight: "15px" }, - { fontSize: "14.0625px", lineHeight: "18.75px" }, + { fontSize: "14px", lineHeight: "20px" }, + { paddingBottom: "4px", paddingTop: "4px" }, + { fontSize: "14px", lineHeight: "16px" }, + { fontSize: "14px", lineHeight: "20px" }, + { fontSize: "12px", lineHeight: "16px" }, + { fontSize: "14px", lineHeight: "20px" }, ]); await waitForAnimations(page); await page.screenshot({ path: `${SHOTS}/05-thread-context-compact.png` }); @@ -454,15 +454,15 @@ 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 = "17px"; + root.style.fontSize = "18.285714px"; }); await expect.poll(readConversationMetrics).toEqual([ - { fontSize: "15.9375px", lineHeight: "21.25px" }, - { paddingBottom: "8.5px", paddingTop: "8.5px" }, - { fontSize: "15.9375px", lineHeight: "18.0625px" }, - { fontSize: "15.9375px", lineHeight: "21.25px" }, - { fontSize: "11.6875px", lineHeight: "17px" }, - { fontSize: "15.9375px", lineHeight: "21.25px" }, + { fontSize: "16px", lineHeight: "22.8571px" }, + { paddingBottom: "9.14286px", paddingTop: "9.14286px" }, + { fontSize: "16px", lineHeight: "18.2857px" }, + { fontSize: "16px", lineHeight: "22.8571px" }, + { fontSize: "13.7143px", lineHeight: "18.2857px" }, + { fontSize: "16px", lineHeight: "22.8571px" }, ]); await waitForAnimations(page); await page.screenshot({ path: `${SHOTS}/06-thread-context-spacious.png` }); @@ -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 = "16px"; + root.style.fontSize = "17.142857px"; }); await page.evaluate(() => { @@ -496,13 +496,13 @@ test.describe("inbox refactor screenshots", () => { ...(await readConversationMetrics()), ]) .toEqual([ - "17.6px", - { fontSize: "16.5px", lineHeight: "22px" }, - { paddingBottom: "6.6px", paddingTop: "6.6px" }, - { fontSize: "16.5px", lineHeight: "18.7px" }, - { fontSize: "16.5px", lineHeight: "22px" }, - { fontSize: "12.1px", lineHeight: "17.6px" }, - { fontSize: "16.5px", lineHeight: "22px" }, + "18.8571px", + { fontSize: "16.5px", lineHeight: "23.5714px" }, + { paddingBottom: "7.07143px", paddingTop: "7.07143px" }, + { fontSize: "16.5px", lineHeight: "18.8571px" }, + { fontSize: "16.5px", lineHeight: "23.5714px" }, + { fontSize: "14.1429px", lineHeight: "18.8571px" }, + { fontSize: "16.5px", lineHeight: "23.5714px" }, ]); }); });