authorgravatar for 71498452+StiensWout@users.noreply.github.comWout Stiens <71498452+StiensWout@users.noreply.github.com> 2026-08-11 17:21:58+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-11 17:21:58+02:00
logac4780f451f98c10d5b518f2bfa3d035b46645df
tree528e761c98f4ed04201efac5f84a430476c51bf7
parent3da7f9c5c35d0c1f2a1b4420fbc10016e11ce069
signature Signed by PGP key B5690EEEBB952194

fix(web): restore typography font sizes to defaults (#6172)


3 files changed, 110 insertions(+), 16 deletions(-)

apps/web/src/components/settings/SettingsPanels.logic.test.ts+14
......@@ -12,12 +12,26 @@ import {
1212 backgroundActivitySharedPolicySettings,
1313 buildProviderInstanceUpdatePatch,
1414 formatDiagnosticsDescription,
15 getChangedTypographySettingLabels,
1516 hasChangedBackgroundActivitySettings,
1617 isProjectGroupingEnabled,
1718 projectGroupingModeFromToggle,
1819 resolveBackgroundActivityProfileOption,
1920} from "./SettingsPanels.logic";
2021
22describe("typography settings restore", () => {
23 it("detects family and size changes by font row", () => {
24 expect(getChangedTypographySettingLabels(DEFAULT_UNIFIED_SETTINGS)).toEqual([]);
25 expect(
26 getChangedTypographySettingLabels({
27 ...DEFAULT_UNIFIED_SETTINGS,
28 fontSizeInterface: 18,
29 fontFamilyCode: "Fira Code",
30 }),
31 ).toEqual(["Interface font", "Code font"]);
32 });
33});
34
2135describe("background activity settings restore", () => {
2236 it("detects legacy interval values even when the structured setting is at its default", () => {
2337 expect(
apps/web/src/components/settings/SettingsPanels.logic.ts+34
......@@ -74,6 +74,40 @@ export function hasChangedBackgroundActivitySettings(
7474 );
7575}
7676
77type TypographySettings = Pick<
78 UnifiedSettings,
79 | "fontFamilySans"
80 | "fontFamilyComposer"
81 | "fontFamilyCode"
82 | "fontFamilyTerminal"
83 | "fontSizeInterface"
84 | "fontSizePrompt"
85 | "fontSizeCode"
86 | "fontSizeTerminal"
87>;
88
89/** Labels the font rows whose family or size differs from the defaults. */
90export function getChangedTypographySettingLabels(settings: TypographySettings): string[] {
91 return [
92 ...(settings.fontFamilySans !== DEFAULT_UNIFIED_SETTINGS.fontFamilySans ||
93 settings.fontSizeInterface !== DEFAULT_UNIFIED_SETTINGS.fontSizeInterface
94 ? ["Interface font"]
95 : []),
96 ...(settings.fontFamilyComposer !== DEFAULT_UNIFIED_SETTINGS.fontFamilyComposer ||
97 settings.fontSizePrompt !== DEFAULT_UNIFIED_SETTINGS.fontSizePrompt
98 ? ["Prompt font"]
99 : []),
100 ...(settings.fontFamilyCode !== DEFAULT_UNIFIED_SETTINGS.fontFamilyCode ||
101 settings.fontSizeCode !== DEFAULT_UNIFIED_SETTINGS.fontSizeCode
102 ? ["Code font"]
103 : []),
104 ...(settings.fontFamilyTerminal !== DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal ||
105 settings.fontSizeTerminal !== DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal
106 ? ["Terminal font"]
107 : []),
108 ];
109}
110
77111export function resolveBackgroundActivityProfileOption(
78112 settings: ServerSettings,
79113): BackgroundActivityProfile | "advanced" {
apps/web/src/components/settings/SettingsPanels.tsx+62-16
......@@ -122,6 +122,7 @@ import {
122122 backgroundActivitySharedPolicySettings,
123123 durationToSeconds,
124124 formatDiagnosticsDescription,
125 getChangedTypographySettingLabels,
125126 normalizeIntervalSeconds,
126127 PROVIDER_HEALTH_INTERVAL_STEP_SECONDS,
127128 hasChangedBackgroundActivitySettings,
......@@ -493,16 +494,7 @@ export function useSettingsRestore(onRestored?: () => void) {
493494 ? ["Auto-settle inactive threads"]
494495 : []),
495496 ...(settings.wordWrap !== DEFAULT_UNIFIED_SETTINGS.wordWrap ? ["Word wrap"] : []),
496 ...(settings.fontFamilySans !== DEFAULT_UNIFIED_SETTINGS.fontFamilySans
497 ? ["Interface font"]
498 : []),
499 ...(settings.fontFamilyComposer !== DEFAULT_UNIFIED_SETTINGS.fontFamilyComposer
500 ? ["Prompt font"]
501 : []),
502 ...(settings.fontFamilyCode !== DEFAULT_UNIFIED_SETTINGS.fontFamilyCode ? ["Code font"] : []),
503 ...(settings.fontFamilyTerminal !== DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal
504 ? ["Terminal font"]
505 : []),
497 ...getChangedTypographySettingLabels(settings),
506498 ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace
507499 ? ["Diff whitespace changes"]
508500 : []),
......@@ -652,6 +644,10 @@ export function useSettingsRestore(onRestored?: () => void) {
652644 fontFamilyComposer: DEFAULT_UNIFIED_SETTINGS.fontFamilyComposer,
653645 fontFamilyCode: DEFAULT_UNIFIED_SETTINGS.fontFamilyCode,
654646 fontFamilyTerminal: DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal,
647 fontSizeInterface: DEFAULT_UNIFIED_SETTINGS.fontSizeInterface,
648 fontSizePrompt: DEFAULT_UNIFIED_SETTINGS.fontSizePrompt,
649 fontSizeCode: DEFAULT_UNIFIED_SETTINGS.fontSizeCode,
650 fontSizeTerminal: DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal,
655651 });
656652 onRestored?.();
657653 }, [
......@@ -1105,13 +1101,21 @@ function InterfaceFontRow({ preview }: { preview?: ReactNode }) {
11051101 {...searchableSetting("interface-font")}
11061102 description="Everything outside code blocks and the terminal."
11071103 defaultFamily={defaults.sans}
1104 defaultValue={DEFAULT_UNIFIED_SETTINGS.fontFamilySans}
11081105 value={settings.fontFamilySans}
11091106 onValueChange={(fontFamilySans) => updateSettings({ fontFamilySans })}
1107 onReset={() =>
1108 updateSettings({
1109 fontFamilySans: DEFAULT_UNIFIED_SETTINGS.fontFamilySans,
1110 fontSizeInterface: DEFAULT_UNIFIED_SETTINGS.fontSizeInterface,
1111 })
1112 }
11101113 size={{
11111114 label: "Interface font size",
11121115 min: MIN_INTERFACE_FONT_SIZE,
11131116 max: MAX_INTERFACE_FONT_SIZE,
11141117 value: settings.fontSizeInterface,
1118 defaultValue: DEFAULT_UNIFIED_SETTINGS.fontSizeInterface,
11151119 onChange: (fontSizeInterface) => updateSettings({ fontSizeInterface }),
11161120 }}
11171121 {...(preview !== undefined ? { preview } : {})}
......@@ -1128,13 +1132,21 @@ function PromptFontRow() {
11281132 {...searchableSetting("prompt-font")}
11291133 description="Only the box you write prompts in. Mono works well here."
11301134 defaultFamily={defaults.interfaceFamily}
1135 defaultValue={DEFAULT_UNIFIED_SETTINGS.fontFamilyComposer}
11311136 value={settings.fontFamilyComposer}
11321137 onValueChange={(fontFamilyComposer) => updateSettings({ fontFamilyComposer })}
1138 onReset={() =>
1139 updateSettings({
1140 fontFamilyComposer: DEFAULT_UNIFIED_SETTINGS.fontFamilyComposer,
1141 fontSizePrompt: DEFAULT_UNIFIED_SETTINGS.fontSizePrompt,
1142 })
1143 }
11331144 size={{
11341145 label: "Prompt font size",
11351146 min: MIN_PROMPT_FONT_SIZE,
11361147 max: MAX_PROMPT_FONT_SIZE,
11371148 value: settings.fontSizePrompt,
1149 defaultValue: DEFAULT_UNIFIED_SETTINGS.fontSizePrompt,
11381150 onChange: (fontSizePrompt) => updateSettings({ fontSizePrompt }),
11391151 }}
11401152 preview={<PromptFontPreview />}
......@@ -1160,14 +1172,22 @@ function CodeFontRow({
11601172 {...(title !== undefined ? { title } : {})}
11611173 description={description}
11621174 defaultFamily={defaults.code}
1175 defaultValue={DEFAULT_UNIFIED_SETTINGS.fontFamilyCode}
11631176 value={settings.fontFamilyCode}
11641177 onValueChange={(fontFamilyCode) => updateSettings({ fontFamilyCode })}
1178 onReset={() =>
1179 updateSettings({
1180 fontFamilyCode: DEFAULT_UNIFIED_SETTINGS.fontFamilyCode,
1181 fontSizeCode: DEFAULT_UNIFIED_SETTINGS.fontSizeCode,
1182 })
1183 }
11651184 requireMonospace
11661185 size={{
11671186 label: "Code font size",
11681187 min: MIN_CODE_FONT_SIZE,
11691188 max: MAX_CODE_FONT_SIZE,
11701189 value: settings.fontSizeCode,
1190 defaultValue: DEFAULT_UNIFIED_SETTINGS.fontSizeCode,
11711191 onChange: (fontSizeCode) => updateSettings({ fontSizeCode }),
11721192 }}
11731193 preview={preview ?? <CodeFontPreview />}
......@@ -1184,14 +1204,22 @@ function TerminalFontRow() {
11841204 {...searchableSetting("terminal-font")}
11851205 description="Terminal output, independent from code blocks and diffs."
11861206 defaultFamily={defaults.code}
1207 defaultValue={DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal}
11871208 value={settings.fontFamilyTerminal}
11881209 onValueChange={(fontFamilyTerminal) => updateSettings({ fontFamilyTerminal })}
1210 onReset={() =>
1211 updateSettings({
1212 fontFamilyTerminal: DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal,
1213 fontSizeTerminal: DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal,
1214 })
1215 }
11891216 requireMonospace
11901217 size={{
11911218 label: "Terminal font size",
11921219 min: MIN_TERMINAL_FONT_SIZE,
11931220 max: MAX_TERMINAL_FONT_SIZE,
11941221 value: settings.fontSizeTerminal,
1222 defaultValue: DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal,
11951223 onChange: (fontSizeTerminal) => updateSettings({ fontSizeTerminal }),
11961224 }}
11971225 preview={
......@@ -1369,9 +1397,11 @@ function FontFamilySettingsRow({
13691397 title,
13701398 description,
13711399 defaultFamily,
1400 defaultValue,
13721401 preview,
13731402 value,
13741403 onValueChange,
1404 onReset,
13751405 requireMonospace = false,
13761406 size,
13771407}: {
......@@ -1380,11 +1410,21 @@ function FontFamilySettingsRow({
13801410 description: string;
13811411 /** What an unset preference renders as, e.g. "Menlo". */
13821412 defaultFamily: string;
1413 /** The persisted family value supplied by the unified settings defaults. */
1414 defaultValue: string;
13831415 preview?: ReactNode;
13841416 value: string;
13851417 onValueChange: (value: string) => void;
1418 onReset: () => void;
13861419 requireMonospace?: boolean;
1387 size: { label: string; min: number; max: number; value: number; onChange: (v: number) => void };
1420 size: {
1421 label: string;
1422 min: number;
1423 max: number;
1424 value: number;
1425 defaultValue: number;
1426 onChange: (v: number) => void;
1427 };
13881428}) {
13891429 const trimmed = value.trim();
13901430 // The fallback input edits a draft; the preference only commits once typing
......@@ -1431,12 +1471,18 @@ function FontFamilySettingsRow({
14311471 // Flag an unknown name only once typing pauses, and never for an empty
14321472 // field - that is the starting state, not a rejected entry.
14331473 const draftPending = draftSettled && draftTrimmed.length > 0 && draftTrimmed !== trimmed;
1474 const resetToDefault = () => {
1475 if (commitTimerRef.current !== null) {
1476 window.clearTimeout(commitTimerRef.current);
1477 commitTimerRef.current = null;
1478 }
1479 setDraft(defaultValue);
1480 setDraftSettled(true);
1481 onReset();
1482 };
14341483 const resetAction =
1435 trimmed.length > 0 ? (
1436 <SettingResetButton
1437 label={`${title.toLowerCase()} family`}
1438 onClick={() => onValueChange("")}
1439 />
1484 value !== defaultValue || size.value !== size.defaultValue ? (
1485 <SettingResetButton label={title.toLowerCase()} onClick={resetToDefault} />
14401486 ) : null;
14411487 const fontEnumeration = useFontEnumeration();
14421488 // Everyone starts on the plain input; focusing it is the user gesture that