10 KiB
APP-3922: Create PR Dialog — Tech Spec
Problem
APP-3920 unified commit, push, and publish into a single GitDialog view and collapsed the event contract to Completed | Cancelled (each mode owns its own toasts and error messaging). The git operations button can reach PrimaryGitActionMode::CreatePr (everything pushed, no PR, not on main), but OpenCreatePrDialog and CommitAndCreatePr actions were stubbed as TODOs. This branch adds the PR dialog as a third mode alongside commit and push, wires the "Commit and create PR" chain in commit mode, and includes a few related fixes.
Relevant Code
app/src/code_review/git_dialog/pr.rs— new submodule for theCreatePrmodeapp/src/code_review/git_dialog/mod.rs— extended withCreatePr(PrState),Pr(PrSubAction),new_for_pr(), and mode dispatchapp/src/code_review/git_dialog/commit.rs— extended withCommitAndCreatePrintent,allow_create_pr: boolparameter, third intent button (hidden when the intent isn't meaningful), a privateCommitOutcomeenum for confirm results, and a chainedrun_commit → run_push → create_prasyncapp/src/code_review/code_review_view.rs—open_pr_dialog(),allow_create_prcomputed and passed throughopen_commit_dialog(),OpenCreatePrDialog/CommitAndCreatePraction handlers,update_git_operations_uirefresh onNewDiffsComputedapp/src/util/git.rs—get_branch_diff_entries()(branch-level numstat diff).create_pr()andPrInfoalready existed.
Current State
GitDialog owns commit and push modes with a shared shape: per-mode state struct, body renderer, confirm async, and dispatch in mod.rs. Each mode calls show_toast / user_facing_git_error (declared in git_dialog/mod.rs) on success and failure, then emits GitDialogEvent::Completed. The parent closes the dialog and refreshes metadata; it no longer knows anything about outcomes. gh CLI helpers (run_gh_command, get_pr_for_branch, PrInfo, create_pr) already exist.
Proposed Changes
1. PR mode for GitDialog (git_dialog/pr.rs)
A new submodule, following the same shape as commit.rs and push.rs.
State:
pub struct PrState {
file_changes: Vec<FileChangeEntry>,
changes_expanded: bool,
summary_mouse_state: MouseStateHandle,
changes_scroll_state: ClippedScrollStateHandle,
}
Sub-action:
pub enum PrSubAction {
ToggleChangesExpanded,
}
Body: branch header + "Changes" section with aggregate stats (file count, +additions, -deletions) and expandable per-file list (scrollable, max 130px). Uses the shared render_branch_section / render_chevron_icon / render_file_list helpers in git_dialog/mod.rs.
Constructor: pr::new_state(repo_path, ctx) spawns get_branch_diff_entries to populate file_changes.
Confirm: pr::start_confirm spawns create_pr(&repo_path).
- On success: calls
show_pr_created_toast(&pr_info, ctx)(see below). - On failure: logs the raw error and calls
show_toast(user_facing_git_error(&err), ctx). - Either way emits
GitDialogEvent::Completed. Toast helper:pr::show_pr_created_toast(pr_info, ctx)— ephemeralDismissibleToastwith message"PR successfully created."and a clickable "Open PR"ToastLinkpointing atpr_info.url. Declaredpub(super)socommit.rscan reuse it for theCommitAndCreatePrchain. Labels/icon: title = "Create pull request"; confirm button = "Create PR" /Icon::Github; loading = "Creating…".
2. GitDialog mode dispatch (git_dialog/mod.rs)
- New variant
GitDialogMode::CreatePr(PrState) - New variant
GitDialogAction::Pr(PrSubAction) - New constructor
GitDialog::new_for_pr(repo_path, branch_name, ctx) - Title / body / focus / confirm / sub-action dispatch extended for
CreatePr new_for_commitsignature grows anallow_create_pr: boolparameter so commit mode can hide its "Commit and create PR" button when the intent isn't meaningful (existing PR or main branch). The caller encodes both conditions into the single boolean so the dialog doesn't need to know the underlying reasons.
3. CommitAndCreatePr intent (git_dialog/commit.rs)
- Adds
CommitIntent::CommitAndCreatePrvariant confirm_label_for/confirm_icon_for/loading_label_forextendedCommitStategainscommit_and_create_pr_button: Option<ViewHandle<ActionButton>>—Nonewhenallow_create_pris false (existing PR or main branch)new_statetakesallow_create_pr: bool. When false:- The "Commit and create PR" button is omitted entirely (not just disabled)
- A
debug_assert!catches callers that dispatchedCommitAndCreatePranyway; in release the subsequentcreate_prcall surfaces the realgherror via the normal failure path rather than silently rewriting the intent
apply_intent_selectorandrender_intent_buttonsskip the third button when it'sNone- A private
CommitOutcome { Committed | Pushed | PrCreated(PrInfo) }enum represents what actually ran, keeping "which stages fired" decoupled from the user's selected intent so the callback can't drift out of sync with the async body. start_confirmchainsrun_commit→run_push(forCommitAndPushorCommitAndCreatePr) →create_pr(forCommitAndCreatePr) in a singlectx.spawn, returning aCommitOutcome- Success toasts by outcome:
Committed→"Changes successfully committed."Pushed→"Changes committed and pushed."PrCreated(pr)→show_pr_created_toast(&pr, ctx)(same link-bearing toast as standalone PR creation)
- Failure →
show_toast(user_facing_git_error(...), ctx) - Either way emits
GitDialogEvent::Completed
4. Code review view integration (code_review_view.rs)
open_pr_dialog(ctx)— uses the existingprepare_git_dialog/attach_git_dialoghelpers; constructsGitDialog::new_for_pr(...).open_commit_dialog(intent, ctx)— computesallow_create_pr = pr_info.is_none() && !is_on_main_branch()fromdiff_state_modeland passes it through toGitDialog::new_for_commit, so the commit dialog can hide the "Create PR" intent when it isn't meaningful.- Action wiring:
OpenCreatePrDialog→self.open_pr_dialog(ctx)(was TODO)CommitAndCreatePr→self.open_commit_dialog(CommitIntent::CommitAndCreatePr, ctx)(was TODO)
- Git operations button refresh fix:
DiffStateModelEvent::NewDiffsComputedhandler now callsupdate_git_operations_ui(ctx)so after a commit the button transitions from "Commit" → "Push" (or "Create PR") without waiting for another event. Parent still knows nothing about outcomes — itsattach_git_dialogsubscriber remains justCompleted → close + refreshandCancelled → close.
5. Git utility (util/git.rs)
get_branch_diff_entries(repo_path) — returns per-file change stats for the branch diff:
- Detects base branch via
detect_main_branch, current branch viadetect_current_branch. - Diffs
{base}..origin/{current}, falling back to{base}..HEADif the remote ref doesn't exist (e.g. branch not yet pushed). - Parses
git diff --numstatintoVec<FileChangeEntry>.
End-to-End Flows
Standalone "Create PR" flow
- User clicks "Create PR" button →
OpenCreatePrDialogaction open_pr_dialog(ctx)→GitDialog::new_for_pr(...);pr::new_statespawnsget_branch_diff_entries- Dialog renders with branch info and change summary
- User clicks "Create PR" →
GitDialogAction::Confirm→pr::start_confirmspawnscreate_pr - Confirm/cancel/close disabled, confirm label reads "Creating…"
- On success →
show_pr_created_toastfires ("PR successfully created." with "Open PR" link) → emitsCompleted→ parent closes dialog + refreshes metadata (header button becomes "PR #N") - On error → toast with friendly message → emits
Completed→ parent closes dialog + refreshes
"Commit and create PR" flow
- User selects "Commit and create PR" in the commit dialog →
CommitIntent::CommitAndCreatePr - Confirm handler chains
run_commit→run_push→create_prin a singlectx.spawn - On success →
show_pr_created_toast(&pr, ctx)→Completed→ parent closes dialog + refreshes - On failure at any stage → friendly error toast →
Completed→ parent closes dialog + refreshes
"Commit and create PR" when the intent isn't meaningful (existing PR or main branch)
open_commit_dialogcomputesallow_create_prfrom the diff state model (PR info +is_on_main_branch) and passes it toGitDialog::new_for_commit- The third intent button is omitted entirely (no disabled dead button)
- The caller is expected to not dispatch
CommitAndCreatePrin this state; adebug_assert!incommit::new_statecatches violations in dev builds, and in release the subsequentcreate_prfailure surfaces via the normal error toast path
Risks and Mitigations
gh CLI not installed or not authenticated
Currently no pre-check. create_pr fails with a descriptive error from gh. The error surfaces as a friendly toast via the unified Failed path. Follow-up: add a gh availability check before enabling the action.
Git operations button not updating after commit
Fixed by calling update_git_operations_ui in the NewDiffsComputed handler, so after a commit triggers a diff reload, the button state is re-evaluated.
Testing and Validation
- Verify PR dialog opens with correct branch name and file change stats.
- Verify expanding changes shows per-file list with correct +/- stats.
- Verify loading state (all three chrome buttons disabled) during PR creation.
- Verify success shows the "PR successfully created." toast with "Open PR" link and header updates to "PR #N".
- Verify error shows friendly error toast and dialog closes.
- Verify "Commit and create PR" chains all three operations and shows the same PR toast on success.
- Verify commit dialog omits the third button entirely when the branch already has a PR OR when on the repo's main branch.
- Verify cancel/close/ESC dismisses the dialog without side effects.
- Verify the git operations button transitions correctly across Commit → Push → Create PR → PR #N.
Follow-ups
- Add
ghCLI availability/auth check before enabling "Create PR" and "Commit and create PR" actions. - Support editing PR title and body (currently uses
--fill). - Support draft PRs.
- Mid-flight mode transitions during
CommitAndCreatePrso the dialog reflects "Committing…" → "Pushing…" → "Creating PR…" stages.