Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/design-system/check-type.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const SIZE_ROLES = [
"body-lg",
"body-sm",
"body",
"message",
"mono-lg",
"mono-sm",
"mono",
Expand Down
21 changes: 11 additions & 10 deletions src/features/messages/Messages.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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);
Expand Down Expand Up @@ -1808,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;
}
Expand Down
4 changes: 4 additions & 0 deletions src/shared/design-system/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions src/shared/design-system/styles/typography.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
12 changes: 11 additions & 1 deletion src/shared/design-system/tokens/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
{
Expand All @@ -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",
Expand Down
25 changes: 25 additions & 0 deletions tests/browser/message-actions.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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) => {
Expand All @@ -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 ({
Expand Down
12 changes: 8 additions & 4 deletions tests/browser/message-overlays.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(() =>
Expand Down
Loading