authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-02 18:51:21-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-02 18:51:21-04:00
logfb93902ee24d4ba380508df33d25478d3baf7a72
treede5158330f547a7b48f2e15f298b2cc104114ccd
parent535557b3f785629e6f48cad40c5a4b78b3b3c5d6
signature Signed by PGP key B5690EEEBB952194

feat(web): add proactive panels (#9276)

Co-authored-by: maria-rcks <254055478+maria-rcks@users.noreply.github.com>

9 files changed, 244 insertions(+), 3 deletions(-)

apps/desktop/src/settings/DesktopClientSettings.test.ts+1
...@@ -43,6 +43,7 @@ const clientSettings: ClientSettings = {...@@ -43,6 +43,7 @@ const clientSettings: ClientSettings = {
43 glassOpacity: 80,43 glassOpacity: 80,
44 panelAnimationDurationMs: 0,44 panelAnimationDurationMs: 0,
45 planModeEnabled: false,45 planModeEnabled: false,
46 proactivePanelsEnabled: true,
46 showSkillsInSlashMenu: false,47 showSkillsInSlashMenu: false,
47 providerModelPreferences: {},48 providerModelPreferences: {},
48 sidebarProjectGroupingMode: "repository_path",49 sidebarProjectGroupingMode: "repository_path",
apps/web/src/components/ChatView.logic.test.ts+47
...@@ -40,6 +40,8 @@ import {...@@ -40,6 +40,8 @@ import {
40 codexArtifactTemplatePromptToAppend,40 codexArtifactTemplatePromptToAppend,
41 shouldDockDraftHeroForSubmission,41 shouldDockDraftHeroForSubmission,
42 shouldReleaseTimelineAnchorForToolActivity,42 shouldReleaseTimelineAnchorForToolActivity,
43 shouldOpenProactivePullRequest,
44 shouldOpenProactiveTurnDiff,
43 shouldShowBranchMismatchBanner,45 shouldShowBranchMismatchBanner,
44 shouldShowPlanFollowUpPrompt,46 shouldShowPlanFollowUpPrompt,
45 shouldWriteThreadErrorToCurrentServerThread,47 shouldWriteThreadErrorToCurrentServerThread,
...@@ -83,6 +85,51 @@ describe("agent browser close confirmation", () => {...@@ -83,6 +85,51 @@ describe("agent browser close confirmation", () => {
83 });85 });
84});86});
8587
88describe("proactive panels", () => {
89 it("opens a pull request only after a newly observed link appears", () => {
90 expect(shouldOpenProactivePullRequest(undefined, "project:repo:42")).toBe(false);
91 expect(shouldOpenProactivePullRequest(null, "project:repo:42")).toBe(true);
92 expect(shouldOpenProactivePullRequest("project:repo:42", "project:repo:42")).toBe(false);
93 expect(shouldOpenProactivePullRequest("project:repo:42", null)).toBe(false);
94 });
95
96 it("opens the diff only when the observed running turn settles", () => {
97 const turnId = TurnId.make("turn-1");
98 expect(
99 shouldOpenProactiveTurnDiff({
100 previousRunningTurnId: undefined,
101 runningTurnId: null,
102 settledTurnId: turnId,
103 turnCompleted: true,
104 }),
105 ).toBe(false);
106 expect(
107 shouldOpenProactiveTurnDiff({
108 previousRunningTurnId: turnId,
109 runningTurnId: null,
110 settledTurnId: turnId,
111 turnCompleted: true,
112 }),
113 ).toBe(true);
114 expect(
115 shouldOpenProactiveTurnDiff({
116 previousRunningTurnId: turnId,
117 runningTurnId: TurnId.make("turn-2"),
118 settledTurnId: turnId,
119 turnCompleted: true,
120 }),
121 ).toBe(false);
122 expect(
123 shouldOpenProactiveTurnDiff({
124 previousRunningTurnId: turnId,
125 runningTurnId: null,
126 settledTurnId: turnId,
127 turnCompleted: false,
128 }),
129 ).toBe(false);
130 });
131});
132
86describe("isVideoPreviewRequestCurrent", () => {133describe("isVideoPreviewRequestCurrent", () => {
87 it("rejects changed threads and replaced previews", () => {134 it("rejects changed threads and replaced previews", () => {
88 expect(isVideoPreviewRequestCurrent("thread-1", "thread-2", 1, 1)).toBe(false);135 expect(isVideoPreviewRequestCurrent("thread-1", "thread-2", 1, 1)).toBe(false);
apps/web/src/components/ChatView.logic.ts+22
...@@ -78,6 +78,28 @@ export function agentControlledBrowserCloseConfirmation(...@@ -78,6 +78,28 @@ export function agentControlledBrowserCloseConfirmation(
78 ].join("\n");78 ].join("\n");
79}79}
8080
81export function shouldOpenProactivePullRequest(
82 previousTargetKey: string | null | undefined,
83 targetKey: string | null,
84): boolean {
85 return previousTargetKey !== undefined && targetKey !== null && targetKey !== previousTargetKey;
86}
87
88export function shouldOpenProactiveTurnDiff(input: {
89 previousRunningTurnId: TurnId | null | undefined;
90 runningTurnId: TurnId | null;
91 settledTurnId: TurnId | null;
92 turnCompleted: boolean;
93}): boolean {
94 return (
95 input.previousRunningTurnId !== undefined &&
96 input.previousRunningTurnId !== null &&
97 input.runningTurnId === null &&
98 input.turnCompleted &&
99 input.settledTurnId === input.previousRunningTurnId
100 );
101}
102
81export function codexArtifactTemplatePromptToAppend(103export function codexArtifactTemplatePromptToAppend(
82 currentDraft: string,104 currentDraft: string,
83 template: CodexArtifactTemplate,105 template: CodexArtifactTemplate,
apps/web/src/components/ChatView.tsx+124-3
...@@ -361,6 +361,8 @@ import {...@@ -361,6 +361,8 @@ import {
361 shouldReleaseTimelineAnchorForToolActivity,361 shouldReleaseTimelineAnchorForToolActivity,
362 shouldShowBranchMismatchBanner,362 shouldShowBranchMismatchBanner,
363 shouldShowPlanFollowUpPrompt,363 shouldShowPlanFollowUpPrompt,
364 shouldOpenProactivePullRequest,
365 shouldOpenProactiveTurnDiff,
364 getStartedThreadModelChangeBlockReason,366 getStartedThreadModelChangeBlockReason,
365 LAST_INVOKED_SCRIPT_BY_PROJECT_KEY,367 LAST_INVOKED_SCRIPT_BY_PROJECT_KEY,
366 LastInvokedScriptByProjectSchema,368 LastInvokedScriptByProjectSchema,
...@@ -1766,6 +1768,7 @@ function ChatViewContent(props: ChatViewProps) {...@@ -1766,6 +1768,7 @@ function ChatViewContent(props: ChatViewProps) {
1766 [activeThreadEnvironmentId, activeThreadId],1768 [activeThreadEnvironmentId, activeThreadId],
1767 );1769 );
1768 const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null;1770 const activeThreadKey = activeThreadRef ? scopedThreadKey(activeThreadRef) : null;
1771 const activeThreadShell = useThreadShell(isServerThread ? activeThreadRef : null);
1769 const changeRequestSnapshotByKey = useAtomValue(threadChangeRequestSnapshotsAtom);1772 const changeRequestSnapshotByKey = useAtomValue(threadChangeRequestSnapshotsAtom);
1770 const [timelineAnchor, setTimelineAnchor] = useState<{1773 const [timelineAnchor, setTimelineAnchor] = useState<{
1771 readonly threadKey: string | null;1774 readonly threadKey: string | null;
...@@ -3665,7 +3668,16 @@ function ChatViewContent(props: ChatViewProps) {...@@ -3665,7 +3668,16 @@ function ChatViewContent(props: ChatViewProps) {
3665 );3668 );
3666 // The thread's own change request, placed against the project it belongs to. Without a3669 // The thread's own change request, placed against the project it belongs to. Without a
3667 // project there is nothing to resolve it against, so the caller falls back to the browser.3670 // project there is nothing to resolve it against, so the caller falls back to the browser.
3668 const linkedThreadPullRequest = activeThread?.linkedPullRequest ?? null;3671 const linkedThreadPullRequest = isServerThread
3672 ? (activeThreadShell?.linkedPullRequest ?? activeThread?.linkedPullRequest ?? null)
3673 : (activeThread?.linkedPullRequest ?? null);
3674 const linkedThreadPullRequestKey = linkedThreadPullRequest
3675 ? JSON.stringify([
3676 linkedThreadPullRequest.projectId,
3677 linkedThreadPullRequest.repository,
3678 linkedThreadPullRequest.number,
3679 ])
3680 : null;
3669 const activeProjectRepository = activeProject?.repositoryIdentity?.displayName ?? null;3681 const activeProjectRepository = activeProject?.repositoryIdentity?.displayName ?? null;
3670 const threadRepository = linkedThreadPullRequest?.repository ?? activeProjectRepository;3682 const threadRepository = linkedThreadPullRequest?.repository ?? activeProjectRepository;
3671 const openThreadPullRequest = useCallback(3683 const openThreadPullRequest = useCallback(
...@@ -3708,6 +3720,117 @@ function ChatViewContent(props: ChatViewProps) {...@@ -3708,6 +3720,117 @@ function ChatViewContent(props: ChatViewProps) {
3708 },3720 },
3709 [activeProject, activeProjectRepository, activeThreadRef, supportsPullRequests],3721 [activeProject, activeProjectRepository, activeThreadRef, supportsPullRequests],
3710 );3722 );
3723 const proactiveTurnObservationRef = useRef<{
3724 threadKey: string;
3725 runningTurnId: TurnId | null;
3726 } | null>(null);
3727 const proactivePullRequestObservationRef = useRef<{
3728 threadKey: string;
3729 targetKey: string | null;
3730 } | null>(null);
3731
3732 useEffect(() => {
3733 if (!isServerThread || activeThreadKey === null || activeThreadRef === null) {
3734 proactiveTurnObservationRef.current = null;
3735 return;
3736 }
3737 if (!clientSettingsHydrated || threadDetailLoading) {
3738 return;
3739 }
3740
3741 const previousObservation = proactiveTurnObservationRef.current;
3742 const observingSameThread = previousObservation?.threadKey === activeThreadKey;
3743 const previousRunningTurnId = observingSameThread
3744 ? previousObservation.runningTurnId
3745 : undefined;
3746 const settledTurnId = latestTurnSettled ? (activeLatestTurn?.turnId ?? null) : null;
3747 const newlyCompletedTurnId = shouldOpenProactiveTurnDiff({
3748 previousRunningTurnId,
3749 runningTurnId: activeRunningTurnId,
3750 settledTurnId,
3751 turnCompleted: activeLatestTurn?.state === "completed",
3752 })
3753 ? settledTurnId
3754 : null;
3755 const eligibleCompletion =
3756 settings.proactivePanelsEnabled && !shouldUseRightPanelSheet && newlyCompletedTurnId !== null;
3757 const checkpointReady =
3758 eligibleCompletion &&
3759 activeThread?.checkpoints.some((checkpoint) => checkpoint.turnId === newlyCompletedTurnId) ===
3760 true;
3761 const shouldOpenTurn = checkpointReady && gitStatusQuery.data?.isRepo === true;
3762 const shouldDeferCompletion =
3763 eligibleCompletion && !shouldOpenTurn && gitStatusQuery.data?.isRepo !== false;
3764 proactiveTurnObservationRef.current = {
3765 threadKey: activeThreadKey,
3766 runningTurnId: shouldDeferCompletion ? (previousRunningTurnId ?? null) : activeRunningTurnId,
3767 };
3768 if (!shouldOpenTurn || newlyCompletedTurnId === null) return;
3769
3770 useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId);
3771 useRightPanelStore.getState().open(activeThreadRef, "diff");
3772 onDiffPanelOpen?.();
3773 }, [
3774 activeThread?.checkpoints,
3775 activeLatestTurn?.turnId,
3776 activeLatestTurn?.state,
3777 activeRunningTurnId,
3778 activeThreadKey,
3779 activeThreadRef,
3780 clientSettingsHydrated,
3781 gitStatusQuery.data?.isRepo,
3782 isServerThread,
3783 latestTurnSettled,
3784 onDiffPanelOpen,
3785 settings.proactivePanelsEnabled,
3786 shouldUseRightPanelSheet,
3787 threadDetailLoading,
3788 ]);
3789
3790 useEffect(() => {
3791 if (!isServerThread || activeThreadKey === null || activeThreadRef === null) {
3792 proactivePullRequestObservationRef.current = null;
3793 return;
3794 }
3795 if (!clientSettingsHydrated || threadDetailLoading) {
3796 return;
3797 }
3798
3799 const previousObservation = proactivePullRequestObservationRef.current;
3800 const observingSameThread = previousObservation?.threadKey === activeThreadKey;
3801 const previousTargetKey = observingSameThread ? previousObservation.targetKey : undefined;
3802 const newlyLinkedPullRequest = shouldOpenProactivePullRequest(
3803 previousTargetKey,
3804 linkedThreadPullRequestKey,
3805 );
3806 const eligibleLink =
3807 settings.proactivePanelsEnabled && !shouldUseRightPanelSheet && newlyLinkedPullRequest;
3808 const shouldOpenLink =
3809 eligibleLink &&
3810 pullRequestsCapabilityKnown &&
3811 supportsPullRequests &&
3812 linkedThreadPullRequest !== null;
3813 const shouldDeferLink = eligibleLink && !pullRequestsCapabilityKnown;
3814 proactivePullRequestObservationRef.current = {
3815 threadKey: activeThreadKey,
3816 targetKey: shouldDeferLink ? (previousTargetKey ?? null) : linkedThreadPullRequestKey,
3817 };
3818 if (!shouldOpenLink || linkedThreadPullRequest === null) return;
3819
3820 useRightPanelStore.getState().openPullRequest(activeThreadRef, linkedThreadPullRequest);
3821 }, [
3822 activeThreadKey,
3823 activeThreadRef,
3824 clientSettingsHydrated,
3825 isServerThread,
3826 linkedThreadPullRequest,
3827 linkedThreadPullRequestKey,
3828 pullRequestsCapabilityKnown,
3829 settings.proactivePanelsEnabled,
3830 shouldUseRightPanelSheet,
3831 supportsPullRequests,
3832 threadDetailLoading,
3833 ]);
3711 const togglePreviewPanel = useCallback(() => {3834 const togglePreviewPanel = useCallback(() => {
3712 if (!activeThreadRef || !isPreviewSupportedInRuntime()) return;3835 if (!activeThreadRef || !isPreviewSupportedInRuntime()) return;
3713 if (previewPanelOpen) {3836 if (previewPanelOpen) {
...@@ -4612,8 +4735,6 @@ function ChatViewContent(props: ChatViewProps) {...@@ -4612,8 +4735,6 @@ function ChatViewContent(props: ChatViewProps) {
4612 : null,4735 : null,
4613 [activeThreadBranch, activeWorktreePath, envMode, gitStatusQuery.data?.refName, isServerThread],4736 [activeThreadBranch, activeWorktreePath, envMode, gitStatusQuery.data?.refName, isServerThread],
4614 );4737 );
4615 // The server-projected settled state keeps the banner and sidebar in sync.
4616 const activeThreadShell = useThreadShell(isServerThread ? activeThreadRef : null);
4617 const activeComposerTasksProgress = useMemo(() => {4738 const activeComposerTasksProgress = useMemo(() => {
4618 if (!activeLatestTurn || latestTurnSettled || activePlan?.turnId !== activeLatestTurn.turnId) {4739 if (!activeLatestTurn || latestTurnSettled || activePlan?.turnId !== activeLatestTurn.turnId) {
4619 return null;4740 return null;
apps/web/src/components/settings/SettingsPanels.tsx+31
...@@ -525,6 +525,9 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -525,6 +525,9 @@ export function useSettingsRestore(onRestored?: () => void) {
525 ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace525 ...(settings.diffIgnoreWhitespace !== DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace
526 ? ["Diff whitespace changes"]526 ? ["Diff whitespace changes"]
527 : []),527 : []),
528 ...(settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled
529 ? ["Proactive panels"]
530 : []),
528 ...(settings.showSkillsInSlashMenu !== DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu531 ...(settings.showSkillsInSlashMenu !== DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu
529 ? ["Show skills in slash menu"]532 ? ["Show skills in slash menu"]
530 : []),533 : []),
...@@ -588,6 +591,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -588,6 +591,7 @@ export function useSettingsRestore(onRestored?: () => void) {
588 settings.defaultThreadEnvMode,591 settings.defaultThreadEnvMode,
589 settings.newWorktreesStartFromOrigin,592 settings.newWorktreesStartFromOrigin,
590 settings.diffIgnoreWhitespace,593 settings.diffIgnoreWhitespace,
594 settings.proactivePanelsEnabled,
591 settings.environmentIdentificationMode,595 settings.environmentIdentificationMode,
592 settings.contextWindowMeterEnabled,596 settings.contextWindowMeterEnabled,
593 settings.fontFamilyCode,597 settings.fontFamilyCode,
...@@ -683,6 +687,7 @@ export function useSettingsRestore(onRestored?: () => void) {...@@ -683,6 +687,7 @@ export function useSettingsRestore(onRestored?: () => void) {
683 timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,687 timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,
684 wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap,688 wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap,
685 diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,689 diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,
690 proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled,
686 showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu,691 showSkillsInSlashMenu: DEFAULT_UNIFIED_SETTINGS.showSkillsInSlashMenu,
687 contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled,692 contextWindowMeterEnabled: DEFAULT_UNIFIED_SETTINGS.contextWindowMeterEnabled,
688 environmentIdentificationMode: DEFAULT_UNIFIED_SETTINGS.environmentIdentificationMode,693 environmentIdentificationMode: DEFAULT_UNIFIED_SETTINGS.environmentIdentificationMode,
...@@ -2206,6 +2211,32 @@ export function GeneralSettingsPanel() {...@@ -2206,6 +2211,32 @@ export function GeneralSettingsPanel() {
2206 }2211 }
2207 />2212 />
22082213
2214 <SettingsRow
2215 {...searchableSetting("proactive-panels")}
2216 description="Automatically open the linked pull request when it appears and the turn diff when agent work finishes."
2217 resetAction={
2218 settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? (
2219 <SettingResetButton
2220 label="proactive panels"
2221 onClick={() =>
2222 updateSettings({
2223 proactivePanelsEnabled: DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled,
2224 })
2225 }
2226 />
2227 ) : null
2228 }
2229 control={
2230 <Switch
2231 checked={settings.proactivePanelsEnabled}
2232 onCheckedChange={(checked) =>
2233 updateSettings({ proactivePanelsEnabled: Boolean(checked) })
2234 }
2235 aria-label="Proactive panels"
2236 />
2237 }
2238 />
2239
2209 <SettingsRow2240 <SettingsRow
2210 {...searchableSetting("skills-in-slash-menu")}2241 {...searchableSetting("skills-in-slash-menu")}
2211 description="Also include skills in the / command menu. Skills always appear when you type $."2242 description="Also include skills in the / command menu. Skills always appear when you type $."
apps/web/src/components/settings/settingsSearch.ts+6
...@@ -184,6 +184,12 @@ export const SETTINGS_SEARCH_ITEMS = [...@@ -184,6 +184,12 @@ export const SETTINGS_SEARCH_ITEMS = [
184 to: "/settings/general",184 to: "/settings/general",
185 searchTerms: ["diff ignore spaces edits default"],185 searchTerms: ["diff ignore spaces edits default"],
186 },186 },
187 {
188 id: "proactive-panels",
189 title: "Proactive panels",
190 to: "/settings/general",
191 searchTerms: ["automatically open diff pull request pr right panel agent completion"],
192 },
187 {193 {
188 id: "skills-in-slash-menu",194 id: "skills-in-slash-menu",
189 title: "Show skills in slash menu",195 title: "Show skills in slash menu",
docs/user/source-control.md+2
...@@ -46,6 +46,8 @@ T3 Code works with the platforms your team already uses:...@@ -46,6 +46,8 @@ T3 Code works with the platforms your team already uses:
46 time, or change size46 time, or change size
47- While working in a thread, open linked reviews in the same compact right-panel tabs without47- While working in a thread, open linked reviews in the same compact right-panel tabs without
48 leaving the conversation48 leaving the conversation
49- Enable **Settings → General → Proactive panels** to open a newly linked review automatically and
50 switch to the completed turn's diff when agent work finishes
49- Open the review directly in your browser with one click51- Open the review directly in your browser with one click
50- If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use52- If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use
51 **Open on GitHub** in the error view53 **Open on GitHub** in the error view
packages/contracts/src/settings.test.ts+9
...@@ -67,6 +67,15 @@ describe("ClientSettings word wrap", () => {...@@ -67,6 +67,15 @@ describe("ClientSettings word wrap", () => {
67 });67 });
68});68});
6969
70describe("ClientSettings proactive panels", () => {
71 it("is opt-in and accepts client-local updates", () => {
72 expect(decodeClientSettings({}).proactivePanelsEnabled).toBe(false);
73 expect(decodeClientSettingsPatch({ proactivePanelsEnabled: true }).proactivePanelsEnabled).toBe(
74 true,
75 );
76 });
77});
78
70describe("ClientSettings quit confirmation", () => {79describe("ClientSettings quit confirmation", () => {
71 it("defaults to hold", () => {80 it("defaults to hold", () => {
72 expect(decodeClientSettings({}).confirmQuit).toBe("hold");81 expect(decodeClientSettings({}).confirmQuit).toBe("hold");
packages/contracts/src/settings.ts+2
...@@ -299,6 +299,7 @@ export const ClientSettingsSchema = Schema.Struct({...@@ -299,6 +299,7 @@ export const ClientSettingsSchema = Schema.Struct({
299 // Legacy context window meter. The composer hides it by default; users who299 // Legacy context window meter. The composer hides it by default; users who
300 // still want the old usage indicator can restore it from Settings.300 // still want the old usage indicator can restore it from Settings.
301 contextWindowMeterEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),301 contextWindowMeterEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
302 proactivePanelsEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
302 showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),303 showSkillsInSlashMenu: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
303 // Legacy sidebar (the original per-project tree). Deliberately a fresh key304 // Legacy sidebar (the original per-project tree). Deliberately a fresh key
304 // (was `sidebarV2Enabled` + `sidebarV2ConfiguredByUser`): decoding drops the305 // (was `sidebarV2Enabled` + `sidebarV2ConfiguredByUser`): decoding drops the
...@@ -1029,6 +1030,7 @@ export const ClientSettingsPatch = Schema.Struct({...@@ -1029,6 +1030,7 @@ export const ClientSettingsPatch = Schema.Struct({
1029 ),1030 ),
1030 planModeEnabled: Schema.optionalKey(Schema.Boolean),1031 planModeEnabled: Schema.optionalKey(Schema.Boolean),
1031 contextWindowMeterEnabled: Schema.optionalKey(Schema.Boolean),1032 contextWindowMeterEnabled: Schema.optionalKey(Schema.Boolean),
1033 proactivePanelsEnabled: Schema.optionalKey(Schema.Boolean),
1032 showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean),1034 showSkillsInSlashMenu: Schema.optionalKey(Schema.Boolean),
1033 legacySidebarEnabled: Schema.optionalKey(Schema.Boolean),1035 legacySidebarEnabled: Schema.optionalKey(Schema.Boolean),
1034 sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),1036 sidebarProjectGroupingMode: Schema.optionalKey(SidebarProjectGroupingMode),