Skip to content

Commit 6d184f0

Browse files
committed
Fix legacy patch fields overriding explicit backgroundActivity and extract duplicated settings utilities
- Fix applyServerSettingsPatch: skip legacy-derived backgroundActivityPatch when an explicit backgroundActivity object is present in the patch, preventing silent override of new-style patches by legacy fields. - Extract shared background activity utilities (BackgroundActivityOverridePatch, durationToSeconds, normalizeIntervalSeconds, backgroundActivityOverrideSettings, BackgroundPolicyTooltip) into backgroundActivityUtils.tsx to eliminate duplication between SettingsPanels.tsx and SourceControlSettings.tsx.
1 parent 038e209 commit 6d184f0

4 files changed

Lines changed: 99 additions & 139 deletions

File tree

apps/web/src/components/settings/SettingsPanels.tsx

Lines changed: 6 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import {
22
ArchiveIcon,
33
ArchiveX,
4-
InfoIcon,
54
LoaderIcon,
65
PlusIcon,
76
RefreshCwIcon,
@@ -106,6 +105,12 @@ import {
106105
SettingsSection,
107106
useRelativeTimeTick,
108107
} from "./settingsLayout";
108+
import {
109+
BackgroundPolicyTooltip as PolicyTooltip,
110+
backgroundActivityOverrideSettings,
111+
durationToSeconds,
112+
normalizeIntervalSeconds,
113+
} from "./backgroundActivityUtils";
109114
import { ProjectFavicon } from "../ProjectFavicon";
110115
import { useServerObservability, useServerProviders } from "../../rpc/serverState";
111116

@@ -137,11 +142,6 @@ const BACKGROUND_ACTIVITY_PROFILE_LABELS: Record<BackgroundActivityProfile, stri
137142
};
138143

139144
type BackgroundActivityProfileOption = BackgroundActivityProfile | "advanced";
140-
type BackgroundActivityOverridePatch = Partial<{
141-
[K in keyof BackgroundActivitySettings["overrides"]]:
142-
| BackgroundActivitySettings["overrides"][K]
143-
| undefined;
144-
}>;
145145

146146
const BACKGROUND_ACTIVITY_PROFILE_OPTION_LABELS: Record<BackgroundActivityProfileOption, string> = {
147147
...BACKGROUND_ACTIVITY_PROFILE_LABELS,
@@ -174,17 +174,6 @@ const BACKGROUND_ACTIVITY_BOOLEAN_OVERRIDES: ReadonlyArray<{
174174
{ key: "pauseWhenOnBattery", label: "Pause on battery" },
175175
];
176176

177-
function durationToSeconds(duration: Duration.Duration): number {
178-
return Math.round(Duration.toMillis(duration) / 1_000);
179-
}
180-
181-
function normalizeIntervalSeconds(value: number | null): number {
182-
if (value === null || !Number.isFinite(value)) {
183-
return 0;
184-
}
185-
return Math.max(0, Math.round(value));
186-
}
187-
188177
function resolveBackgroundActivityProfileOption(settings: {
189178
readonly backgroundActivity: BackgroundActivitySettings;
190179
}): BackgroundActivityProfileOption {
@@ -209,50 +198,6 @@ function backgroundActivityProfileSettings(profile: BackgroundActivityProfile) {
209198
};
210199
}
211200

212-
function backgroundActivityOverrideSettings(
213-
current: BackgroundActivitySettings,
214-
overrides: BackgroundActivityOverridePatch,
215-
) {
216-
const nextOverrides: BackgroundActivityOverridePatch = {
217-
...current.overrides,
218-
...overrides,
219-
};
220-
for (const [key, value] of Object.entries(nextOverrides)) {
221-
if (value === undefined) {
222-
delete nextOverrides[key as keyof typeof nextOverrides];
223-
}
224-
}
225-
return {
226-
backgroundActivity: {
227-
schemaVersion: 1 as const,
228-
profile: "custom" as const,
229-
baseProfile: getBackgroundActivityBaseProfile(current),
230-
overrides: nextOverrides as BackgroundActivitySettings["overrides"],
231-
},
232-
};
233-
}
234-
235-
function PolicyTooltip({ children }: { readonly children: string }) {
236-
return (
237-
<Tooltip>
238-
<TooltipTrigger
239-
render={
240-
<button
241-
type="button"
242-
className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
243-
aria-label="Background policy details"
244-
>
245-
<InfoIcon className="size-3.5" />
246-
</button>
247-
}
248-
/>
249-
<TooltipPopup side="top" className="max-w-72">
250-
{children}
251-
</TooltipPopup>
252-
</Tooltip>
253-
);
254-
}
255-
256201
function withoutProviderInstanceKey<V>(
257202
record: Readonly<Record<ProviderInstanceId, V>> | undefined,
258203
key: ProviderInstanceId,

apps/web/src/components/settings/SourceControlSettings.tsx

Lines changed: 7 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
import { ChevronDownIcon, GitPullRequestIcon, InfoIcon, RefreshCwIcon } from "lucide-react";
1+
import { ChevronDownIcon, GitPullRequestIcon, RefreshCwIcon } from "lucide-react";
22
import * as Duration from "effect/Duration";
33
import * as Option from "effect/Option";
44
import { useState, type ReactNode } from "react";
55
import type {
6-
BackgroundActivitySettings,
76
SourceControlProviderKind,
87
SourceControlDiscoveryResult,
98
SourceControlProviderAuth,
@@ -55,6 +54,12 @@ import {
5554
} from "../Icons";
5655
import { RedactedSensitiveText } from "./RedactedSensitiveText";
5756
import { SettingResetButton, SettingsPageContainer, SettingsSection } from "./settingsLayout";
57+
import {
58+
BackgroundPolicyTooltip,
59+
backgroundActivityOverrideSettings,
60+
durationToSeconds,
61+
normalizeIntervalSeconds as normalizeFetchIntervalSeconds,
62+
} from "./backgroundActivityUtils";
5863

5964
const EMPTY_DISCOVERY_RESULT: SourceControlDiscoveryResult = {
6065
versionControlSystems: [],
@@ -75,43 +80,6 @@ const VCS_ICONS: Partial<Record<VcsDriverKind, Icon>> = {
7580

7681
const SOURCE_CONTROL_SKELETON_ROWS = ["primary", "secondary"] as const;
7782
const GIT_FETCH_INTERVAL_STEP_SECONDS = 5;
78-
type BackgroundActivityOverridePatch = Partial<{
79-
[K in keyof BackgroundActivitySettings["overrides"]]:
80-
| BackgroundActivitySettings["overrides"][K]
81-
| undefined;
82-
}>;
83-
84-
function durationToSeconds(duration: Duration.Duration): number {
85-
return Math.round(Duration.toMillis(duration) / 1_000);
86-
}
87-
88-
function normalizeFetchIntervalSeconds(value: number | null): number {
89-
if (value === null || !Number.isFinite(value)) {
90-
return 0;
91-
}
92-
return Math.max(0, Math.round(value));
93-
}
94-
95-
function BackgroundPolicyTooltip({ children }: { readonly children: string }) {
96-
return (
97-
<Tooltip>
98-
<TooltipTrigger
99-
render={
100-
<button
101-
type="button"
102-
className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
103-
aria-label="Background policy details"
104-
>
105-
<InfoIcon className="size-3.5" />
106-
</button>
107-
}
108-
/>
109-
<TooltipPopup side="top" className="max-w-72">
110-
{children}
111-
</TooltipPopup>
112-
</Tooltip>
113-
);
114-
}
11583

11684
function optionLabel(value: Option.Option<string>): string | null {
11785
return Option.getOrNull(value);
@@ -335,28 +303,6 @@ function GitFetchIntervalSettings() {
335303
);
336304
const canResetFetchInterval =
337305
automaticGitFetchIntervalSeconds !== defaultAutomaticGitFetchIntervalSeconds;
338-
const backgroundActivityOverrideSettings = (
339-
current: BackgroundActivitySettings,
340-
overrides: BackgroundActivityOverridePatch,
341-
) => {
342-
const nextOverrides: BackgroundActivityOverridePatch = {
343-
...current.overrides,
344-
...overrides,
345-
};
346-
for (const [key, value] of Object.entries(nextOverrides)) {
347-
if (value === undefined) {
348-
delete nextOverrides[key as keyof typeof nextOverrides];
349-
}
350-
}
351-
return {
352-
backgroundActivity: {
353-
schemaVersion: 1 as const,
354-
profile: "custom" as const,
355-
baseProfile: getBackgroundActivityBaseProfile(current),
356-
overrides: nextOverrides as BackgroundActivitySettings["overrides"],
357-
},
358-
};
359-
};
360306

361307
return (
362308
<div className="grid gap-3">
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { InfoIcon } from "lucide-react";
2+
import * as Duration from "effect/Duration";
3+
import type { BackgroundActivitySettings } from "@t3tools/contracts";
4+
import { getBackgroundActivityBaseProfile } from "@t3tools/shared/backgroundActivitySettings";
5+
6+
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
7+
8+
export type BackgroundActivityOverridePatch = Partial<{
9+
[K in keyof BackgroundActivitySettings["overrides"]]:
10+
| BackgroundActivitySettings["overrides"][K]
11+
| undefined;
12+
}>;
13+
14+
export function durationToSeconds(duration: Duration.Duration): number {
15+
return Math.round(Duration.toMillis(duration) / 1_000);
16+
}
17+
18+
export function normalizeIntervalSeconds(value: number | null): number {
19+
if (value === null || !Number.isFinite(value)) {
20+
return 0;
21+
}
22+
return Math.max(0, Math.round(value));
23+
}
24+
25+
export function backgroundActivityOverrideSettings(
26+
current: BackgroundActivitySettings,
27+
overrides: BackgroundActivityOverridePatch,
28+
) {
29+
const nextOverrides: BackgroundActivityOverridePatch = {
30+
...current.overrides,
31+
...overrides,
32+
};
33+
for (const [key, value] of Object.entries(nextOverrides)) {
34+
if (value === undefined) {
35+
delete nextOverrides[key as keyof typeof nextOverrides];
36+
}
37+
}
38+
return {
39+
backgroundActivity: {
40+
schemaVersion: 1 as const,
41+
profile: "custom" as const,
42+
baseProfile: getBackgroundActivityBaseProfile(current),
43+
overrides: nextOverrides as BackgroundActivitySettings["overrides"],
44+
},
45+
};
46+
}
47+
48+
export function BackgroundPolicyTooltip({ children }: { readonly children: string }) {
49+
return (
50+
<Tooltip>
51+
<TooltipTrigger
52+
render={
53+
<button
54+
type="button"
55+
className="inline-flex size-5 items-center justify-center rounded-sm text-muted-foreground hover:text-foreground"
56+
aria-label="Background policy details"
57+
>
58+
<InfoIcon className="size-3.5" />
59+
</button>
60+
}
61+
/>
62+
<TooltipPopup side="top" className="max-w-72">
63+
{children}
64+
</TooltipPopup>
65+
</Tooltip>
66+
);
67+
}

packages/shared/src/serverSettings.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,26 +89,28 @@ export function applyServerSettingsPatch(
8989
...patchForMerge
9090
} = patch;
9191
const backgroundActivityPatch =
92-
backgroundActivityProfile !== undefined
93-
? {
94-
schemaVersion: 1 as const,
95-
profile: backgroundActivityProfile,
96-
overrides: {},
97-
}
98-
: automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined
92+
backgroundActivity !== undefined
93+
? undefined
94+
: backgroundActivityProfile !== undefined
9995
? {
10096
schemaVersion: 1 as const,
101-
profile: "custom" as const,
102-
baseProfile: getBackgroundActivityBaseProfile(current.backgroundActivity),
103-
overrides: {
104-
...current.backgroundActivity.overrides,
105-
...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}),
106-
...(providerHealthRefreshInterval !== undefined
107-
? { providerHealthRefreshInterval }
108-
: {}),
109-
},
97+
profile: backgroundActivityProfile,
98+
overrides: {},
11099
}
111-
: undefined;
100+
: automaticGitFetchInterval !== undefined || providerHealthRefreshInterval !== undefined
101+
? {
102+
schemaVersion: 1 as const,
103+
profile: "custom" as const,
104+
baseProfile: getBackgroundActivityBaseProfile(current.backgroundActivity),
105+
overrides: {
106+
...current.backgroundActivity.overrides,
107+
...(automaticGitFetchInterval !== undefined ? { automaticGitFetchInterval } : {}),
108+
...(providerHealthRefreshInterval !== undefined
109+
? { providerHealthRefreshInterval }
110+
: {}),
111+
},
112+
}
113+
: undefined;
112114
const next = deepMerge(current, patchForMerge);
113115
const nextWithReplacementsBase = {
114116
...next,

0 commit comments

Comments
 (0)