From ad413c70a7738d556d44401554d9df79d06d4ca1 Mon Sep 17 00:00:00 2001 From: Adam Higgins Date: Thu, 22 Oct 2026 18:53:49 +0100 Subject: [PATCH 1/2] feat(desktop): configure quick message appearance independently --- .../settings/AppearanceSettingsPanel.tsx | 3 + .../QuickMessageAppearanceSettings.tsx | 227 ++++++++++++++++++ .../src/components/settings/settingsSearch.ts | 6 + apps/web/src/hooks/useTheme.ts | 57 ++++- apps/web/src/index.css | 41 ++++ apps/web/src/quickMessageAppearance.test.ts | 123 ++++++++++ apps/web/src/quickMessageAppearance.ts | 47 ++++ apps/web/src/routes/__root.tsx | 26 +- docs/fork-differences.md | 7 +- docs/user/keybindings.md | 5 + packages/contracts/src/settings.test.ts | 23 ++ packages/contracts/src/settings.ts | 18 ++ 12 files changed, 573 insertions(+), 10 deletions(-) create mode 100644 apps/web/src/components/settings/QuickMessageAppearanceSettings.tsx create mode 100644 apps/web/src/quickMessageAppearance.test.ts create mode 100644 apps/web/src/quickMessageAppearance.ts diff --git a/apps/web/src/components/settings/AppearanceSettingsPanel.tsx b/apps/web/src/components/settings/AppearanceSettingsPanel.tsx index a13bc93bb9ce8..24871f86a6f10 100644 --- a/apps/web/src/components/settings/AppearanceSettingsPanel.tsx +++ b/apps/web/src/components/settings/AppearanceSettingsPanel.tsx @@ -1,3 +1,4 @@ +import { QuickMessageAppearanceSettings } from "./QuickMessageAppearanceSettings"; import type { CSSProperties } from "react"; import { useState } from "react"; import { @@ -103,6 +104,8 @@ export function AppearanceSettingsPanel() { + + (null); + if (!window.desktopBridge?.quickMessage) return null; + + const save = (patch: Parameters[0]) => { + setError(null); + void updateClientSettings(patch).catch(() => + setError("Could not save quick message appearance."), + ); + }; + const themes = [...BUILT_IN_THEMES, ...environmentThemes, ...customThemes]; + const resolved = resolveQuickMessageAppearance( + settings, + { + theme: app.theme, + appearance: app.resolvedTheme, + halves: app.themeHalves, + }, + window.matchMedia("(prefers-color-scheme: dark)").matches, + ); + const definition = getThemeDefinition(resolved.theme); + const colors = + (definition && getThemeColorsForMode(definition, resolved.appearance)) ?? + getStandardThemeColors(resolved.appearance); + const previewStyle = { + "--quick-preview-surface": + definition || resolved.appearance === "dark" ? colors.surfaceRaised : colors.surface, + "--quick-preview-text": colors.text, + "--quick-preview-muted": colors.secondaryLabel, + "--quick-preview-border": colors.border, + "--quick-preview-action": colors.messageAction, + "--quick-preview-action-text": colors.messageActionForeground, + "--quick-preview-canvas": colors.canvas, + "--appearance-contrast-base": `${Math.min(resolved.contrast, 100)}%`, + "--appearance-contrast-boost": `${Math.max(resolved.contrast - 100, 0)}%`, + "--appearance-contrast-border-boost": `${Math.max(resolved.contrast - 100, 0) / 4}%`, + "--appearance-contrast-target": resolved.appearance === "dark" ? "white" : "black", + } as CSSProperties; + const themeItems = [ + { value: "light", label: "T3 Code" }, + ...themes.map((theme) => ({ value: theme.id, label: theme.label })), + ...(!["light", "dark", "system", ...themes.map((theme) => theme.id)].includes( + settings.quickMessageTheme, + ) + ? [{ value: settings.quickMessageTheme, label: "Unavailable theme (following app)" }] + : []), + ]; + return ( + + save({ quickMessageThemeSource: "app" })} + /> + ) : undefined + } + control={ + + } + /> + {settings.quickMessageThemeSource === "theme" && ( + value && save({ quickMessageTheme: value })} + > + + + + + {themeItems.map((theme) => ( + + {theme.label} + + ))} + + + } + /> + )} + {settings.quickMessageThemeSource !== "app" && ( + value && save({ quickMessageAppearance: value })} + > + + {APPEARANCES[settings.quickMessageAppearance]} + + + {Object.entries(APPEARANCES).map(([value, label]) => ( + + {label} + + ))} + + + } + /> + )} + {settings.quickMessageThemeSource === "adapt" && ( + + + +{settings.quickMessageExtraContrast}% + + save({ quickMessageExtraContrast: Number(e.currentTarget.value) })} + style={ + { + "--settings-slider-progress": `${settings.quickMessageExtraContrast}%`, + "--settings-slider-fill-offset": `${0.5 - settings.quickMessageExtraContrast / 100}rem`, + } as CSSProperties + } + /> + + } + /> + )} +
+
+

Preview over another app

+
+ +
+
+
+
+
+ t3code⌄ + ··· +
+

Ask anything…

+
+ Model ⌄  Medium ⌄ + + Create worktree + +
+
+
+
+
+ ); +} diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index 543d6550763b8..a6600b02a37b5 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -216,6 +216,12 @@ export const SETTINGS_SEARCH_ITEMS = [ searchTerms: ["appearance colors palette custom import"], targetId: "appearance", }, + { + id: "quick-message-appearance", + title: "Quick message appearance", + to: "/settings/appearance", + searchTerms: ["floating composer theme contrast independent light dark"], + }, { // Prefixed because the slider control already owns the `appearance-contrast` id. id: "setting-appearance-contrast", diff --git a/apps/web/src/hooks/useTheme.ts b/apps/web/src/hooks/useTheme.ts index a8f414d0849a8..c63caea9ca608 100644 --- a/apps/web/src/hooks/useTheme.ts +++ b/apps/web/src/hooks/useTheme.ts @@ -25,6 +25,25 @@ import { type ThemePreferenceMode, } from "../themePalette"; +import { isQuickMessageWindow } from "../components/quickMessage/quickMessageWindow"; +import { + resolveQuickMessageAppearance, + type QuickMessageThemeSettings, +} from "../quickMessageAppearance"; +import { applyAppearanceContrast } from "../appearanceContrast"; + +let quickMessageThemeSettings: QuickMessageThemeSettings | null = null; + +export function configureQuickMessageAppearance(settings: QuickMessageThemeSettings | null) { + const previous = quickMessageThemeSettings; + quickMessageThemeSettings = settings; + lastAppliedTheme = null; + applyTheme(getStored(), { suppressTransitions: true }); + if (!settings && previous) + applyAppearanceContrast(document.documentElement, previous.appearanceContrast); + emitChange(); +} + type Theme = ThemePreference; type ThemeSnapshot = { theme: Theme; @@ -340,8 +359,23 @@ function applyTheme(theme: Theme, { suppressTransitions = false, preservePreview appearanceMode, themeHalves, ); - applyThemePalette(resolveThemeHalf(theme, themeHalves, resolvedAppearance), resolvedAppearance); - document.documentElement.classList.toggle("dark", resolvedAppearance === "dark"); + const quick = quickMessageThemeSettings + ? resolveQuickMessageAppearance( + quickMessageThemeSettings, + { theme, appearance: resolvedAppearance, halves: themeHalves }, + getSystemDark(), + ) + : null; + const appearance = quick?.appearance ?? resolvedAppearance; + applyThemePalette(quick?.theme ?? resolveThemeHalf(theme, themeHalves, appearance), appearance); + document.documentElement.classList.toggle("dark", appearance === "dark"); + if (quick) { + document.documentElement.dataset.quickMessageThemeSource = + quickMessageThemeSettings?.quickMessageThemeSource; + applyAppearanceContrast(document.documentElement, quick.contrast); + } else { + delete document.documentElement.dataset.quickMessageThemeSource; + } lastAppliedTheme = { theme, systemDark, followSystem, appearanceMode, themeHalves }; syncBrowserChromeTheme(); syncDesktopTheme(theme, followSystem, appearanceMode); @@ -373,7 +407,7 @@ function syncDesktopTheme( followSystem?: boolean, appearanceMode?: ThemePreferenceMode, ) { - if (typeof window === "undefined") return; + if (typeof window === "undefined" || isQuickMessageWindow) return; const bridge = window.desktopBridge; const halves = readStoredThemeHalves(); const desktopTheme = resolveDesktopTheme(theme, followSystem, appearanceMode, halves); @@ -411,16 +445,23 @@ function getSnapshot(): ThemeSnapshot { const theme = getStored(); const appearanceMode = readAppearanceModePreference(theme); const followSystem = appearanceMode === "system"; - const systemDark = followSystem ? getSystemDark() : false; + const systemDark = getSystemDark(); const themeHalves = readStoredThemeHalves(); - const resolvedTheme = resolveThemeAppearance( + const appAppearance = resolveThemeAppearance( theme, systemDark, followSystem, appearanceMode, themeHalves, ); + const resolvedTheme = quickMessageThemeSettings + ? resolveQuickMessageAppearance( + quickMessageThemeSettings, + { theme, appearance: appAppearance, halves: themeHalves }, + getSystemDark(), + ).appearance + : appAppearance; if ( lastSnapshot && lastSnapshot.theme === theme && @@ -443,7 +484,11 @@ function getServerSnapshot() { function handleSystemAppearanceChange() { const storedTheme = getStored(); - if (readAppearanceModePreference(storedTheme) === "system") { + if ( + readAppearanceModePreference(storedTheme) === "system" || + quickMessageThemeSettings?.quickMessageAppearance === "system" + ) { + lastAppliedTheme = null; applyTheme(storedTheme, { suppressTransitions: true }); } emitChange(); diff --git a/apps/web/src/index.css b/apps/web/src/index.css index a395cd38581cb..67ee6b560a8c4 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2612,3 +2612,44 @@ code { } } } + +.quick-message-theme-preview { + --quick-preview-foreground: color-mix( + in oklab, + color-mix( + in oklab, + var(--quick-preview-text) var(--appearance-contrast-base), + var(--quick-preview-canvas) + ), + var(--appearance-contrast-target) var(--appearance-contrast-boost) + ); + --quick-preview-secondary: color-mix( + in oklab, + color-mix( + in oklab, + var(--quick-preview-muted) var(--appearance-contrast-base), + var(--quick-preview-canvas) + ), + var(--appearance-contrast-target) var(--appearance-contrast-boost) + ); +} +.quick-message-theme-preview-composer { + background: var(--quick-preview-surface); + color: var(--quick-preview-secondary); + border-color: color-mix( + in srgb, + color-mix(in srgb, var(--quick-preview-border) var(--appearance-contrast-base), transparent), + var(--quick-preview-text) var(--appearance-contrast-border-boost) + ); +} +.quick-message-theme-preview-prompt { + color: var(--quick-preview-foreground); +} +.quick-message-theme-preview-action { + background: var(--quick-preview-action); + color: var(--quick-preview-action-text); +} + +html[data-quick-message-theme-source="adapt"] [data-slot="composer-shell"] { + --chat-composer-outline: var(--contrast-border); +} diff --git a/apps/web/src/quickMessageAppearance.test.ts b/apps/web/src/quickMessageAppearance.test.ts new file mode 100644 index 0000000000000..be3155fe5af44 --- /dev/null +++ b/apps/web/src/quickMessageAppearance.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from "vite-plus/test"; +import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; +import { resolveQuickMessageAppearance } from "./quickMessageAppearance"; +import { + getDefaultThemeColors, + getEnvironmentThemes, + setEnvironmentThemes, + GROVE_THEME, +} from "./themePalette"; + +const app = { theme: "light", appearance: "light" as const, halves: null }; + +describe("quick message appearance", () => { + it("respects a single-mode theme when the other appearance is requested", () => { + const previous = getEnvironmentThemes(); + try { + setEnvironmentThemes([ + { + id: "quick-single-dark", + label: "Single dark", + appearance: "dark", + colors: getDefaultThemeColors("dark"), + }, + ]); + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "theme", + quickMessageTheme: "quick-single-dark", + quickMessageAppearance: "light", + }, + app, + false, + ), + ).toEqual({ theme: "quick-single-dark", appearance: "dark", contrast: 100 }); + } finally { + setEnvironmentThemes(previous); + } + }); + it("follows the app by default and ignores inactive overrides", () => { + expect( + resolveQuickMessageAppearance( + { ...DEFAULT_CLIENT_SETTINGS, quickMessageTheme: "grove", quickMessageAppearance: "dark" }, + app, + true, + ), + ).toEqual({ theme: "light", appearance: "light", contrast: 100 }); + }); + + it("adapts the app's palette and clamps extra contrast", () => { + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "adapt", + quickMessageAppearance: "dark", + appearanceContrast: 180, + }, + { ...app, theme: GROVE_THEME.id }, + false, + ), + ).toEqual({ theme: GROVE_THEME.id, appearance: "dark", contrast: 200 }); + }); + + it("selects the app's dark half when adapting to system appearance", () => { + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "adapt", + quickMessageAppearance: "system", + }, + { ...app, halves: { dark: GROVE_THEME.id } }, + true, + ), + ).toEqual({ theme: GROVE_THEME.id, appearance: "dark", contrast: 140 }); + }); + + it("keeps independent themes separate from the app's mixed halves", () => { + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "theme", + quickMessageTheme: GROVE_THEME.id, + quickMessageAppearance: "dark", + }, + { ...app, halves: { dark: "t3-chat" } }, + false, + ), + ).toEqual({ theme: GROVE_THEME.id, appearance: "dark", contrast: 100 }); + }); + + it("falls back to the app if an independent theme was removed", () => { + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "theme", + quickMessageTheme: "removed-theme", + }, + app, + true, + ), + ).toEqual({ theme: "light", appearance: "light", contrast: 100 }); + }); + + it("lets a stock theme use the explicitly selected appearance", () => { + expect( + resolveQuickMessageAppearance( + { + ...DEFAULT_CLIENT_SETTINGS, + quickMessageThemeSource: "theme", + quickMessageTheme: "dark", + quickMessageAppearance: "light", + }, + app, + false, + ).appearance, + ).toBe("light"); + }); +}); diff --git a/apps/web/src/quickMessageAppearance.ts b/apps/web/src/quickMessageAppearance.ts new file mode 100644 index 0000000000000..92c957916727d --- /dev/null +++ b/apps/web/src/quickMessageAppearance.ts @@ -0,0 +1,47 @@ +import { MAX_APPEARANCE_CONTRAST, type ClientSettings } from "@t3tools/contracts/settings"; +import { + isKnownThemePreference, + resolveThemeAppearance, + resolveThemeHalf, + type ThemeAppearance, + type ThemeHalves, +} from "./themePalette"; + +export type QuickMessageThemeSettings = Pick< + ClientSettings, + | "quickMessageThemeSource" + | "quickMessageTheme" + | "quickMessageAppearance" + | "quickMessageExtraContrast" + | "appearanceContrast" +>; + +export function resolveQuickMessageAppearance( + settings: QuickMessageThemeSettings, + app: { theme: string; appearance: ThemeAppearance; halves: ThemeHalves | null }, + systemDark: boolean, +) { + const independent = settings.quickMessageThemeSource === "theme"; + const theme = + independent && isKnownThemePreference(settings.quickMessageTheme) + ? settings.quickMessageTheme + : app.theme; + const halves = + independent && isKnownThemePreference(settings.quickMessageTheme) ? null : app.halves; + const mode = settings.quickMessageThemeSource === "app" ? "app" : settings.quickMessageAppearance; + const appearance = + mode === "app" + ? resolveThemeAppearance(theme, systemDark, false, app.appearance, halves) + : resolveThemeAppearance(theme, systemDark, mode === "system", mode, halves); + return { + theme: resolveThemeHalf(theme, halves, appearance), + appearance, + contrast: + settings.quickMessageThemeSource === "adapt" + ? Math.min( + MAX_APPEARANCE_CONTRAST, + settings.appearanceContrast + settings.quickMessageExtraContrast, + ) + : settings.appearanceContrast, + }; +} diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index dd06ea624ce9c..5a6d617baeb24 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -76,7 +76,7 @@ import { selectProjectGroupingSettings, } from "../logicalProject"; import { useUiStateStore } from "../uiStateStore"; -import { syncBrowserChromeTheme } from "../hooks/useTheme"; +import { configureQuickMessageAppearance, syncBrowserChromeTheme } from "../hooks/useTheme"; import { configureClientTracing } from "../observability/clientTracing"; import { resolveInitialServerAuthGateState } from "../environments/primary"; import { hasHostedPairingRequest, isHostedStaticApp } from "../hostedPairing"; @@ -179,7 +179,7 @@ function RootRouteView() { if (pathname === "/quick-message") { return ( <> - + {primaryEnvironmentAuthenticated ? : } @@ -309,6 +309,28 @@ function EnvironmentThemeSync() { return null; } +function QuickMessageAppearanceSync() { + useEnvironmentThemeSync(); + const settings = useClientSettings(); + useEffect(() => { + configureQuickMessageAppearance({ + quickMessageThemeSource: settings.quickMessageThemeSource, + quickMessageTheme: settings.quickMessageTheme, + quickMessageAppearance: settings.quickMessageAppearance, + quickMessageExtraContrast: settings.quickMessageExtraContrast, + appearanceContrast: settings.appearanceContrast, + }); + return () => configureQuickMessageAppearance(null); + }, [ + settings.quickMessageThemeSource, + settings.quickMessageTheme, + settings.quickMessageAppearance, + settings.quickMessageExtraContrast, + settings.appearanceContrast, + ]); + return null; +} + function ContrastAppearanceSync() { const appearanceContrast = useClientSettings((settings) => settings.appearanceContrast); const diffColorScheme = useClientSettings((settings) => settings.diffColorScheme); diff --git a/docs/fork-differences.md b/docs/fork-differences.md index a4a5d7f82afcf..ea8489a7329b3 100644 --- a/docs/fork-differences.md +++ b/docs/fork-differences.md @@ -2368,8 +2368,11 @@ Code: `packages/client-runtime/src/work-log/webTool.ts`, rendered on web by A system-wide shortcut in the desktop app opens a floating composer over any app and starts a new thread from it, like the ChatGPT desktop app's companion window. Turn it on and record the shortcut -in **Settings → General → Quick message**. The panel is a separate transparent window that follows -the app's theme and has the in-app composer's controls. The panel collects the message, files, +in **Settings → General → Quick message**. The panel is a separate transparent window with the +in-app composer's controls. **Settings → Appearance → Quick message** can follow the app theme, +adapt its palette with extra text and border contrast, or choose an independent theme. Adapted +and independent themes can use a separate light, dark, or system appearance; themes with only one +appearance keep that mode. A preview shows the result over light and dark backgrounds. The panel collects the message, files, project, model, account and modes. Choose **No project** in its project selector to start without a checkout on the selected environment. Its **⋯** and attach menu open their pickers in the panel itself, including **Model**, which names a model in the new message. The main window then opens a diff --git a/docs/user/keybindings.md b/docs/user/keybindings.md index 652d513d86c6e..f00112bf44089 100644 --- a/docs/user/keybindings.md +++ b/docs/user/keybindings.md @@ -211,6 +211,11 @@ request, a model, and the rest of the attach menu. Use **⋯** to start from a P Picks show above your message and are applied to the new thread when you send. On Linux the shortcut does not work in a Wayland session. +Use **Settings → Appearance → Quick message** to give the floating composer its own appearance. +Choose **Follow app**, **Adapt app theme** with extra contrast, or **Choose theme** from your theme +library. Adapted and independent themes can use light, dark, or system appearance where the theme +supports it. The preview lets you compare the composer over light and dark backgrounds. + ## Desktop quit shortcut Use `Cmd+Q` on macOS or `Ctrl+Q` on Windows and Linux. In the default **Hold** mode, diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts index b19aa3cf5e7ad..6d95a4094d0bb 100644 --- a/packages/contracts/src/settings.test.ts +++ b/packages/contracts/src/settings.test.ts @@ -628,3 +628,26 @@ describe("server model preferences", () => { expect(decodeServerSettingsPatch(input)).toEqual(input); }); }); + +describe("quick message appearance settings", () => { + it("preserves app appearance for existing installs and round-trips independent preferences", () => { + expect(decodeClientSettings({})).toMatchObject({ + quickMessageThemeSource: "app", + quickMessageAppearance: "app", + quickMessageExtraContrast: 40, + }); + const patch = { + quickMessageThemeSource: "theme", + quickMessageTheme: "grove", + quickMessageAppearance: "dark", + quickMessageExtraContrast: 65, + }; + expect(decodeClientSettingsPatch(patch)).toEqual(patch); + expect(decodeClientSettings(encodeClientSettings(decodeClientSettings(patch)))).toMatchObject( + patch, + ); + }); + it.each([-1, 101, 2.5])("rejects invalid extra contrast %s", (quickMessageExtraContrast) => { + expect(() => decodeClientSettingsPatch({ quickMessageExtraContrast })).toThrow(); + }); +}); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index 76a76a37c35e7..f81bd7c66be41 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -420,6 +420,10 @@ export type ModelPreferences = { readonly providerModelPreferences: typeof ProviderModelPreferences.Type; }; +const QuickMessageThemeSource = Schema.Literals(["app", "adapt", "theme"]); +const QuickMessageAppearance = Schema.Literals(["app", "system", "light", "dark"]); +const QuickMessageExtraContrast = Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 })); + export const ClientSettingsSchema = Schema.Struct({ notificationMode: NotificationMode.pipe( Schema.withDecodingDefault(Effect.succeed("off" as const)), @@ -629,6 +633,16 @@ export const ClientSettingsSchema = Schema.Struct({ ), snapShotFlash: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), snapShotAnimations: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))), + quickMessageThemeSource: QuickMessageThemeSource.pipe( + Schema.withDecodingDefault(Effect.succeed("app" as const)), + ), + quickMessageTheme: Schema.String.pipe(Schema.withDecodingDefault(Effect.succeed("light"))), + quickMessageAppearance: QuickMessageAppearance.pipe( + Schema.withDecodingDefault(Effect.succeed("app" as const)), + ), + quickMessageExtraContrast: QuickMessageExtraContrast.pipe( + Schema.withDecodingDefault(Effect.succeed(40)), + ), quickMessageEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), quickMessageShortcut: GlobalShortcutChord.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_QUICK_MESSAGE_SHORTCUT)), @@ -2274,6 +2288,10 @@ export const ClientSettingsPatch = Schema.Struct({ snapShotSound: Schema.optionalKey(SnapShotSound), snapShotFlash: Schema.optionalKey(Schema.Boolean), snapShotAnimations: Schema.optionalKey(Schema.Boolean), + quickMessageThemeSource: Schema.optionalKey(QuickMessageThemeSource), + quickMessageTheme: Schema.optionalKey(Schema.String), + quickMessageAppearance: Schema.optionalKey(QuickMessageAppearance), + quickMessageExtraContrast: Schema.optionalKey(QuickMessageExtraContrast), quickMessageEnabled: Schema.optionalKey(Schema.Boolean), quickMessageShortcut: Schema.optionalKey(GlobalShortcutChord), wordWrap: Schema.optionalKey(Schema.Boolean), From f22a580aafa0ad2719abb33f99d8af12cb4de048 Mon Sep 17 00:00:00 2001 From: Adam Higgins Date: Thu, 22 Oct 2026 18:55:14 +0100 Subject: [PATCH 2/2] fix(web): restore quick message appearance with client settings --- apps/web/src/components/settings/SettingsPanels.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx index 8efda04c77c82..c83df830a27b0 100644 --- a/apps/web/src/components/settings/SettingsPanels.tsx +++ b/apps/web/src/components/settings/SettingsPanels.tsx @@ -431,6 +431,12 @@ export function useSettingsRestore(onRestored?: () => void) { ? ["Contrast"] : []), ...(settings.glassOpacity !== DEFAULT_UNIFIED_SETTINGS.glassOpacity ? ["Glass opacity"] : []), + ...(settings.quickMessageThemeSource !== DEFAULT_UNIFIED_SETTINGS.quickMessageThemeSource || + settings.quickMessageTheme !== DEFAULT_UNIFIED_SETTINGS.quickMessageTheme || + settings.quickMessageAppearance !== DEFAULT_UNIFIED_SETTINGS.quickMessageAppearance || + settings.quickMessageExtraContrast !== DEFAULT_UNIFIED_SETTINGS.quickMessageExtraContrast + ? ["Quick message appearance"] + : []), ...(settings.diffColorScheme !== DEFAULT_UNIFIED_SETTINGS.diffColorScheme ? ["Diff colors"] : []), @@ -644,6 +650,10 @@ export function useSettingsRestore(onRestored?: () => void) { return; } updateSettings({ + quickMessageThemeSource: DEFAULT_UNIFIED_SETTINGS.quickMessageThemeSource, + quickMessageTheme: DEFAULT_UNIFIED_SETTINGS.quickMessageTheme, + quickMessageAppearance: DEFAULT_UNIFIED_SETTINGS.quickMessageAppearance, + quickMessageExtraContrast: DEFAULT_UNIFIED_SETTINGS.quickMessageExtraContrast, appearanceContrast: DEFAULT_UNIFIED_SETTINGS.appearanceContrast, diffColorScheme: DEFAULT_UNIFIED_SETTINGS.diffColorScheme, chatWidth: DEFAULT_UNIFIED_SETTINGS.chatWidth,