mirror of
https://github.com/block/buzz.git
synced 2026-11-04 08:21:48 +00:00
## Summary Follow-up to #5644. Cmd +/- had become a text-only zoom: type scaled while rem-based padding, gaps, widths, avatars, and controls stayed frozen, which produced cramped layouts (see [#buzz-frontend thread](buzz://message?channel=a410ffde-c61f-416a-96e0-c296b5f5ecc9&id=1a758115cf07b00c097f6e988553908c045165325a57637519cfa7ed9c9accec)). Root cause: #5644 introduced a virtual typography rem so the **Font size** preference could change text without moving layout — a good decoupling — but it also routed **Cmd +/- zoom** through that same px-valued token and pinned the real root at 16px. One decision ("freeze layout") was applied to two dials that shouldn't share it. This PR gives each dial one owner and lets CSS compose them: | Control | Changes | How | |---|---|---| | **Cmd +/- zoom** | Everything — true zoom | Scales the real `<html>` font-size again (`useWebviewZoomShortcuts`) | | **Font size preference** | Text only | Sets `data-font-size`; `typography.css` maps it to a unitless `--buzz-type-scale`, mirroring how density already works | `--buzz-type-rem` becomes `calc(1rem * var(--buzz-type-scale))` — rem-relative, so it rides on zoom automatically. Resulting text px = `16 × zoom × scale × token-ratio`. The 13 / 14 / 15px conversation contract is unchanged at default zoom. Density and the type ramp from #5644 are untouched. The preference module no longer does px math or knows about zoom; the zoom hook no longer imports the preference module. Net deletion in production code. ## Validation - `pnpm test` — 5,308 desktop unit tests - `pnpm check:px-text`, `tsc --noEmit`, biome - Playwright: `top-chrome-zoom-clearance.spec.ts` (native-chrome clearance stays fixed under root zoom), `inbox-refactor-screenshots.spec.ts` (zoomed row padding now asserts `4.4px` instead of the frozen `4px`), and both `profile.spec.ts` zoom tests (composed zoom × preference, cross-window storage reset) - Before/after screenshots at 140% zoom in the comment below --------- Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com>
113 lines
4.0 KiB
JavaScript
113 lines
4.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import { readFileSync } from "node:fs";
|
|
|
|
import config from "../../../tailwind.config.js";
|
|
|
|
const typographyCss = readFileSync(
|
|
new URL("../styles/globals/typography.css", import.meta.url),
|
|
"utf8",
|
|
);
|
|
|
|
const values = new Map();
|
|
const attributes = new Map();
|
|
const windowListeners = new Map();
|
|
|
|
globalThis.window = {
|
|
addEventListener: (type, listener) => windowListeners.set(type, listener),
|
|
};
|
|
globalThis.localStorage = {
|
|
getItem: (key) => values.get(key) ?? null,
|
|
setItem: (key, value) => values.set(key, String(value)),
|
|
};
|
|
globalThis.document = {
|
|
documentElement: {
|
|
setAttribute: (name, value) => attributes.set(name, value),
|
|
},
|
|
};
|
|
|
|
const preference = await import("./fontSizePreference.ts");
|
|
|
|
test("scales fixed line-height utilities with the typography rem", () => {
|
|
assert.deepEqual(config.theme.extend.lineHeight, {
|
|
3: "calc(var(--buzz-type-rem) * 0.75)",
|
|
4: "var(--buzz-type-rem)",
|
|
5: "calc(var(--buzz-type-rem) * 1.25)",
|
|
6: "calc(var(--buzz-type-rem) * 1.5)",
|
|
7: "calc(var(--buzz-type-rem) * 1.75)",
|
|
8: "calc(var(--buzz-type-rem) * 2)",
|
|
"message-author": "var(--conversation-author-line-height)",
|
|
});
|
|
});
|
|
|
|
test("derives the typography rem from the real root so zoom scales layout too", () => {
|
|
// Cmd +/- scales the root font-size; the type rem must stay rem-relative
|
|
// (never an absolute px) or text would zoom while containers froze.
|
|
assert.match(
|
|
typographyCss,
|
|
/--buzz-type-rem:\s*calc\(1rem \* var\(--buzz-type-scale\)\);/,
|
|
);
|
|
assert.doesNotMatch(typographyCss, /--buzz-type-rem:\s*[\d.]+px/);
|
|
});
|
|
|
|
test("maps the font size attribute to the 13 / 14 / 15px type contract", () => {
|
|
assert.match(typographyCss, /:root\s*\{[^}]*--buzz-type-scale:\s*1;/s);
|
|
assert.match(
|
|
typographyCss,
|
|
/:root\[data-font-size="smaller"\]\s*\{\s*--buzz-type-scale:\s*calc\(13 \/ 14\);/,
|
|
);
|
|
assert.match(
|
|
typographyCss,
|
|
/:root\[data-font-size="larger"\]\s*\{\s*--buzz-type-scale:\s*calc\(15 \/ 14\);/,
|
|
);
|
|
});
|
|
|
|
test("defaults invalid and missing font sizes to default", () => {
|
|
assert.equal(preference.parseFontSize(null), "default");
|
|
assert.equal(preference.parseFontSize("medium"), "default");
|
|
assert.equal(preference.parseFontSize("smaller"), "smaller");
|
|
assert.equal(preference.parseFontSize("default"), "default");
|
|
assert.equal(preference.parseFontSize("larger"), "larger");
|
|
});
|
|
|
|
test("persists and applies the selected font size across the app", () => {
|
|
preference.setFontSize("smaller");
|
|
assert.equal(preference.getFontSize(), "smaller");
|
|
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
|
|
assert.equal(attributes.get("data-font-size"), "smaller");
|
|
});
|
|
|
|
test("previews a font size without changing the saved preference", () => {
|
|
preference.setFontSize("smaller");
|
|
preference.previewFontSize("larger");
|
|
assert.equal(preference.getFontSize(), "smaller");
|
|
assert.equal(values.get(preference.FONT_SIZE_STORAGE_KEY), "smaller");
|
|
assert.equal(attributes.get("data-font-size"), "larger");
|
|
|
|
preference.previewFontSize(null);
|
|
assert.equal(attributes.get("data-font-size"), "smaller");
|
|
});
|
|
|
|
test("initializes from the stored font size", () => {
|
|
values.set(preference.FONT_SIZE_STORAGE_KEY, "larger");
|
|
preference.initializeFontSizePreference();
|
|
assert.equal(preference.getFontSize(), "larger");
|
|
assert.equal(attributes.get("data-font-size"), "larger");
|
|
});
|
|
|
|
test("applies font size changes from another window", () => {
|
|
values.set(preference.FONT_SIZE_STORAGE_KEY, "smaller");
|
|
windowListeners.get("storage")({ key: preference.FONT_SIZE_STORAGE_KEY });
|
|
assert.equal(preference.getFontSize(), "smaller");
|
|
assert.equal(attributes.get("data-font-size"), "smaller");
|
|
});
|
|
|
|
test("returns to the default when another window clears storage", () => {
|
|
preference.setFontSize("larger");
|
|
values.clear();
|
|
windowListeners.get("storage")({ key: null });
|
|
assert.equal(preference.getFontSize(), "default");
|
|
assert.equal(attributes.get("data-font-size"), "default");
|
|
});
|