Files
buzz/desktop/src/main.tsx
klopez4212 7e2651791d Add font size and conversation density preferences (#5644)
## Summary

- Add separate **Font size** and **Conversation density** controls in
Appearance.
- Use a 13 / 14 / 15px conversation text ramp for Smaller / Default /
Larger while scaling interface typography through a shared virtual-rem
system.
- Keep layout geometry fixed while typography changes, and compose Cmd
+/- text zoom on top of the selected preference.
- Let Compact, Comfy, and Spacious control conversation row spacing plus
paragraph and list rhythm in Markdown content across Buzz.
- Preview font size and density together with click-and-drag comparison;
interrupted scrubbing restores the saved preference.
- Synchronize preferences across open Buzz windows, including full
local-storage clears during sign-out or dev reset.
- Promote the segmented control to shared UI and make Settings rows
adapt to their card width.

## Validation

- `just ci`
- 4,965+ desktop unit tests across the final merged branch
- 1,465 mobile tests through the full repository gate
- Focused Playwright coverage for font-size/density independence, drag
preview cancellation, Inbox geometry, thread rendering, keyboard zoom,
and cross-surface typography
- Fresh E2E build against the final merged branch

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: morgmart <98432065+morgmart@users.noreply.github.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: morgmart <98432065+morgmart@users.noreply.github.com>
2026-08-18 15:24:09 -07:00

138 lines
4.9 KiB
TypeScript

import React from "react";
import ReactDOM from "react-dom/client";
import { App } from "@/app/App";
import { RootErrorBoundary } from "@/app/RootErrorBoundary";
import { NostrBindConsentDialog } from "@/features/profile/ui/NostrBindConsentDialog";
import "@fontsource-variable/inter/opsz.css";
import "@fontsource-variable/inter/opsz-italic.css";
import "@fontsource/jetbrains-mono/400.css";
import "@fontsource/jetbrains-mono/700.css";
import "@/shared/styles/globals.css";
import { UpdaterProvider } from "@/features/settings/hooks/UpdaterProvider";
import { migrateLegacyCommunityStorageBeforeRender } from "@/features/communities/legacyCommunityStorage";
import { CommunitiesProvider } from "@/features/communities/useCommunities";
import { huddleWindowChannelId } from "@/features/huddle/lib/huddleWindow";
import { CommunityOnboardingProvider } from "@/features/onboarding/communityOnboarding";
import { ThemeProvider } from "@/shared/theme/ThemeProvider";
import { EmojiBurstProvider } from "@/shared/ui/EmojiBurstProvider";
import { PoofBurstProvider } from "@/shared/ui/PoofBurstProvider";
import { Toaster } from "@/shared/ui/sonner";
import { TooltipProvider } from "@/shared/ui/tooltip";
import { recoverLocalStorageQuotaOnStartup } from "@/shared/lib/localStorageQuota";
import { startLocalStorageSweep } from "@/shared/lib/localStorageSweep";
import { initializeConversationDensityPreference } from "@/shared/lib/conversationDensityPreference";
import { initializeFontSizePreference } from "@/shared/lib/fontSizePreference";
type E2eWindow = Window & {
__BUZZ_E2E__?: unknown;
};
const E2E_DEFAULT_PUBKEY = "deadbeef".repeat(8);
const E2E_COMMUNITY_ID = "e2e-default-community";
const ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX = "buzz-onboarding-complete.v1:";
const DEV_STATE_RESET_PARAM = "resetDevState";
function resetDevWebviewStateFromUrl() {
if (!import.meta.env.DEV) {
return;
}
const url = new URL(window.location.href);
if (url.searchParams.get(DEV_STATE_RESET_PARAM) !== "1") {
return;
}
// WebKit groups every Buzz binary under one disk directory, but storage is
// isolated by origin. Clearing here resets only this dev server's origin;
// deleting the shared WebKit directory would also destroy installed-app state.
window.localStorage.clear();
window.sessionStorage.clear();
url.searchParams.delete(DEV_STATE_RESET_PARAM);
window.history.replaceState(window.history.state, "", url);
}
function configureDevE2eBridgeFromUrl() {
if (!import.meta.env.DEV) {
return;
}
const url = new URL(window.location.href);
if (url.searchParams.get("e2e") !== "mock") {
return;
}
const e2eWindow = window as E2eWindow;
e2eWindow.__BUZZ_E2E__ ??= { mode: "mock" };
const community = {
addedAt: new Date().toISOString(),
id: E2E_COMMUNITY_ID,
name: "E2E Test",
relayUrl: "ws://localhost:3000",
};
window.localStorage.setItem("buzz-communities", JSON.stringify([community]));
window.localStorage.setItem("buzz-active-community-id", E2E_COMMUNITY_ID);
window.localStorage.setItem(
`${ONBOARDING_COMPLETION_STORAGE_KEY_PREFIX}${E2E_DEFAULT_PUBKEY}`,
"true",
);
}
function renderApp() {
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
{/* block/buzz#5078 — catch any uncaught render error so a WebKit
SecurityError from localStorage can't blank the whole window. */}
<RootErrorBoundary>
<CommunitiesProvider>
<CommunityOnboardingProvider
enabled={huddleWindowChannelId() === null}
>
<ThemeProvider defaultTheme="buzz">
<TooltipProvider>
<EmojiBurstProvider>
<PoofBurstProvider>
<UpdaterProvider>
<App />
<NostrBindConsentDialog />
</UpdaterProvider>
<Toaster />
</PoofBurstProvider>
</EmojiBurstProvider>
</TooltipProvider>
</ThemeProvider>
</CommunityOnboardingProvider>
</CommunitiesProvider>
</RootErrorBoundary>
</React.StrictMode>,
);
}
async function installE2eBridgeIfConfigured() {
// The mock bridge is compiled only into dev and explicit E2E builds. A
// pre-bootstrap global alone must never activate mock IPC in production.
if (
!(import.meta.env.DEV || import.meta.env.MODE === "e2e") ||
!(window as E2eWindow).__BUZZ_E2E__
) {
return;
}
const { maybeInstallE2eTauriMocks } = await import("@/testing/e2eBridge");
maybeInstallE2eTauriMocks();
}
async function bootstrap() {
resetDevWebviewStateFromUrl();
configureDevE2eBridgeFromUrl();
recoverLocalStorageQuotaOnStartup();
initializeConversationDensityPreference();
initializeFontSizePreference();
startLocalStorageSweep();
await installE2eBridgeIfConfigured();
await migrateLegacyCommunityStorageBeforeRender();
renderApp();
}
void bootstrap();