diff --git a/desktop/src/shared/lib/conversationDensityPreference.test.mjs b/desktop/src/shared/lib/conversationDensityPreference.test.mjs index 4a51b0f11..86dc7757e 100644 --- a/desktop/src/shared/lib/conversationDensityPreference.test.mjs +++ b/desktop/src/shared/lib/conversationDensityPreference.test.mjs @@ -3,7 +3,11 @@ import test from "node:test"; 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)), @@ -57,3 +61,12 @@ test("initializes from the persisted conversation density", () => { assert.equal(preference.getConversationDensity(), "spacious"); assert.equal(attributes.get("data-conversation-density"), "spacious"); }); + +test("applies conversation density changes from another window", () => { + values.set(preference.CONVERSATION_DENSITY_STORAGE_KEY, "compact"); + windowListeners.get("storage")({ + key: preference.CONVERSATION_DENSITY_STORAGE_KEY, + }); + assert.equal(preference.getConversationDensity(), "compact"); + assert.equal(attributes.get("data-conversation-density"), "compact"); +}); diff --git a/desktop/src/shared/lib/conversationDensityPreference.ts b/desktop/src/shared/lib/conversationDensityPreference.ts index 302bce565..4395fcf34 100644 --- a/desktop/src/shared/lib/conversationDensityPreference.ts +++ b/desktop/src/shared/lib/conversationDensityPreference.ts @@ -9,6 +9,7 @@ export const DEFAULT_CONVERSATION_DENSITY: ConversationDensity = "comfortable"; const listeners = new Set<() => void>(); let conversationDensity: ConversationDensity = DEFAULT_CONVERSATION_DENSITY; +let listeningForStorageChanges = false; export function parseConversationDensity( value: string | null | undefined, @@ -39,8 +40,7 @@ function notifyListeners(): void { for (const listener of listeners) listener(); } -/** Apply the persisted preference before React renders to avoid a layout jump. */ -export function initializeConversationDensityPreference(): void { +function applyStoredConversationDensity(): void { const nextDensity = readStoredConversationDensity(); const changed = nextDensity !== conversationDensity; conversationDensity = nextDensity; @@ -48,6 +48,23 @@ export function initializeConversationDensityPreference(): void { if (changed) notifyListeners(); } +function listenForStorageChanges(): void { + if (listeningForStorageChanges || !globalThis.window?.addEventListener) + return; + globalThis.window.addEventListener("storage", (event) => { + if (event.key === CONVERSATION_DENSITY_STORAGE_KEY) { + applyStoredConversationDensity(); + } + }); + listeningForStorageChanges = true; +} + +/** Apply the persisted preference before React renders to avoid a layout jump. */ +export function initializeConversationDensityPreference(): void { + applyStoredConversationDensity(); + listenForStorageChanges(); +} + function subscribe(listener: () => void): () => void { listeners.add(listener); return () => listeners.delete(listener); diff --git a/desktop/src/shared/lib/fontSizePreference.test.mjs b/desktop/src/shared/lib/fontSizePreference.test.mjs index a9e7cd169..9f75f5c6e 100644 --- a/desktop/src/shared/lib/fontSizePreference.test.mjs +++ b/desktop/src/shared/lib/fontSizePreference.test.mjs @@ -4,10 +4,14 @@ import test from "node:test"; const values = new Map(); const attributes = new Map(); const styleValues = new Map(); +const windowListeners = new Map(); const style = { setProperty: (name, value) => styleValues.set(name, value), }; +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)), @@ -60,3 +64,11 @@ test("initializes from the stored font size", () => { assert.equal(attributes.get("data-font-size"), "larger"); assert.equal(styleValues.get("--buzz-type-rem"), "18.285714px"); }); + +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"); + assert.equal(styleValues.get("--buzz-type-rem"), "16px"); +}); diff --git a/desktop/src/shared/lib/fontSizePreference.ts b/desktop/src/shared/lib/fontSizePreference.ts index 9ccde63d0..22b0d1644 100644 --- a/desktop/src/shared/lib/fontSizePreference.ts +++ b/desktop/src/shared/lib/fontSizePreference.ts @@ -21,6 +21,7 @@ const TYPE_REM_PROPERTY = "--buzz-type-rem"; const listeners = new Set<() => void>(); let fontSize: FontSize = DEFAULT_FONT_SIZE; let textZoomFactor = 1; +let listeningForStorageChanges = false; export function parseFontSize(value: string | null | undefined): FontSize { return value === "smaller" || value === "default" || value === "larger" @@ -54,8 +55,7 @@ function notifyListeners(): void { for (const listener of listeners) listener(); } -/** Apply the persisted preference before React renders to avoid a layout jump. */ -export function initializeFontSizePreference(): void { +function applyStoredFontSize(): void { const nextSize = readStoredFontSize(); const changed = nextSize !== fontSize; fontSize = nextSize; @@ -63,6 +63,21 @@ export function initializeFontSizePreference(): void { if (changed) notifyListeners(); } +function listenForStorageChanges(): void { + if (listeningForStorageChanges || !globalThis.window?.addEventListener) + return; + globalThis.window.addEventListener("storage", (event) => { + if (event.key === FONT_SIZE_STORAGE_KEY) applyStoredFontSize(); + }); + listeningForStorageChanges = true; +} + +/** Apply the persisted preference before React renders to avoid a layout jump. */ +export function initializeFontSizePreference(): void { + applyStoredFontSize(); + listenForStorageChanges(); +} + /** Combine Cmd +/- zoom with the selected app-wide type scale. */ export function applyTextZoomFactor(zoomFactor: number): void { if (!Number.isFinite(zoomFactor) || zoomFactor <= 0) return;