mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
## Problem With macOS in dark mode (the default for fresh profiles is to follow the system scheme), the onboarding "What's an identity key?" help dialog renders its title in near-white on the always-white textured card, making it unreadable. The body paragraphs stay readable because they use the fixed olive `--buzz-onboarding-backup-ink`; only the `text-foreground` title (and the close button's hover color) flip with the theme. ## Cause The dialog's `DialogContent` carries `buzz-onboarding-neutral-theme` but is portaled outside the `buzz-startup-shell` subtree, so in dark mode it matches `.dark .buzz-onboarding-neutral-theme:not(.buzz-startup-shell)` (`components.css`), which flips `--foreground` to `0 0% 98%`. The textured powder card (`buzz-card-textured`) has no dark variant — it is baked light — so the near-white title disappears against it. ## Fix One attribute: pin the dialog to the light neutral theme with `data-system-color-scheme="light"`. This is the established pattern for always-light onboarding dialogs (`HostedCommunityOnboarding.tsx`, the `CommunityOnboardingFlow.tsx` avatar dialog), and the pinned-light CSS rule already exists and out-specifies the dark-mode flip. No new CSS. ## Testing - Added a dark-mode regression test to `tests/e2e/identity-key-help.spec.ts` (already registered in the Playwright smoke project): emulates `prefers-color-scheme: dark`, opens the dialog, and asserts the title resolves to the pinned light-neutral ink `rgb(23, 23, 23)`. Before the fix it rendered `rgb(250, 250, 250)`. - Manual repro: macOS appearance set to Dark → fresh profile → machine onboarding → click "What's an identity key?". Before/after screenshots are in the comment below. Signed-off-by: Michael Pfister <pfista@gmail.com>
94 lines
3.2 KiB
TypeScript
94 lines
3.2 KiB
TypeScript
import { expect, test } from "@playwright/test";
|
||
|
||
import { waitForAnimations } from "../helpers/animations";
|
||
import { installMockBridge } from "../helpers/bridge";
|
||
|
||
const HELP_SEEN_KEY = "buzz.machine-onboarding.identity-key-help-seen.v1";
|
||
|
||
test("identity key help explains the first-run choice", async ({ page }) => {
|
||
await installMockBridge(page, undefined, {
|
||
skipCommunitySeed: true,
|
||
skipOnboardingSeed: true,
|
||
});
|
||
await page.goto("/");
|
||
|
||
const trigger = page.getByTestId("identity-key-help-trigger");
|
||
// No initial opacity-0 assertion: on a slow runner the 2s reveal timer can
|
||
// fire before the first assertion runs, failing the test for the wrong
|
||
// reason. The reveal + persistence assertions below carry the coverage.
|
||
await expect(trigger).toHaveCSS("opacity", "1", { timeout: 5000 });
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate((key) => localStorage.getItem(key), HELP_SEEN_KEY),
|
||
)
|
||
.toBe("true");
|
||
|
||
await page.setViewportSize({ width: 720, height: 620 });
|
||
await trigger.click();
|
||
|
||
const dialog = page.getByTestId("identity-key-help-dialog");
|
||
await expect(dialog).toBeVisible();
|
||
await waitForAnimations(page);
|
||
await expect(
|
||
dialog.getByRole("heading", { name: "What’s an identity key?" }),
|
||
).toBeVisible();
|
||
await expect(dialog).toHaveClass(/shadow-none/);
|
||
await expect(page.getByTestId("dialog-overlay")).toHaveCSS(
|
||
"background-color",
|
||
"rgba(0, 0, 0, 0)",
|
||
);
|
||
const dialogWrapper = dialog.locator("..");
|
||
await expect(dialogWrapper).toHaveCSS("overflow-x", "hidden");
|
||
const dialogBounds = await dialog.boundingBox();
|
||
expect(dialogBounds).not.toBeNull();
|
||
expect(dialogBounds?.x).toBeGreaterThanOrEqual(0);
|
||
expect(
|
||
(dialogBounds?.x ?? 0) + (dialogBounds?.width ?? 0),
|
||
).toBeLessThanOrEqual(720);
|
||
|
||
await page.keyboard.press("Escape");
|
||
await expect(dialog).not.toBeVisible();
|
||
await expect(trigger).toHaveCSS("opacity", "1");
|
||
|
||
await page.reload();
|
||
await expect(page.getByTestId("identity-key-help-trigger")).toHaveCSS(
|
||
"opacity",
|
||
"1",
|
||
);
|
||
});
|
||
|
||
test("identity key help stays readable when the app resolves dark mode", async ({
|
||
page,
|
||
}) => {
|
||
await page.emulateMedia({ colorScheme: "dark" });
|
||
await installMockBridge(page, undefined, {
|
||
skipCommunitySeed: true,
|
||
skipOnboardingSeed: true,
|
||
});
|
||
await page.goto("/");
|
||
|
||
// Fresh profiles follow the system scheme, so the emulated dark scheme is
|
||
// the first-run repro: the app resolves the dark theme while onboarding.
|
||
await expect
|
||
.poll(() =>
|
||
page.evaluate(() => document.documentElement.classList.contains("dark")),
|
||
)
|
||
.toBe(true);
|
||
|
||
const trigger = page.getByTestId("identity-key-help-trigger");
|
||
await expect(trigger).toHaveCSS("opacity", "1", { timeout: 5000 });
|
||
await trigger.click();
|
||
|
||
const dialog = page.getByTestId("identity-key-help-dialog");
|
||
await expect(dialog).toBeVisible();
|
||
await waitForAnimations(page);
|
||
|
||
// The textured powder card is baked light in both themes, so the dialog pins
|
||
// the neutral onboarding theme to its light variant. Without the pin the
|
||
// dark theme flips --foreground to near-white and the title disappears
|
||
// against the white card.
|
||
await expect(
|
||
dialog.getByRole("heading", { name: "What’s an identity key?" }),
|
||
).toHaveCSS("color", "rgb(23, 23, 23)");
|
||
});
|