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 {
4141 resolveComposerInteractionMode,
4242 resolveComposerProviderSelection,
4343 resolveDraftPromotionNavigationTarget,
44 resolveProactiveTurnDiffAction,
4445 resolveThreadMetadataUpdateForNextTurn,
4546 resolveSendEnvMode,
4647 resolveDraftHeroState,
......@@ -159,6 +160,80 @@ describe("proactive panels", () => {
159160 }),
160161 ).toBe(false);
161162 });
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 });
162237});
163238
164239describe("toolGroupConsumesUpwardNavigation", () => {
apps/web/src/components/ChatView.logic.ts+18
......@@ -121,6 +121,24 @@ export function shouldOpenProactiveTurnDiff(input: {
121121 );
122122}
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
124142export function codexArtifactTemplatePromptToAppend(
125143 currentDraft: string,
126144 template: CodexArtifactTemplate,
apps/web/src/components/ChatView.tsx+14-9
......@@ -385,6 +385,7 @@ import {
385385 resolveComposerInteractionMode,
386386 resolveComposerProviderSelection,
387387 resolveDraftHeroState,
388 resolveProactiveTurnDiffAction,
388389 resolveThreadMetadataUpdateForNextTurn,
389390 resolveSendEnvMode,
390391 revokeBlobPreviewUrl,
......@@ -3922,18 +3923,21 @@ export default function ChatView(props: ChatViewProps) {
39223923 : null;
39233924 const eligibleCompletion =
39243925 settings.proactivePanelsEnabled && !shouldUseRightPanelSheet && newlyCompletedTurnId !== null;
3925 const checkpointReady =
3926 eligibleCompletion &&
3927 activeThread?.checkpoints.some((checkpoint) => checkpoint.turnId === newlyCompletedTurnId) ===
3928 true;
3929 const shouldOpenTurn = checkpointReady && gitStatusQuery.data?.isRepo === true;
3930 const shouldDeferCompletion =
3931 eligibleCompletion && !shouldOpenTurn && gitStatusQuery.data?.isRepo !== false;
3926 const completedCheckpoint = eligibleCompletion
3927 ? activeThread?.checkpoints.find((checkpoint) => checkpoint.turnId === newlyCompletedTurnId)
3928 : undefined;
3929 const diffAction = eligibleCompletion
3930 ? resolveProactiveTurnDiffAction({
3931 checkpoint: completedCheckpoint,
3932 isGitRepo: gitStatusQuery.data?.isRepo,
3933 activeSurfaceKind: activeRightPanelSurface?.kind ?? null,
3934 })
3935 : "ignore";
39323936 proactiveTurnObservationRef.current = {
39333937 threadKey: activeThreadKey,
3934 runningTurnId: shouldDeferCompletion ? (previousRunningTurnId ?? null) : activeRunningTurnId,
3938 runningTurnId: diffAction === "defer" ? (previousRunningTurnId ?? null) : activeRunningTurnId,
39353939 };
3936 if (!shouldOpenTurn || newlyCompletedTurnId === null) return;
3940 if (diffAction !== "open" || newlyCompletedTurnId === null) return;
39373941
39383942 useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId);
39393943 useRightPanelStore.getState().open(activeThreadRef, "diff");
......@@ -3945,6 +3949,7 @@ export default function ChatView(props: ChatViewProps) {
39453949 activeRunningTurnId,
39463950 activeThreadKey,
39473951 activeThreadRef,
3952 activeRightPanelSurface?.kind,
39483953 clientSettingsHydrated,
39493954 gitStatusQuery.data?.isRepo,
39503955 isServerThread,
apps/web/src/components/settings/SettingsPanels.tsx+1-1
......@@ -2308,7 +2308,7 @@ export function GeneralSettingsPanel() {
23082308
23092309 <SettingsRow
23102310 {...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."
23122312 resetAction={
23132313 settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? (
23142314 <SettingResetButton
docs/user/source-control.md+1-1
......@@ -64,7 +64,7 @@ T3 Code works with the platforms your team already uses:
6464 changed files as folders and jump straight to any of them. The toolbar toggle remembers your
6565 choice.
6666- Enable **Settings → General → Proactive panels** to open a newly linked review automatically and
67 switch to the completed turn's diff when agent work finishes
67 switch to the completed turn's diff when agent work changes files. An open review stays in front
6868- Open the review directly in your browser with one click
6969- If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use
7070 **Open on GitHub** in the error view