| author | |
| committer | |
| log | d115a96763b76d00f06b06272688cf68eebe8206 |
| tree | a1d4d4890b069878381fdffb3749e0314f48ba91 |
| parent | bc03c3640d6d3bb44e5fb477bfd78d7484cd0e00 |
| signature | Signed by PGP key B5690EEEBB952194 |
5 files changed, 109 insertions(+), 11 deletions(-)
apps/web/src/components/ChatView.logic.test.ts+75| ... | ... | @@ -41,6 +41,7 @@ import { |
| 41 | 41 | resolveComposerInteractionMode, |
| 42 | 42 | resolveComposerProviderSelection, |
| 43 | 43 | resolveDraftPromotionNavigationTarget, |
| 44 | resolveProactiveTurnDiffAction, | |
| 44 | 45 | resolveThreadMetadataUpdateForNextTurn, |
| 45 | 46 | resolveSendEnvMode, |
| 46 | 47 | resolveDraftHeroState, |
| ... | ... | @@ -159,6 +160,80 @@ describe("proactive panels", () => { |
| 159 | 160 | }), |
| 160 | 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 | }); |
| 163 | 238 | |
| 164 | 239 | describe("toolGroupConsumesUpwardNavigation", () => { |
apps/web/src/components/ChatView.logic.ts+18| ... | ... | @@ -121,6 +121,24 @@ export function shouldOpenProactiveTurnDiff(input: { |
| 121 | 121 | ); |
| 122 | 122 | } |
| 123 | 123 | |
| 124 | export 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 | ||
| 124 | 142 | export function codexArtifactTemplatePromptToAppend( |
| 125 | 143 | currentDraft: string, |
| 126 | 144 | template: CodexArtifactTemplate, |
apps/web/src/components/ChatView.tsx+14-9| ... | ... | @@ -385,6 +385,7 @@ import { |
| 385 | 385 | resolveComposerInteractionMode, |
| 386 | 386 | resolveComposerProviderSelection, |
| 387 | 387 | resolveDraftHeroState, |
| 388 | resolveProactiveTurnDiffAction, | |
| 388 | 389 | resolveThreadMetadataUpdateForNextTurn, |
| 389 | 390 | resolveSendEnvMode, |
| 390 | 391 | revokeBlobPreviewUrl, |
| ... | ... | @@ -3922,18 +3923,21 @@ export default function ChatView(props: ChatViewProps) { |
| 3922 | 3923 | : null; |
| 3923 | 3924 | const eligibleCompletion = |
| 3924 | 3925 | 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"; | |
| 3932 | 3936 | proactiveTurnObservationRef.current = { |
| 3933 | 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; | |
| 3937 | 3941 | |
| 3938 | 3942 | useDiffPanelStore.getState().selectTurn(activeThreadRef, newlyCompletedTurnId); |
| 3939 | 3943 | useRightPanelStore.getState().open(activeThreadRef, "diff"); |
| ... | ... | @@ -3945,6 +3949,7 @@ export default function ChatView(props: ChatViewProps) { |
| 3945 | 3949 | activeRunningTurnId, |
| 3946 | 3950 | activeThreadKey, |
| 3947 | 3951 | activeThreadRef, |
| 3952 | activeRightPanelSurface?.kind, | |
| 3948 | 3953 | clientSettingsHydrated, |
| 3949 | 3954 | gitStatusQuery.data?.isRepo, |
| 3950 | 3955 | isServerThread, |
apps/web/src/components/settings/SettingsPanels.tsx+1-1| ... | ... | @@ -2308,7 +2308,7 @@ export function GeneralSettingsPanel() { |
| 2308 | 2308 | |
| 2309 | 2309 | <SettingsRow |
| 2310 | 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 | 2312 | resetAction={ |
| 2313 | 2313 | settings.proactivePanelsEnabled !== DEFAULT_UNIFIED_SETTINGS.proactivePanelsEnabled ? ( |
| 2314 | 2314 | <SettingResetButton |
docs/user/source-control.md+1-1| ... | ... | @@ -64,7 +64,7 @@ T3 Code works with the platforms your team already uses: |
| 64 | 64 | changed files as folders and jump straight to any of them. The toolbar toggle remembers your |
| 65 | 65 | choice. |
| 66 | 66 | - 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 | |
| 68 | 68 | - Open the review directly in your browser with one click |
| 69 | 69 | - If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use |
| 70 | 70 | **Open on GitHub** in the error view |