diff --git a/apps/server/src/git/Layers/GitManager.test.ts b/apps/server/src/git/Layers/GitManager.test.ts index 6fd55030fd..ce1cfe87f5 100644 --- a/apps/server/src/git/Layers/GitManager.test.ts +++ b/apps/server/src/git/Layers/GitManager.test.ts @@ -2739,7 +2739,17 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => { expect.objectContaining({ kind: "phase_started", phase: "pr", - label: "Creating PR...", + label: "Preparing PR...", + }), + expect.objectContaining({ + kind: "phase_started", + phase: "pr", + label: "Generating PR content...", + }), + expect.objectContaining({ + kind: "phase_started", + phase: "pr", + label: "Creating GitHub pull request...", }), ]); }), diff --git a/apps/server/src/git/Layers/GitManager.ts b/apps/server/src/git/Layers/GitManager.ts index ca9d562c03..20aa21aa9a 100644 --- a/apps/server/src/git/Layers/GitManager.ts +++ b/apps/server/src/git/Layers/GitManager.ts @@ -37,6 +37,7 @@ const STATUS_RESULT_CACHE_TTL = Duration.seconds(1); const STATUS_RESULT_CACHE_CAPACITY = 2_048; type StripProgressContext = T extends any ? Omit : never; type GitActionProgressPayload = StripProgressContext; +type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect; interface OpenPrInfo { number: number; @@ -1200,6 +1201,7 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { modelSelection: ModelSelection, cwd: string, fallbackBranch: string | null, + emit: GitActionProgressEmitter, ) { const details = yield* gitCore.statusDetails(cwd); const branch = details.branch ?? fallbackBranch; @@ -1234,6 +1236,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { } const baseBranch = yield* resolveBaseBranch(cwd, branch, details.upstreamRef, headContext); + yield* emit({ + kind: "phase_started", + phase: "pr", + label: "Generating PR content...", + }); const rangeContext = yield* gitCore.readRangeContext(cwd, baseBranch); const generated = yield* textGeneration.generatePrContent({ @@ -1254,6 +1261,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { gitManagerError("runPrStep", "Failed to write pull request body temp file.", cause), ), ); + yield* emit({ + kind: "phase_started", + phase: "pr", + label: "Creating GitHub pull request...", + }); yield* gitHubCli .createPullRequest({ cwd, @@ -1612,11 +1624,13 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () { .emit({ kind: "phase_started", phase: "pr", - label: "Creating PR...", + label: "Preparing PR...", }) .pipe( Effect.tap(() => Ref.set(currentPhase, Option.some("pr"))), - Effect.flatMap(() => runPrStep(modelSelection, input.cwd, currentBranch)), + Effect.flatMap(() => + runPrStep(modelSelection, input.cwd, currentBranch, progress.emit), + ), ) : { status: "skipped_not_requested" as const }; diff --git a/apps/web/src/components/GitActionsControl.logic.test.ts b/apps/web/src/components/GitActionsControl.logic.test.ts index f21c924c79..a46b552a30 100644 --- a/apps/web/src/components/GitActionsControl.logic.test.ts +++ b/apps/web/src/components/GitActionsControl.logic.test.ts @@ -877,7 +877,12 @@ describe("buildGitActionProgressStages", () => { pushTarget: "origin/feature/test", shouldPushBeforePr: true, }); - assert.deepEqual(stages, ["Pushing to origin/feature/test...", "Creating PR..."]); + assert.deepEqual(stages, [ + "Pushing to origin/feature/test...", + "Preparing PR...", + "Generating PR content...", + "Creating GitHub pull request...", + ]); }); it("shows only PR progress when create-pr can skip the push", () => { @@ -887,7 +892,11 @@ describe("buildGitActionProgressStages", () => { hasWorkingTreeChanges: false, shouldPushBeforePr: false, }); - assert.deepEqual(stages, ["Creating PR..."]); + assert.deepEqual(stages, [ + "Preparing PR...", + "Generating PR content...", + "Creating GitHub pull request...", + ]); }); it("includes commit stages for commit+push when working tree is dirty", () => { @@ -903,6 +912,22 @@ describe("buildGitActionProgressStages", () => { "Pushing to origin/feature/test...", ]); }); + + it("includes granular PR stages for commit+push+PR actions", () => { + const stages = buildGitActionProgressStages({ + action: "commit_push_pr", + hasCustomCommitMessage: true, + hasWorkingTreeChanges: true, + pushTarget: "origin/feature/test", + }); + assert.deepEqual(stages, [ + "Committing...", + "Pushing to origin/feature/test...", + "Preparing PR...", + "Generating PR content...", + "Creating GitHub pull request...", + ]); + }); }); describe("resolveThreadBranchUpdate", () => { diff --git a/apps/web/src/components/GitActionsControl.logic.ts b/apps/web/src/components/GitActionsControl.logic.ts index 80906a982b..b4b0b98b0b 100644 --- a/apps/web/src/components/GitActionsControl.logic.ts +++ b/apps/web/src/components/GitActionsControl.logic.ts @@ -47,12 +47,17 @@ export function buildGitActionProgressStages(input: { }): string[] { const branchStages = input.featureBranch ? ["Preparing feature branch..."] : []; const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing..."; + const prStages = [ + "Preparing PR...", + "Generating PR content...", + "Creating GitHub pull request...", + ]; if (input.action === "push") { return [pushStage]; } if (input.action === "create_pr") { - return input.shouldPushBeforePr ? [pushStage, "Creating PR..."] : ["Creating PR..."]; + return input.shouldPushBeforePr ? [pushStage, ...prStages] : prStages; } const shouldIncludeCommitStages = input.action === "commit" || input.hasWorkingTreeChanges; @@ -67,7 +72,7 @@ export function buildGitActionProgressStages(input: { if (input.action === "commit_push") { return [...branchStages, ...commitStages, pushStage]; } - return [...branchStages, ...commitStages, pushStage, "Creating PR..."]; + return [...branchStages, ...commitStages, pushStage, ...prStages]; } export function buildMenuItems(