authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-04-02 11:24:24-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-02 11:24:24-07:00
log9ea443d73afc72cf9fa617bdc1344e67913e4c63
tree6ef41cc03f794a7a26968d9ef8d240d0647a78cb
parent60f7ae86bbeaa95ebdfeb0fd83edc564119258df
signature Signed by PGP key B5690EEEBB952194

Split PR creation into granular progress stages (#1694)


4 files changed, 61 insertions(+), 7 deletions(-)

apps/server/src/git/Layers/GitManager.test.ts+11-1
......@@ -2739,7 +2739,17 @@ it.layer(GitManagerTestLayer)("GitManager", (it) => {
27392739 expect.objectContaining({
27402740 kind: "phase_started",
27412741 phase: "pr",
2742 label: "Creating PR...",
2742 label: "Preparing PR...",
2743 }),
2744 expect.objectContaining({
2745 kind: "phase_started",
2746 phase: "pr",
2747 label: "Generating PR content...",
2748 }),
2749 expect.objectContaining({
2750 kind: "phase_started",
2751 phase: "pr",
2752 label: "Creating GitHub pull request...",
27432753 }),
27442754 ]);
27452755 }),
apps/server/src/git/Layers/GitManager.ts+16-2
......@@ -37,6 +37,7 @@ const STATUS_RESULT_CACHE_TTL = Duration.seconds(1);
3737const STATUS_RESULT_CACHE_CAPACITY = 2_048;
3838type StripProgressContext<T> = T extends any ? Omit<T, "actionId" | "cwd" | "action"> : never;
3939type GitActionProgressPayload = StripProgressContext<GitActionProgressEvent>;
40type GitActionProgressEmitter = (event: GitActionProgressPayload) => Effect.Effect<void, never>;
4041
4142interface OpenPrInfo {
4243 number: number;
......@@ -1200,6 +1201,7 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
12001201 modelSelection: ModelSelection,
12011202 cwd: string,
12021203 fallbackBranch: string | null,
1204 emit: GitActionProgressEmitter,
12031205 ) {
12041206 const details = yield* gitCore.statusDetails(cwd);
12051207 const branch = details.branch ?? fallbackBranch;
......@@ -1234,6 +1236,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
12341236 }
12351237
12361238 const baseBranch = yield* resolveBaseBranch(cwd, branch, details.upstreamRef, headContext);
1239 yield* emit({
1240 kind: "phase_started",
1241 phase: "pr",
1242 label: "Generating PR content...",
1243 });
12371244 const rangeContext = yield* gitCore.readRangeContext(cwd, baseBranch);
12381245
12391246 const generated = yield* textGeneration.generatePrContent({
......@@ -1254,6 +1261,11 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
12541261 gitManagerError("runPrStep", "Failed to write pull request body temp file.", cause),
12551262 ),
12561263 );
1264 yield* emit({
1265 kind: "phase_started",
1266 phase: "pr",
1267 label: "Creating GitHub pull request...",
1268 });
12571269 yield* gitHubCli
12581270 .createPullRequest({
12591271 cwd,
......@@ -1612,11 +1624,13 @@ export const makeGitManager = Effect.fn("makeGitManager")(function* () {
16121624 .emit({
16131625 kind: "phase_started",
16141626 phase: "pr",
1615 label: "Creating PR...",
1627 label: "Preparing PR...",
16161628 })
16171629 .pipe(
16181630 Effect.tap(() => Ref.set(currentPhase, Option.some("pr"))),
1619 Effect.flatMap(() => runPrStep(modelSelection, input.cwd, currentBranch)),
1631 Effect.flatMap(() =>
1632 runPrStep(modelSelection, input.cwd, currentBranch, progress.emit),
1633 ),
16201634 )
16211635 : { status: "skipped_not_requested" as const };
16221636
apps/web/src/components/GitActionsControl.logic.test.ts+27-2
......@@ -877,7 +877,12 @@ describe("buildGitActionProgressStages", () => {
877877 pushTarget: "origin/feature/test",
878878 shouldPushBeforePr: true,
879879 });
880 assert.deepEqual(stages, ["Pushing to origin/feature/test...", "Creating PR..."]);
880 assert.deepEqual(stages, [
881 "Pushing to origin/feature/test...",
882 "Preparing PR...",
883 "Generating PR content...",
884 "Creating GitHub pull request...",
885 ]);
881886 });
882887
883888 it("shows only PR progress when create-pr can skip the push", () => {
......@@ -887,7 +892,11 @@ describe("buildGitActionProgressStages", () => {
887892 hasWorkingTreeChanges: false,
888893 shouldPushBeforePr: false,
889894 });
890 assert.deepEqual(stages, ["Creating PR..."]);
895 assert.deepEqual(stages, [
896 "Preparing PR...",
897 "Generating PR content...",
898 "Creating GitHub pull request...",
899 ]);
891900 });
892901
893902 it("includes commit stages for commit+push when working tree is dirty", () => {
......@@ -903,6 +912,22 @@ describe("buildGitActionProgressStages", () => {
903912 "Pushing to origin/feature/test...",
904913 ]);
905914 });
915
916 it("includes granular PR stages for commit+push+PR actions", () => {
917 const stages = buildGitActionProgressStages({
918 action: "commit_push_pr",
919 hasCustomCommitMessage: true,
920 hasWorkingTreeChanges: true,
921 pushTarget: "origin/feature/test",
922 });
923 assert.deepEqual(stages, [
924 "Committing...",
925 "Pushing to origin/feature/test...",
926 "Preparing PR...",
927 "Generating PR content...",
928 "Creating GitHub pull request...",
929 ]);
930 });
906931});
907932
908933describe("resolveThreadBranchUpdate", () => {
apps/web/src/components/GitActionsControl.logic.ts+7-2
......@@ -47,12 +47,17 @@ export function buildGitActionProgressStages(input: {
4747}): string[] {
4848 const branchStages = input.featureBranch ? ["Preparing feature branch..."] : [];
4949 const pushStage = input.pushTarget ? `Pushing to ${input.pushTarget}...` : "Pushing...";
50 const prStages = [
51 "Preparing PR...",
52 "Generating PR content...",
53 "Creating GitHub pull request...",
54 ];
5055
5156 if (input.action === "push") {
5257 return [pushStage];
5358 }
5459 if (input.action === "create_pr") {
55 return input.shouldPushBeforePr ? [pushStage, "Creating PR..."] : ["Creating PR..."];
60 return input.shouldPushBeforePr ? [pushStage, ...prStages] : prStages;
5661 }
5762
5863 const shouldIncludeCommitStages = input.action === "commit" || input.hasWorkingTreeChanges;
......@@ -67,7 +72,7 @@ export function buildGitActionProgressStages(input: {
6772 if (input.action === "commit_push") {
6873 return [...branchStages, ...commitStages, pushStage];
6974 }
70 return [...branchStages, ...commitStages, pushStage, "Creating PR..."];
75 return [...branchStages, ...commitStages, pushStage, ...prStages];
7176}
7277
7378export function buildMenuItems(