From 8319602f28510888df53bdc97af8e46a3849329c Mon Sep 17 00:00:00 2001 From: morgmart <98432065+morgmart@users.noreply.github.com> Date: Sat, 10 Oct 2026 10:32:27 -0700 Subject: [PATCH 1/2] Give messages more breathing room Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> --- scripts/design-system/check-type.mjs | 1 + src/features/messages/Messages.module.css | 14 +++++--------- src/shared/design-system/DESIGN.md | 4 ++++ src/shared/design-system/styles/typography.css | 5 +++++ src/shared/design-system/tokens/registry.ts | 12 +++++++++++- 5 files changed, 26 insertions(+), 10 deletions(-) diff --git a/scripts/design-system/check-type.mjs b/scripts/design-system/check-type.mjs index 0f04f5560..1cf362653 100644 --- a/scripts/design-system/check-type.mjs +++ b/scripts/design-system/check-type.mjs @@ -48,6 +48,7 @@ const SIZE_ROLES = [ "body-lg", "body-sm", "body", + "message", "mono-lg", "mono-sm", "mono", diff --git a/src/features/messages/Messages.module.css b/src/features/messages/Messages.module.css index e4f9df6e4..aaf26f7cc 100644 --- a/src/features/messages/Messages.module.css +++ b/src/features/messages/Messages.module.css @@ -145,12 +145,7 @@ min-width: 0; } .text { - margin: var(--space-1) 0 0; - font-size: var(--text-body-sm); - line-height: var(--text-body-sm--line-height); - letter-spacing: var(--text-body-sm--letter-spacing); white-space: normal; - overflow-wrap: anywhere; } .text[data-single-emoji] { --inline-emoji-size: var(--size-emoji-display); @@ -1301,9 +1296,10 @@ button.mediaPreview { .text, .plainText { margin: var(--space-1) 0 0; - font-size: var(--text-body-sm); - line-height: var(--text-body-sm--line-height); - letter-spacing: var(--text-body-sm--letter-spacing); + font-size: var(--text-message); + line-height: var(--text-message--line-height); + letter-spacing: var(--text-message--letter-spacing); + font-weight: var(--text-message--font-weight); overflow-wrap: anywhere; } .plainText { @@ -1323,7 +1319,7 @@ button.mediaPreview { margin-bottom: 0; } .text :where(p, ul, ol, blockquote, pre, table, hr) { - margin: var(--space-3) 0; + margin: var(--space-4) 0; } .text :where(h1, h2, h3, h4, h5, h6) { margin: var(--space-4) 0 var(--space-1h); diff --git a/src/shared/design-system/DESIGN.md b/src/shared/design-system/DESIGN.md index 7032e3579..888b05b53 100644 --- a/src/shared/design-system/DESIGN.md +++ b/src/shared/design-system/DESIGN.md @@ -1122,6 +1122,10 @@ Values scale with the host interface-size preference. independent from caption even though their sizes currently match. `text-mono-lg` and `text-mono-sm` are compatibility aliases for this same setting, not extra sizes. - Caption uses 12/16 and 0.0133em tracking. Buzz’s `text-body` uses 14/20; `text-label` uses 16/24 and `text-body-lg` uses 20/28. +- Message prose uses `text-message`: 14px with 1.625 leading (22.75px at + default), matching Berd’s relaxed reply rhythm for both sent and received + messages. The message renderer uses `space-4` between prose blocks; labels, + controls, code, tables and headings retain their existing type roles. - Preserve text preferences and browser zoom. Author values in scaled rem and keep layout geometry independent of text scaling. diff --git a/src/shared/design-system/styles/typography.css b/src/shared/design-system/styles/typography.css index 48d8d558c..26682079d 100644 --- a/src/shared/design-system/styles/typography.css +++ b/src/shared/design-system/styles/typography.css @@ -64,6 +64,11 @@ --text-body-sm--line-height: 1.4285714285714286; --text-body-sm--letter-spacing: -0.0025em; --text-body-sm--font-weight: var(--type-weight-normal); + /* Message prose keeps interface sizing with Berd's relaxed reading rhythm. */ + --text-message: var(--type-size-14); + --text-message--line-height: 1.625; + --text-message--letter-spacing: -0.0025em; + --text-message--font-weight: var(--type-weight-normal); --text-label: var(--type-size-16); --text-label--line-height: 1.5; --text-label--letter-spacing: 0em; diff --git a/src/shared/design-system/tokens/registry.ts b/src/shared/design-system/tokens/registry.ts index bc9056bf8..5a64b5444 100644 --- a/src/shared/design-system/tokens/registry.ts +++ b/src/shared/design-system/tokens/registry.ts @@ -1035,7 +1035,7 @@ export const TYPE_ROLES: TypeRole[] = [ lineHeight: "20px", tracking: "-0.005em", weight: "400", - use: "Reading text and messages.", + use: "Interface reading text.", status: "core", }, { @@ -1048,6 +1048,16 @@ export const TYPE_ROLES: TypeRole[] = [ use: "Supporting or dense reading text.", status: "core", }, + { + token: "text-message", + pointsAt: "body/message", + size: "14px", + lineHeight: "1.625 (22.75px at default)", + tracking: "-0.0025em", + weight: "400", + use: "Sent and received message prose, with relaxed line spacing.", + status: "core", + }, { token: "text-label", pointsAt: "body/label-medium", From 47791c78b630bf14b6be4995d5b85462a933b91f Mon Sep 17 00:00:00 2001 From: morgmart <98432065+morgmart@users.noreply.github.com> Date: Sat, 10 Oct 2026 12:57:21 -0700 Subject: [PATCH 2/2] Keep message timestamps aligned and preserve overlay coverage Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> --- src/features/messages/Messages.module.css | 7 ++++++- tests/browser/message-actions.spec.mjs | 25 +++++++++++++++++++++++ tests/browser/message-overlays.spec.mjs | 12 +++++++---- 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/src/features/messages/Messages.module.css b/src/features/messages/Messages.module.css index aaf26f7cc..30c3154e9 100644 --- a/src/features/messages/Messages.module.css +++ b/src/features/messages/Messages.module.css @@ -1804,9 +1804,14 @@ html[data-keyboard-input] .quickReactionGlyph { z-index: 1; background: var(--surface-panel); color: var(--text-subtle); + /* Align the caption within the prose's first line without changing its type. */ + display: flex; + align-items: center; + block-size: calc(var(--text-message) * var(--text-message--line-height)); font-size: var(--text-caption); - line-height: calc(var(--text-body-sm) * var(--text-body-sm--line-height)); + line-height: var(--text-caption--line-height); letter-spacing: var(--text-caption--letter-spacing); + font-weight: var(--text-caption--font-weight); font-variant-numeric: tabular-nums; white-space: nowrap; } diff --git a/tests/browser/message-actions.spec.mjs b/tests/browser/message-actions.spec.mjs index 398bc060f..155bd5040 100644 --- a/tests/browser/message-actions.spec.mjs +++ b/tests/browser/message-actions.spec.mjs @@ -468,6 +468,24 @@ test("narrow timeline continuation actions never cover prose or move adjacent ro await page.mouse.move(0, 0); await expect(actions).toHaveCSS("opacity", "0"); await settle(page); + const expectClockAligned = async () => { + // The caption-sized clock is centered beside the first prose line, including + // when interface scaling changes their independently owned type roles. + await expect + .poll(() => + row.evaluate((element) => { + const clock = element + .querySelector('time > span[aria-hidden="true"]') + .getBoundingClientRect(); + const prose = element.querySelector("p"); + const firstLineCenter = + prose.getBoundingClientRect().top + + Number.parseFloat(getComputedStyle(prose).lineHeight) / 2; + return Math.abs(clock.top + clock.height / 2 - firstLineCenter); + }), + ) + .toBeLessThan(0.5); + }; const baseline = await following.boundingBox(); for (const mode of ["hover", "focus"]) { if (mode === "hover") await row.hover(); @@ -481,6 +499,7 @@ test("narrow timeline continuation actions never cover prose or move adjacent ro expect(toolbar.y + toolbar.height).toBeLessThanOrEqual(prose.y); await expect(actions).toHaveCSS("position", "fixed"); expect((await following.boundingBox()).y).toBe(baseline.y); + await expectClockAligned(); await expect .poll(() => link.evaluate((node) => { @@ -495,6 +514,12 @@ test("narrow timeline continuation actions never cover prose or move adjacent ro ) .toBe(true); } + await page.evaluate(() => + document.documentElement.style.setProperty("--buzz-text-scale", "2"), + ); + await row.scrollIntoViewIfNeeded(); + await settle(page); + await expectClockAligned(); }); test("historical single-day DMs and their threads expose dates without hover", async ({ diff --git a/tests/browser/message-overlays.spec.mjs b/tests/browser/message-overlays.spec.mjs index e0ac1bd9c..45169558c 100644 --- a/tests/browser/message-overlays.spec.mjs +++ b/tests/browser/message-overlays.spec.mjs @@ -70,10 +70,14 @@ test("video speed options escape the thread and restore focus after selection an }); await trigger.evaluate((button) => { const scroller = button.closest("[data-message-scroller]"); - scroller.scrollTop += - button.getBoundingClientRect().top - - scroller.getBoundingClientRect().top - - 40; + // WebKit truncates fractional scroll requests. Round the final position so + // fractional message heights still reach the same constrained-menu setup. + scroller.scrollTop = Math.round( + scroller.scrollTop + + button.getBoundingClientRect().top - + scroller.getBoundingClientRect().top - + 40, + ); }); await expect .poll(() =>