Skip to content

Commit 38ee807

Browse files
Yih-Chun HuYih-Chun Hu
authored andcommitted
fix(web): preserve snooze time drafts and group auto-settle settings
1 parent a9bcd94 commit 38ee807

3 files changed

Lines changed: 171 additions & 92 deletions

File tree

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

Lines changed: 84 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,106 +1051,106 @@ export function GeneralSettingsPanel() {
10511051
</Select>
10521052
}
10531053
/>
1054+
</SettingsSection>
10541055

1055-
{supportsAutoSettlement ? (
1056-
<>
1056+
{supportsAutoSettlement ? (
1057+
<SettingsSection id="auto-settle" title="Auto-settle">
1058+
<SettingsRow
1059+
serverScoped
1060+
settingKeys={["sidebarAutoSettleOnMerge"]}
1061+
{...searchableSetting("auto-settle-merged-threads")}
1062+
description="Settle a thread when its pull request merges. Closed pull requests still settle automatically."
1063+
resetAction={
1064+
settings.sidebarAutoSettleOnMerge !==
1065+
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge ? (
1066+
<SettingResetButton
1067+
label="auto-settle on merge"
1068+
onClick={() =>
1069+
updateSettings({
1070+
sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge,
1071+
})
1072+
}
1073+
/>
1074+
) : null
1075+
}
1076+
control={
1077+
<ScopedSwitch
1078+
settingKeys={["sidebarAutoSettleOnMerge"]}
1079+
checked={settings.sidebarAutoSettleOnMerge}
1080+
onCheckedChange={(checked) =>
1081+
updateSettings({ sidebarAutoSettleOnMerge: Boolean(checked) })
1082+
}
1083+
aria-label="Auto-settle merged threads"
1084+
/>
1085+
}
1086+
/>
1087+
1088+
<SettingsRow
1089+
serverScoped
1090+
settingKeys={["sidebarAutoSettleAfterDays"]}
1091+
{...searchableSetting("auto-settle-inactive-threads")}
1092+
description="Sidebar threads with no activity for this long settle automatically."
1093+
resetAction={
1094+
settings.sidebarAutoSettleAfterDays !==
1095+
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays ? (
1096+
<SettingResetButton
1097+
label="auto-settle"
1098+
onClick={() =>
1099+
updateSettings({
1100+
sidebarAutoSettleAfterDays:
1101+
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays,
1102+
})
1103+
}
1104+
/>
1105+
) : null
1106+
}
1107+
control={
1108+
<ScopedSwitch
1109+
settingKeys={["sidebarAutoSettleAfterDays"]}
1110+
checked={settings.sidebarAutoSettleAfterDays !== null}
1111+
onCheckedChange={(checked) =>
1112+
updateSettings({
1113+
sidebarAutoSettleAfterDays: checked ? AUTO_SETTLE_DEFAULT_DAYS : null,
1114+
})
1115+
}
1116+
aria-label="Auto-settle inactive threads"
1117+
/>
1118+
}
1119+
/>
1120+
{settings.sidebarAutoSettleAfterDays !== null ? (
10571121
<SettingsRow
10581122
serverScoped
1059-
settingKeys={["sidebarAutoSettleOnMerge"]}
1060-
{...searchableSetting("auto-settle-merged-threads")}
1061-
description="Settle a thread when its pull request merges. Closed pull requests still settle automatically."
1062-
resetAction={
1063-
settings.sidebarAutoSettleOnMerge !==
1064-
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge ? (
1065-
<SettingResetButton
1066-
label="auto-settle on merge"
1067-
onClick={() =>
1068-
updateSettings({
1069-
sidebarAutoSettleOnMerge: DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleOnMerge,
1070-
})
1071-
}
1072-
/>
1073-
) : null
1074-
}
1123+
settingKeys={["sidebarAutoSettleAfterDays"]}
1124+
title={searchableSetting("days-before-auto-settle").title}
1125+
description="Any new activity un-settles a thread automatically."
10751126
control={
1076-
<ScopedSwitch
1077-
settingKeys={["sidebarAutoSettleOnMerge"]}
1078-
checked={settings.sidebarAutoSettleOnMerge}
1079-
onCheckedChange={(checked) =>
1080-
updateSettings({ sidebarAutoSettleOnMerge: Boolean(checked) })
1081-
}
1082-
aria-label="Auto-settle merged threads"
1127+
<AutoSettleDaysInput
1128+
value={settings.sidebarAutoSettleAfterDays}
1129+
onCommit={(days) => updateSettings({ sidebarAutoSettleAfterDays: days })}
10831130
/>
10841131
}
10851132
/>
1086-
1133+
) : null}
1134+
{settings.sidebarAutoSettleAfterDays !== null && supportsAutoSettlementScope ? (
10871135
<SettingsRow
10881136
serverScoped
1089-
settingKeys={["sidebarAutoSettleAfterDays"]}
1090-
{...searchableSetting("auto-settle-inactive-threads")}
1091-
description="Sidebar threads with no activity for this long settle automatically."
1092-
resetAction={
1093-
settings.sidebarAutoSettleAfterDays !==
1094-
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays ? (
1095-
<SettingResetButton
1096-
label="auto-settle"
1097-
onClick={() =>
1098-
updateSettings({
1099-
sidebarAutoSettleAfterDays:
1100-
DEFAULT_UNIFIED_SETTINGS.sidebarAutoSettleAfterDays,
1101-
})
1102-
}
1103-
/>
1104-
) : null
1105-
}
1137+
settingKeys={["sidebarAutoSettleKeepPullRequests"]}
1138+
title="Keep threads with a PR"
1139+
description="Inactivity does not settle threads with a pull request. Merge and close still do."
11061140
control={
11071141
<ScopedSwitch
1108-
settingKeys={["sidebarAutoSettleAfterDays"]}
1109-
checked={settings.sidebarAutoSettleAfterDays !== null}
1142+
settingKeys={["sidebarAutoSettleKeepPullRequests"]}
1143+
checked={settings.sidebarAutoSettleKeepPullRequests}
11101144
onCheckedChange={(checked) =>
1111-
updateSettings({
1112-
sidebarAutoSettleAfterDays: checked ? AUTO_SETTLE_DEFAULT_DAYS : null,
1113-
})
1145+
updateSettings({ sidebarAutoSettleKeepPullRequests: checked })
11141146
}
1115-
aria-label="Auto-settle inactive threads"
1147+
aria-label="Keep threads with a PR"
11161148
/>
11171149
}
11181150
/>
1119-
{settings.sidebarAutoSettleAfterDays !== null ? (
1120-
<SettingsRow
1121-
serverScoped
1122-
settingKeys={["sidebarAutoSettleAfterDays"]}
1123-
title={searchableSetting("days-before-auto-settle").title}
1124-
description="Any new activity un-settles a thread automatically."
1125-
control={
1126-
<AutoSettleDaysInput
1127-
value={settings.sidebarAutoSettleAfterDays}
1128-
onCommit={(days) => updateSettings({ sidebarAutoSettleAfterDays: days })}
1129-
/>
1130-
}
1131-
/>
1132-
) : null}
1133-
{settings.sidebarAutoSettleAfterDays !== null && supportsAutoSettlementScope ? (
1134-
<SettingsRow
1135-
serverScoped
1136-
settingKeys={["sidebarAutoSettleKeepPullRequests"]}
1137-
title="Keep threads with a PR"
1138-
description="Inactivity does not settle threads with a pull request. Merge and close still do."
1139-
control={
1140-
<ScopedSwitch
1141-
settingKeys={["sidebarAutoSettleKeepPullRequests"]}
1142-
checked={settings.sidebarAutoSettleKeepPullRequests}
1143-
onCheckedChange={(checked) =>
1144-
updateSettings({ sidebarAutoSettleKeepPullRequests: checked })
1145-
}
1146-
aria-label="Keep threads with a PR"
1147-
/>
1148-
}
1149-
/>
1150-
) : null}
1151-
</>
1152-
) : null}
1153-
</SettingsSection>
1151+
) : null}
1152+
</SettingsSection>
1153+
) : null}
11541154

11551155
<SnoozeSettings />
11561156

‎apps/web/src/components/settings/SnoozeSettings.browser.test.tsx‎

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,54 @@ it("ignores fractional hours without blocking later client-setting persistence",
8282
snoozePreferences: [{ mode: "relative", hours: 168 }, ...DEFAULT_SNOOZE_PRESETS.slice(1)],
8383
});
8484
});
85+
86+
it("allows clearing and replacing a snooze time without persisting an empty value", async () => {
87+
using _settings = setup();
88+
await using screen = await render(<SnoozeSettings />);
89+
await userEvent.click(screen.getByRole("button", { name: "Snooze", exact: true }));
90+
const time = screen.container.querySelector<HTMLInputElement>(
91+
'input[aria-label="Snooze slot 3 time"]',
92+
)!;
93+
94+
await userEvent.fill(time, "");
95+
await expect.element(time).toHaveValue("");
96+
await act(() => persistClientSettingsPatch({ timestampFormat: "24-hour" }));
97+
expect(readBrowserClientSettings()?.snoozePreferences?.[2]).toEqual({
98+
mode: "absolute",
99+
day: "today",
100+
time: "18:00",
101+
});
102+
await expect.element(time).toHaveValue("");
103+
104+
await userEvent.fill(time, "06:30");
105+
await vi.waitFor(() => {
106+
expect(readBrowserClientSettings()?.snoozePreferences?.[2]).toEqual({
107+
mode: "absolute",
108+
day: "today",
109+
time: "06:30",
110+
});
111+
});
112+
});
113+
114+
it("restores an empty time on blur and reflects changes to the saved time", async () => {
115+
using _settings = setup();
116+
await using screen = await render(<SnoozeSettings />);
117+
await userEvent.click(screen.getByRole("button", { name: "Snooze", exact: true }));
118+
const time = screen.container.querySelector<HTMLInputElement>(
119+
'input[aria-label="Snooze slot 3 time"]',
120+
)!;
121+
const hours = screen.container.querySelector<HTMLInputElement>(
122+
'input[aria-label="Snooze slot 1 hours"]',
123+
)!;
124+
125+
await userEvent.fill(time, "");
126+
await expect.element(time).toHaveValue("");
127+
await userEvent.click(hours);
128+
await expect.element(time).toHaveValue("18:00");
129+
130+
await userEvent.fill(time, "");
131+
const preferences = [...DEFAULT_SNOOZE_PRESETS];
132+
preferences[2] = { mode: "absolute", day: "today", time: "20:00" };
133+
await act(() => persistClientSettingsPatch({ snoozePreferences: preferences }));
134+
await expect.element(time).toHaveValue("20:00");
135+
});

‎apps/web/src/components/settings/SnoozeSettings.tsx‎

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useState } from "react";
12
import * as Schema from "effect/Schema";
23
import {
34
DEFAULT_SNOOZE_PRESETS,
@@ -23,6 +24,38 @@ import { FoldedSettingsSection } from "./FoldedSettingsSection";
2324
const isSnoozeTime = Schema.is(SnoozeTime);
2425
const isSnoozeDay = Schema.is(SnoozeDay);
2526

27+
function SnoozeTimeInput({
28+
label,
29+
value,
30+
onCommit,
31+
}: {
32+
label: string;
33+
value: string;
34+
onCommit: (time: string) => void;
35+
}) {
36+
const [draft, setDraft] = useState(value);
37+
const [draftSource, setDraftSource] = useState(value);
38+
if (draftSource !== value) {
39+
setDraftSource(value);
40+
setDraft(value);
41+
}
42+
43+
return (
44+
<Input
45+
size="sm"
46+
type="time"
47+
aria-label={label}
48+
value={draft}
49+
onChange={(event) => {
50+
const time = event.target.value;
51+
setDraft(time);
52+
if (isSnoozeTime(time)) onCommit(time);
53+
}}
54+
onBlur={() => setDraft(value)}
55+
/>
56+
);
57+
}
58+
2659
function SnoozeSlotEditor({
2760
slot,
2861
index,
@@ -112,15 +145,10 @@ function SnoozeSlotEditor({
112145
</div>
113146
<span className="text-xs text-muted-foreground">at</span>
114147
<div className="w-32">
115-
<Input
116-
size="sm"
117-
type="time"
118-
aria-label={`${label} time`}
148+
<SnoozeTimeInput
149+
label={`${label} time`}
119150
value={slot.time}
120-
onChange={(event) => {
121-
if (isSnoozeTime(event.target.value))
122-
onChange({ ...slot, time: event.target.value });
123-
}}
151+
onCommit={(time) => onChange({ ...slot, time })}
124152
/>
125153
</div>
126154
</>

0 commit comments

Comments
 (0)