authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-04 15:20:07-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-04 15:20:07-04:00
logd115a96763b76d00f06b06272688cf68eebe8206
treea1d4d4890b069878381fdffb3749e0314f48ba91
parentbc03c3640d6d3bb44e5fb477bfd78d7484cd0e00
signature Signed by PGP key B5690EEEBB952194

fix(web): stop empty diffs replacing pull requests (#9753)


5 files changed, 109 insertions(+), 11 deletions(-)

apps/web/src/components/ChatView.logic.test.ts+75
...@@ -41,6 +41,7 @@ import {...@@ -41,6 +41,7 @@ import {
41 resolveComposerInteractionMode,41 resolveComposerInteractionMode,
42 resolveComposerProviderSelection,42 resolveComposerProviderSelection,
43 resolveDraftPromotionNavigationTarget,43 resolveDraftPromotionNavigationTarget,
44 resolveProactiveTurnDiffAction,
44 resolveThreadMetadataUpdateForNextTurn,45 resolveThreadMetadataUpdateForNextTurn,
45 resolveSendEnvMode,46 resolveSendEnvMode,
46 resolveDraftHeroState,47 resolveDraftHeroState,
...@@ -159,6 +160,80 @@ describe("proactive panels", () => {...@@ -159,6 +160,80 @@ describe("proactive panels", () => {
159 }),160 }),
160 ).toBe(false);161 ).toBe(false);
161 });162 });
163
164 it("opens a completed turn diff only for changed files", () => {
165 const changedCheckpoint = {
166 status: "ready",
167 files: [{ path: "src/app.ts", kind: "modified", additions: 1, deletions: 0 }],
168 } satisfies Pick<TurnDiffSummary, "status" | "files">;
169 const unchangedCheckpoint = {
170 status: "ready",
171 files: [],
172 } satisfies Pick<TurnDiffSummary, "status" | "files">;
173
174 expect(
175 resolveProactiveTurnDiffAction({
176 checkpoint: changedCheckpoint,
177 isGitRepo: true,
178 activeSurfaceKind: null,
179 }),
180 ).toBe("open");
181 expect(
182 resolveProactiveTurnDiffAction({
183 checkpoint: unchangedCheckpoint,
184 isGitRepo: true,
185 activeSurfaceKind: null,
186 }),
187 ).toBe("ignore");
188 });
189
190 it("waits for definitive checkpoint and repository state", () => {
191 const missingCheckpoint = {
192 status: "missing",
193 files: [],
194 } satisfies Pick<TurnDiffSummary, "status" | "files">;
195 const changedCheckpoint = {
196 status: "ready",
197 files: [{ path: "src/app.ts", kind: "modified", additions: 1, deletions: 0 }],
198 } satisfies Pick<TurnDiffSummary, "status" | "files">;
199
200 expect(
201 resolveProactiveTurnDiffAction({
202 checkpoint: undefined,
203 isGitRepo: true,
204 activeSurfaceKind: null,
205 }),
206 ).toBe("defer");
207 expect(
208 resolveProactiveTurnDiffAction({
209 checkpoint: missingCheckpoint,
210 isGitRepo: true,
211 activeSurfaceKind: null,
212 }),
213 ).toBe("defer");
214 expect(
215 resolveProactiveTurnDiffAction({
216 checkpoint: changedCheckpoint,
217 isGitRepo: undefined,
218 activeSurfaceKind: null,
219 }),
220 ).toBe("defer");
221 });
222
223 it("keeps an active pull request above a completed turn diff", () => {
224 const changedCheckpoint = {
225 status: "ready",
226 files: [{ path: "src/app.ts", kind: "modified", additions: 1, deletions: 0 }],
227 } satisfies Pick<TurnDiffSummary, "status" | "files">;
228
229 expect(
230 resolveProactiveTurnDiffAction({
231 checkpoint: changedCheckpoint,
232 isGitRepo: true,
233 activeSurfaceKind: "pull-request",
234 }),
235 ).toBe("ignore");
236 });
162});237});
163238
164describe("toolGroupConsumesUpwardNavigation", () => {239describe("toolGroupConsumesUpwardNavigation", () => {
apps/web/src/components/ChatView.logic.ts+18
...@@ -121,6 +121,24 @@ export function shouldOpenProactiveTurnDiff(input: {...@@ -121,6 +121,24 @@ export function shouldOpenProactiveTurnDiff(input: {
121 );121 );
122}122}
123123
124export function resolveProactiveTurnDiffAction(input: {
125 checkpoint: Pick<TurnDiffSummary, "status" | "files"> | undefined;
126 isGitRepo: boolean | undefined;
127 activeSurfaceKind: RightPanelSurface["kind"] | null;
128}): "defer" | "ignore" | "open" {
129 if (input.activeSurfaceKind === "pull-request") return "ignore";
130 if (input.checkpoint === undefined || input.checkpoint.status === "missing") return "defer";
131 if (input.isGitRepo === undefined) return "defer";
132 if (
133 !input.isGitRepo ||
134 input.checkpoint.status !== "ready" ||
135 input.checkpoint.files.length === 0
136 ) {
137 return "ignore";
138 }
139 return "open";
140}
141
124export function codexArtifactTemplatePromptToAppend(142export function codexArtifactTemplatePromptToAppend(
125 currentDraft: string,143 currentDraft: string,
126 template: CodexArtifactTemplate,144 template: CodexArtifactTemplate,
apps/web/src/components/ChatView.tsx+14-9
...@@ -385,6 +385,7 @@ import {...@@ -385,6 +385,7 @@ import {
385 resolveComposerInteractionMode,385 resolveComposerInteractionMode,
386 resolveComposerProviderSelection,386 resolveComposerProviderSelection,
387 resolveDraftHeroState,387 resolveDraftHeroState,
388 resolveProactiveTurnDiffAction,
388 resolveThreadMetadataUpdateForNextTurn,389 resolveThreadMetadataUpdateForNextTurn,
389 resolveSendEnvMode,390 resolveSendEnvMode,
390 revokeBlobPreviewUrl,391 revokeBlobPreviewUrl,
...@@ -3922,18 +3923,21 @@ export default function ChatView(props: ChatViewProps) {...@@ -3922,18 +3923,21 @@ export default function ChatView(props: ChatViewProps) {
3922 : null;3923 : null;
3923 const eligibleCompletion =3924 const eligibleCompletion =
3924 settings.proactivePanelsEnabled && !shouldUseRightPanelSheet && newlyCompletedTurnId !== null;3925 settings.proactivePanelsEnabled && !shouldUseRightPanelSheet && newlyCompletedTurnId !== null;
3925 const checkpointReady =3926 const completedCheckpoint = eligibleCompletion
3926 eligibleCompletion &&3927 ? activeThread?.checkpoints.find((checkpoint) => checkpoint.turnId === newlyCompletedTurnId)
3927 activeThread?.checkpoints.some((checkpoint) => checkpoint.turnId === newlyCompletedTurnId) ===3928 : undefined;
3928 true;3929 const diffAction = eligibleCompletion
3929 const shouldOpenTurn = checkpointReady && gitStatusQuery.data?.isRepo === true;3930 ? resolveProactiveTurnDiffAction({
3930 const shouldDeferCompletion =3931 checkpoint: completedCheckpoint,
3931 eligibleCompletion && !shouldOpenTurn && gitStatusQuery.data?.isRepo !== false;3932 isGitRepo: gitStatusQuery.data?.isRepo,
3933 activeSurfaceKind: activeRightPanelSurface?.kind ?? null,
3934 })
3935 : "ignore";
3932 proactiveTurnObservationRef.current = {3936 proactiveTurnObservationRef.current = {
3933 threadKey: activeThreadKey,3937 threadKey: activeThreadKey,
3934 runningTurnId: shouldDeferCompletion ? (previousRunningTurnId ?? null) : activeRunningTurnId,3938 runningTurnId: diffAction === "defer" ? (previousRunningTurnId ?? null) : activeRunningTurnId,
3935 };3939 };
3936 if (!shouldOpenTurn || newlyCompletedTurnId === null) return;3940 if (diffAction !== "open" || newlyCompletedTurnId === null) return;
39373941
3938 useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId);3942 useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId);
3939 useRightPanelStore.getState().open(activeThreadRef, "diff");3943 useRightPanelStore.getState().open(activeThreadRef, "diff");
...@@ -3945,6 +3949,7 @@ export default function ChatView(props: ChatViewProps) {...@@ -3945,6 +3949,7 @@ export default function ChatView(props: ChatViewProps) {
3945 activeRunningTurnId,3949 activeRunningTurnId,
3946 activeThreadKey,3950 activeThreadKey,
3947 activeThreadRef,3951 activeThreadRef,
3952 activeRightPanelSurface?.kind,
3948 clientSettingsHydrated,3953 clientSettingsHydrated,
3949 gitStatusQuery.data?.isRepo,3954 gitStatusQuery.data?.isRepo,
3950 isServerThread,3955 isServerThread,
apps/web/src/components/settings/SettingsPanels.tsx+1-1
...@@ -2308,7 +2308,7 @@ export function GeneralSettingsPanel() {...@@ -2308,7 +2308,7 @@ export function GeneralSettingsPanel() {
23082308
2309 <SettingsRow2309 <SettingsRow
2310 {...searchableSetting("proactive-panels")}2310 {...searchableSetting("proactive-panels")}
2311 description="Open linked pull requests when found and turn diffs when work finishes."2311 description="Open linked pull requests when found and turn diffs when work changes files."
2312 resetAction={2312 resetAction={
2313 settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? (2313 settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? (
2314 <SettingResetButton2314 <SettingResetButton
docs/user/source-control.md+1-1
...@@ -64,7 +64,7 @@ T3 Code works with the platforms your team already uses:...@@ -64,7 +64,7 @@ T3 Code works with the platforms your team already uses:
64 changed files as folders and jump straight to any of them. The toolbar toggle remembers your64 changed files as folders and jump straight to any of them. The toolbar toggle remembers your
65 choice.65 choice.
66- Enable **Settings → General → Proactive panels** to open a newly linked review automatically and66- Enable **Settings → General → Proactive panels** to open a newly linked review automatically and
67 switch to the completed turn's diff when agent work finishes67 switch to the completed turn's diff when agent work changes files. An open review stays in front
68- Open the review directly in your browser with one click68- Open the review directly in your browser with one click
69- If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use69- If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use
70 **Open on GitHub** in the error view70 **Open on GitHub** in the error view