| 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,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 | }); |
| 163 | 238 | ||
| 164 | describe("toolGroupConsumesUpwardNavigation", () => { | 239 | describe("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 | } |
| 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 | export function codexArtifactTemplatePromptToAppend( | 142 | export 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; |
| 3937 | 3941 | ||
| 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() { |
| 2308 | 2308 | ||
| 2309 | <SettingsRow | 2309 | <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 | <SettingResetButton | 2314 | <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 your | 64 | 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 and | 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 | - Open the review directly in your browser with one click | 68 | - 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, use | 69 | - If T3 Code cannot load a GitHub pull request, including when GitHub rate limits requests, use |
| 70 | **Open on GitHub** in the error view | 70 | **Open on GitHub** in the error view |