authorgravatar for 73941998+UtkarshUsername@users.noreply.github.comUtkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> 2026-08-29 05:05:47+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-29 05:05:47+05:30
log22c311ddecfbab2e541a374a46f2df87d4fc6305
tree3ccb28628e0656f6491fc25cf50f34260a11950b
parentac3b2adf96e755580fc09e149b7c7eb057d0b282
signature Signed by PGP key B5690EEEBB952194

feat(web): add toggleable confirmation before unpinning a thread (#7313)

Co-authored-by: Shivam Sharma <91240327+shivamhwp@users.noreply.github.com>

12 files changed, 177 insertions(+), 21 deletions(-)

apps/desktop/src/settings/DesktopClientSettings.test.ts+1
...@@ -21,6 +21,7 @@ const clientSettings: ClientSettings = {...@@ -21,6 +21,7 @@ const clientSettings: ClientSettings = {
21 confirmQuit: true,21 confirmQuit: true,
22 confirmThreadArchive: true,22 confirmThreadArchive: true,
23 confirmThreadDelete: false,23 confirmThreadDelete: false,
24 confirmThreadUnpin: false,
24 dismissedProviderUpdateNotificationKeys: [],25 dismissedProviderUpdateNotificationKeys: [],
25 diffIgnoreWhitespace: true,26 diffIgnoreWhitespace: true,
26 environmentIdentificationMode: "artwork",27 environmentIdentificationMode: "artwork",
apps/web/src/components/ChatView.tsx+15-13
...@@ -1273,7 +1273,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -1273,7 +1273,7 @@ function ChatViewContent(props: ChatViewProps) {
1273 const threadSyncPhase = routeKind === "server" ? (props.threadSyncPhase ?? null) : null;1273 const threadSyncPhase = routeKind === "server" ? (props.threadSyncPhase ?? null) : null;
1274 const threadDetailLoading = threadSyncPhase === "loading";1274 const threadDetailLoading = threadSyncPhase === "loading";
1275 const handleNewThread = useNewThreadHandler();1275 const handleNewThread = useNewThreadHandler();
1276 const { settleThread, pinThread, unpinThread } = useThreadActions();1276 const { settleThread, pinThread, confirmAndUnpinThread } = useThreadActions();
1277 const routeThreadRef = useMemo(1277 const routeThreadRef = useMemo(
1278 () => scopeThreadRef(environmentId, threadId),1278 () => scopeThreadRef(environmentId, threadId),
1279 [environmentId, threadId],1279 [environmentId, threadId],
...@@ -5146,17 +5146,19 @@ function ChatViewContent(props: ChatViewProps) {...@@ -5146,17 +5146,19 @@ function ChatViewContent(props: ChatViewProps) {
5146 event.stopPropagation();5146 event.stopPropagation();
5147 if (!isServerThread || !activeThreadRef || !supportsPinning) return;5147 if (!isServerThread || !activeThreadRef || !supportsPinning) return;
5148 const pinned = activeThreadPinned;5148 const pinned = activeThreadPinned;
5149 void (pinned ? unpinThread(activeThreadRef) : pinThread(activeThreadRef)).then((result) => {5149 void (pinned ? confirmAndUnpinThread(activeThreadRef) : pinThread(activeThreadRef)).then(
5150 if (result._tag !== "Failure" || isAtomCommandInterrupted(result)) return;5150 (result) => {
5151 const error = squashAtomCommandFailure(result);5151 if (result._tag !== "Failure" || isAtomCommandInterrupted(result)) return;
5152 toastManager.add(5152 const error = squashAtomCommandFailure(result);
5153 stackedThreadToast({5153 toastManager.add(
5154 type: "error",5154 stackedThreadToast({
5155 title: pinned ? "Failed to unpin thread" : "Failed to pin thread",5155 type: "error",
5156 description: error instanceof Error ? error.message : "An error occurred.",5156 title: pinned ? "Failed to unpin thread" : "Failed to pin thread",
5157 }),5157 description: error instanceof Error ? error.message : "An error occurred.",
5158 );5158 }),
5159 });5159 );
5160 },
5161 );
5160 return;5162 return;
5161 }5163 }
51625164
...@@ -5284,7 +5286,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -5284,7 +5286,7 @@ function ChatViewContent(props: ChatViewProps) {
5284 settleThread,5286 settleThread,
5285 supportsPinning,5287 supportsPinning,
5286 supportsSettlement,5288 supportsSettlement,
5287 unpinThread,5289 confirmAndUnpinThread,
5288 toggleRightPanel,5290 toggleRightPanel,
5289 toggleRightPanelMaximized,5291 toggleRightPanelMaximized,
5290 toggleTerminalVisibility,5292 toggleTerminalVisibility,
apps/web/src/components/Sidebar.tsx+3-3
...@@ -1749,7 +1749,7 @@ export default function Sidebar() {...@@ -1749,7 +1749,7 @@ export default function Sidebar() {
1749 snoozeThread,1749 snoozeThread,
1750 unsnoozeThread,1750 unsnoozeThread,
1751 pinThread,1751 pinThread,
1752 unpinThread,1752 confirmAndUnpinThread,
1753 reorderPinnedThread,1753 reorderPinnedThread,
1754 archiveThread,1754 archiveThread,
1755 deleteThread,1755 deleteThread,
...@@ -2709,7 +2709,7 @@ export default function Sidebar() {...@@ -2709,7 +2709,7 @@ export default function Sidebar() {
2709 const attemptUnpin = useCallback(2709 const attemptUnpin = useCallback(
2710 (threadRef: ScopedThreadRef) => {2710 (threadRef: ScopedThreadRef) => {
2711 void (async () => {2711 void (async () => {
2712 const result = await unpinThread(threadRef);2712 const result = await confirmAndUnpinThread(threadRef);
2713 if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) {2713 if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) {
2714 const error = squashAtomCommandFailure(result);2714 const error = squashAtomCommandFailure(result);
2715 toastManager.add(2715 toastManager.add(
...@@ -2722,7 +2722,7 @@ export default function Sidebar() {...@@ -2722,7 +2722,7 @@ export default function Sidebar() {
2722 }2722 }
2723 })();2723 })();
2724 },2724 },
2725 [unpinThread],2725 [confirmAndUnpinThread],
2726 );2726 );
27272727
2728 const handlePinnedDragEnd = useCallback(2728 const handlePinnedDragEnd = useCallback(
apps/web/src/components/settings/SettingsPanels.tsx+31
...@@ -526,6 +526,9 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -526,6 +526,9 @@ export function useSettingsRestore(onRestored?: () => void) {
526 ...(settings.addProjectBaseDirectory !== DEFAULT_UNIFIED_SETTINGS.addProjectBaseDirectory526 ...(settings.addProjectBaseDirectory !== DEFAULT_UNIFIED_SETTINGS.addProjectBaseDirectory
527 ? ["Add project base directory"]527 ? ["Add project base directory"]
528 : []),528 : []),
529 ...(settings.confirmThreadUnpin !== DEFAULT_UNIFIED_SETTINGS.confirmThreadUnpin
530 ? ["Unpin confirmation"]
531 : []),
529 ...(settings.confirmThreadArchive !== DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive532 ...(settings.confirmThreadArchive !== DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive
530 ? ["Archive confirmation"]533 ? ["Archive confirmation"]
531 : []),534 : []),
...@@ -554,6 +557,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -554,6 +557,7 @@ export function useSettingsRestore(onRestored?: () => void) {
554 settings.confirmQuit,557 settings.confirmQuit,
555 settings.confirmThreadArchive,558 settings.confirmThreadArchive,
556 settings.confirmThreadDelete,559 settings.confirmThreadDelete,
560 settings.confirmThreadUnpin,
557 settings.addProjectBaseDirectory,561 settings.addProjectBaseDirectory,
558 settings.defaultThreadEnvMode,562 settings.defaultThreadEnvMode,
559 settings.newWorktreesStartFromOrigin,563 settings.newWorktreesStartFromOrigin,
...@@ -666,6 +670,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -666,6 +670,7 @@ export function useSettingsRestore(onRestored?: () => void) {
666 addProjectBaseDirectory: DEFAULT_UNIFIED_SETTINGS.addProjectBaseDirectory,670 addProjectBaseDirectory: DEFAULT_UNIFIED_SETTINGS.addProjectBaseDirectory,
667 confirmThreadArchive: DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive,671 confirmThreadArchive: DEFAULT_UNIFIED_SETTINGS.confirmThreadArchive,
668 confirmThreadDelete: DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete,672 confirmThreadDelete: DEFAULT_UNIFIED_SETTINGS.confirmThreadDelete,
673 confirmThreadUnpin: DEFAULT_UNIFIED_SETTINGS.confirmThreadUnpin,
669 confirmQuit: DEFAULT_UNIFIED_SETTINGS.confirmQuit,674 confirmQuit: DEFAULT_UNIFIED_SETTINGS.confirmQuit,
670 textGenerationModelSelection: DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection,675 textGenerationModelSelection: DEFAULT_UNIFIED_SETTINGS.textGenerationModelSelection,
671 fontFamilySans: DEFAULT_UNIFIED_SETTINGS.fontFamilySans,676 fontFamilySans: DEFAULT_UNIFIED_SETTINGS.fontFamilySans,
...@@ -2313,6 +2318,32 @@ export function GeneralSettingsPanel() {...@@ -2313,6 +2318,32 @@ export function GeneralSettingsPanel() {
2313 }2318 }
2314 />2319 />
23152320
2321 <SettingsRow
2322 {...searchableSetting("unpin-confirmation")}
2323 description="Ask before unpinning a thread from the pinned section."
2324 resetAction={
2325 settings.confirmThreadUnpin !== DEFAULT_UNIFIED_SETTINGS.confirmThreadUnpin ? (
2326 <SettingResetButton
2327 label="unpin confirmation"
2328 onClick={() =>
2329 updateSettings({
2330 confirmThreadUnpin: DEFAULT_UNIFIED_SETTINGS.confirmThreadUnpin,
2331 })
2332 }
2333 />
2334 ) : null
2335 }
2336 control={
2337 <Switch
2338 checked={settings.confirmThreadUnpin}
2339 onCheckedChange={(checked) =>
2340 updateSettings({ confirmThreadUnpin: Boolean(checked) })
2341 }
2342 aria-label="Confirm thread unpinning"
2343 />
2344 }
2345 />
2346
2316 <SettingsRow2347 <SettingsRow
2317 {...searchableSetting("archive-confirmation")}2348 {...searchableSetting("archive-confirmation")}
2318 description="Require a second click on the inline archive action before a thread is archived."2349 description="Require a second click on the inline archive action before a thread is archived."
apps/web/src/components/settings/settingsSearch.test.ts+8
...@@ -56,6 +56,14 @@ describe("searchSettings", () => {...@@ -56,6 +56,14 @@ describe("searchSettings", () => {
56 ]);56 ]);
57 });57 });
5858
59 it("lists thread confirmations in panel order", () => {
60 expect(searchSettings("confirmation").map((item) => item.id)).toEqual([
61 "unpin-confirmation",
62 "archive-confirmation",
63 "delete-confirmation",
64 ]);
65 });
66
59 it("returns no results for an empty query", () => {67 it("returns no results for an empty query", () => {
60 expect(searchSettings(" ", ITEMS)).toEqual([]);68 expect(searchSettings(" ", ITEMS)).toEqual([]);
61 });69 });
apps/web/src/components/settings/settingsSearch.ts+5
...@@ -152,6 +152,11 @@ export const SETTINGS_SEARCH_ITEMS = [...@@ -152,6 +152,11 @@ export const SETTINGS_SEARCH_ITEMS = [
152 title: "Add project starts in",152 title: "Add project starts in",
153 to: "/settings/general",153 to: "/settings/general",
154 },154 },
155 {
156 id: "unpin-confirmation",
157 title: "Unpin confirmation",
158 to: "/settings/general",
159 },
155 {160 {
156 id: "archive-confirmation",161 id: "archive-confirmation",
157 title: "Archive confirmation",162 title: "Archive confirmation",
apps/web/src/hooks/useThreadActionMenu.ts+5-4
...@@ -71,7 +71,7 @@ export function useThreadActionMenu(input: {...@@ -71,7 +71,7 @@ export function useThreadActionMenu(input: {
71 snoozeThread,71 snoozeThread,
72 unsnoozeThread,72 unsnoozeThread,
73 pinThread,73 pinThread,
74 unpinThread,74 confirmAndUnpinThread,
75 archiveThread,75 archiveThread,
76 deleteThread,76 deleteThread,
77 } = useThreadActions();77 } = useThreadActions();
...@@ -215,9 +215,10 @@ export function useThreadActionMenu(input: {...@@ -215,9 +215,10 @@ export function useThreadActionMenu(input: {
215 case "pin":215 case "pin":
216 await reportFailure("Failed to pin thread", () => pinThread(threadRef));216 await reportFailure("Failed to pin thread", () => pinThread(threadRef));
217 return;217 return;
218 case "unpin":218 case "unpin": {
219 await reportFailure("Failed to unpin thread", () => unpinThread(threadRef));219 await reportFailure("Failed to unpin thread", () => confirmAndUnpinThread(threadRef));
220 return;220 return;
221 }
221 case "rename":222 case "rename":
222 onStartRename();223 onStartRename();
223 return;224 return;
...@@ -315,6 +316,7 @@ export function useThreadActionMenu(input: {...@@ -315,6 +316,7 @@ export function useThreadActionMenu(input: {
315 changeRequest,316 changeRequest,
316 confirmThreadArchive,317 confirmThreadArchive,
317 confirmThreadDelete,318 confirmThreadDelete,
319 confirmAndUnpinThread,
318 copyBranchToClipboard,320 copyBranchToClipboard,
319 copyPathToClipboard,321 copyPathToClipboard,
320 copyThreadIdToClipboard,322 copyThreadIdToClipboard,
...@@ -328,7 +330,6 @@ export function useThreadActionMenu(input: {...@@ -328,7 +330,6 @@ export function useThreadActionMenu(input: {
328 snoozeThread,330 snoozeThread,
329 threadRef,331 threadRef,
330 timestampFormat,332 timestampFormat,
331 unpinThread,
332 unsettleThread,333 unsettleThread,
333 unsnoozeThread,334 unsnoozeThread,
334 updateThreadMetadata,335 updateThreadMetadata,
apps/web/src/hooks/useThreadActions.test.ts+55-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import { EnvironmentId, ThreadId } from "@t3tools/contracts";1import { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { describe, expect, it } from "vite-plus/test";2import { describe, expect, it } from "vite-plus/test";
33
4import { ThreadArchiveBlockedError } from "./useThreadActions";4import { requestThreadUnpinConfirmation, ThreadArchiveBlockedError } from "./useThreadActions";
55
6describe("ThreadArchiveBlockedError", () => {6describe("ThreadArchiveBlockedError", () => {
7 it("keeps the blocked thread context with the fixed message", () => {7 it("keeps the blocked thread context with the fixed message", () => {
...@@ -17,3 +17,57 @@ describe("ThreadArchiveBlockedError", () => {...@@ -17,3 +17,57 @@ describe("ThreadArchiveBlockedError", () => {
17 expect(error.message).toBe("Cannot archive a running thread.");17 expect(error.message).toBe("Cannot archive a running thread.");
18 });18 });
19});19});
20
21describe("requestThreadUnpinConfirmation", () => {
22 it("skips the dialog when confirmation is disabled", async () => {
23 let callCount = 0;
24 const result = await requestThreadUnpinConfirmation({
25 enabled: false,
26 title: "Pinned thread",
27 confirm: async () => {
28 callCount += 1;
29 return false;
30 },
31 });
32
33 expect(result).toMatchObject({ _tag: "Success", value: true });
34 expect(callCount).toBe(0);
35 });
36
37 it("degrades gracefully when dialogs are unavailable", async () => {
38 const result = await requestThreadUnpinConfirmation({
39 enabled: true,
40 title: "Pinned thread",
41 confirm: null,
42 });
43
44 expect(result).toMatchObject({ _tag: "Success", value: true });
45 });
46
47 it("uses the thread title and returns the user's decision", async () => {
48 let message = "";
49 const result = await requestThreadUnpinConfirmation({
50 enabled: true,
51 title: "Release prep",
52 confirm: async (nextMessage) => {
53 message = nextMessage;
54 return false;
55 },
56 });
57
58 expect(message).toBe(
59 'Unpin thread "Release prep"?\nThis will move the thread out of your pinned section.',
60 );
61 expect(result).toMatchObject({ _tag: "Success", value: false });
62 });
63
64 it("keeps dialog failures observable", async () => {
65 const result = await requestThreadUnpinConfirmation({
66 enabled: true,
67 title: "Pinned thread",
68 confirm: () => Promise.reject(new Error("dialog unavailable")),
69 });
70
71 expect(result._tag).toBe("Failure");
72 });
73});
apps/web/src/hooks/useThreadActions.ts+43
...@@ -136,6 +136,26 @@ export class ThreadPinReorderUnsupportedError extends Schema.TaggedErrorClass<Th...@@ -136,6 +136,26 @@ export class ThreadPinReorderUnsupportedError extends Schema.TaggedErrorClass<Th
136 }136 }
137}137}
138138
139export async function requestThreadUnpinConfirmation(input: {
140 enabled: boolean;
141 title: string;
142 confirm: ((message: string) => Promise<boolean>) | null;
143}) {
144 const { confirm } = input;
145 if (!input.enabled || confirm === null) {
146 return AsyncResult.success(true);
147 }
148
149 return settlePromise(() =>
150 confirm(
151 [
152 `Unpin thread "${input.title}"?`,
153 "This will move the thread out of your pinned section.",
154 ].join("\n"),
155 ),
156 );
157}
158
139export function useThreadActions() {159export function useThreadActions() {
140 const closeTerminal = useAtomCommand(terminalEnvironment.close);160 const closeTerminal = useAtomCommand(terminalEnvironment.close);
141 const archiveThreadMutation = useAtomCommand(threadEnvironment.archive, {161 const archiveThreadMutation = useAtomCommand(threadEnvironment.archive, {
...@@ -177,6 +197,7 @@ export function useThreadActions() {...@@ -177,6 +197,7 @@ export function useThreadActions() {
177 });197 });
178 const sidebarThreadSortOrder = useClientSettings((settings) => settings.sidebarThreadSortOrder);198 const sidebarThreadSortOrder = useClientSettings((settings) => settings.sidebarThreadSortOrder);
179 const confirmThreadDelete = useClientSettings((settings) => settings.confirmThreadDelete);199 const confirmThreadDelete = useClientSettings((settings) => settings.confirmThreadDelete);
200 const confirmThreadUnpin = useClientSettings((settings) => settings.confirmThreadUnpin);
180 const clearComposerDraftForThread = useComposerDraftStore((store) => store.clearDraftThread);201 const clearComposerDraftForThread = useComposerDraftStore((store) => store.clearDraftThread);
181 const clearProjectDraftThreadById = useComposerDraftStore(202 const clearProjectDraftThreadById = useComposerDraftStore(
182 (store) => store.clearProjectDraftThreadById,203 (store) => store.clearProjectDraftThreadById,
...@@ -590,6 +611,26 @@ export function useThreadActions() {...@@ -590,6 +611,26 @@ export function useThreadActions() {
590 [unpinThreadMutation],611 [unpinThreadMutation],
591 );612 );
592613
614 const confirmAndUnpinThread = useCallback(
615 async (target: ScopedThreadRef) => {
616 const localApi = readLocalApi();
617 const resolved = resolveThreadTarget(target);
618 const confirmationResult = await requestThreadUnpinConfirmation({
619 enabled: confirmThreadUnpin,
620 title: resolved?.thread.title ?? "this thread",
621 confirm: localApi ? (message) => localApi.dialogs.confirm(message) : null,
622 });
623 if (confirmationResult._tag === "Failure") {
624 return confirmationResult;
625 }
626 if (!confirmationResult.value) {
627 return AsyncResult.success(undefined);
628 }
629 return unpinThread(target);
630 },
631 [confirmThreadUnpin, resolveThreadTarget, unpinThread],
632 );
633
593 const reorderPinnedThread = useCallback(634 const reorderPinnedThread = useCallback(
594 async (target: ScopedThreadRef, orderKey: string) => {635 async (target: ScopedThreadRef, orderKey: string) => {
595 // Callers (the sidebar drag handler) only enable dragging on636 // Callers (the sidebar drag handler) only enable dragging on
...@@ -709,11 +750,13 @@ export function useThreadActions() {...@@ -709,11 +750,13 @@ export function useThreadActions() {
709 unsnoozeThread,750 unsnoozeThread,
710 pinThread,751 pinThread,
711 unpinThread,752 unpinThread,
753 confirmAndUnpinThread,
712 reorderPinnedThread,754 reorderPinnedThread,
713 }),755 }),
714 [756 [
715 archiveThread,757 archiveThread,
716 confirmAndDeleteThread,758 confirmAndDeleteThread,
759 confirmAndUnpinThread,
717 deleteThread,760 deleteThread,
718 pinThread,761 pinThread,
719 reorderPinnedThread,762 reorderPinnedThread,
docs/user/thread-sidebar.md+3
...@@ -4,6 +4,9 @@ Pin a thread from its context menu to keep it in the pinned section above your a...@@ -4,6 +4,9 @@ Pin a thread from its context menu to keep it in the pinned section above your a
4`mod+shift+p` pins or unpins the thread you have open. Pinned threads are shown independently of4`mod+shift+p` pins or unpins the thread you have open. Pinned threads are shown independently of
5their project, including when you connect to more than one environment.5their project, including when you connect to more than one environment.
66
7To require confirmation before unpinning, enable **Settings → General → Unpin confirmation**. The
8confirmation applies to the sidebar controls, thread menus, and the `mod+shift+p` shortcut.
9
7Pinned threads follow the auto-settle policy in **Settings → General**. Choose exactly one policy:10Pinned threads follow the auto-settle policy in **Settings → General**. Choose exactly one policy:
8settle when a pull request merges or closes, settle after a configurable period of inactivity, or11settle when a pull request merges or closes, settle after a configurable period of inactivity, or
9never auto-settle. **Never** leaves every automatic path off, so threads settle only when you choose12never auto-settle. **Never** leaves every automatic path off, so threads settle only when you choose
packages/contracts/src/settings.test.ts+6
...@@ -141,6 +141,12 @@ describe("ClientSettings sidebar", () => {...@@ -141,6 +141,12 @@ describe("ClientSettings sidebar", () => {
141 );141 );
142 });142 });
143143
144 it("keeps unpin confirmation opt-in and patchable", () => {
145 expect(decodeClientSettings({}).confirmThreadUnpin).toBe(false);
146 expect(decodeClientSettingsPatch({ confirmThreadUnpin: true }).confirmThreadUnpin).toBe(true);
147 expect(() => decodeClientSettingsPatch({ confirmThreadUnpin: "yes" })).toThrow();
148 });
149
144 it("allows auto-settle by inactivity to be disabled", () => {150 it("allows auto-settle by inactivity to be disabled", () => {
145 expect(151 expect(
146 decodeClientSettings({ sidebarAutoSettleAfterDays: null }).sidebarAutoSettleAfterDays,152 decodeClientSettings({ sidebarAutoSettleAfterDays: null }).sidebarAutoSettleAfterDays,
packages/contracts/src/settings.ts+2
...@@ -170,6 +170,7 @@ export const ClientSettingsSchema = Schema.Struct({...@@ -170,6 +170,7 @@ export const ClientSettingsSchema = Schema.Struct({
170 confirmQuit: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),170 confirmQuit: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
171 confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),171 confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
172 confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),172 confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
173 confirmThreadUnpin: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
173 dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe(174 dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe(
174 Schema.withDecodingDefault(Effect.succeed([])),175 Schema.withDecodingDefault(Effect.succeed([])),
175 ),176 ),
...@@ -913,6 +914,7 @@ export const ClientSettingsPatch = Schema.Struct({...@@ -913,6 +914,7 @@ export const ClientSettingsPatch = Schema.Struct({
913 confirmQuit: Schema.optionalKey(Schema.Boolean),914 confirmQuit: Schema.optionalKey(Schema.Boolean),
914 confirmThreadArchive: Schema.optionalKey(Schema.Boolean),915 confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
915 confirmThreadDelete: Schema.optionalKey(Schema.Boolean),916 confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
917 confirmThreadUnpin: Schema.optionalKey(Schema.Boolean),
916 diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean),918 diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean),
917 environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode),919 environmentIdentificationMode: Schema.optionalKey(EnvironmentIdentificationMode),
918 glassOpacity: Schema.optionalKey(GlassOpacity),920 glassOpacity: Schema.optionalKey(GlassOpacity),