Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
# APP-3923: AI-autogenerated commit messages, PR titles, and PR descriptions
|
||||
## Summary
|
||||
Pre-populate the code review git dialogs with AI-generated copy so users don't have to write commit messages, PR titles, or PR descriptions by hand. When the commit dialog opens, a draft commit message is generated from the current diff and dropped into the editor. When a PR is created (either standalone "Create PR" or the "Commit and create PR" chain), the PR title and body are generated from the branch's diff and commit history just before `gh pr create` runs.
|
||||
## Problem
|
||||
APP-3922 shipped the "Create PR" dialog and the `CommitAndCreatePr` chain, but both relied on the user to write a commit message and both passed `--fill` to `gh pr create`, which just copies the most recent commit subject and body into the PR. That produces mediocre PR titles/descriptions and still requires the user to write a good commit message manually. Writing these is low-value boilerplate users typically want to skip.
|
||||
## Goals
|
||||
- Generate a draft commit message automatically when the commit dialog opens so the user only has to review and optionally edit it.
|
||||
- Generate a PR title and body automatically at PR creation time (both flows: standalone and "Commit and create PR").
|
||||
- Fail safely: if any generation call fails, the user is told what happened and can recover (type a message manually; retry the PR).
|
||||
- Keep the user in full control: the generated commit message is always editable; the user can also clear it and type their own.
|
||||
- Make the commit confirm button's enabled state directly reflect whether a committable message exists — no silent second generation at confirm time.
|
||||
## Non-goals
|
||||
- Adding a new feature flag for this capability. `FeatureFlag::GitOperationsInCodeReview` already gates the entire git dialog surface, so autogen is only reachable inside that gate.
|
||||
- Adding an `AISettings` opt-out or an enterprise / customer-type guard for sending diffs to AI. Both are noted as explicit follow-ups (see `app/src/ai/generate_code_review_content/mod.rs` TODO) and tracked separately.
|
||||
- A preview/edit UI for the generated PR title and body — they are sent directly to `gh pr create`.
|
||||
- Regenerate button for commit messages / PR fields.
|
||||
- Draft PR support (inherited from APP-3922).
|
||||
- Editing reviewers, labels, or milestones in the dialog (inherited from APP-3922).
|
||||
## Figma
|
||||
none provided — this feature is copy-only and reuses the existing dialog layouts from APP-3920 / APP-3922.
|
||||
## User experience
|
||||
### Commit message autogeneration (commit dialog)
|
||||
When the commit dialog opens:
|
||||
- Placeholder reads `Generating commit message…`.
|
||||
- Editor buffer is empty.
|
||||
- Confirm button is disabled (no message, file changes may also still be loading).
|
||||
- An AI generation request fires immediately in the background using the current diff as input (staged + unstaged, with untracked files included as synthetic diff hunks when `include_unstaged` is true).
|
||||
On generation success:
|
||||
- If the editor is still empty (user hasn't typed anything), the generated message is inserted into the editor.
|
||||
- If the user has already typed a non-empty message, the generated draft is silently discarded — user input is never clobbered.
|
||||
- Placeholder changes to `Type a commit message` (visible only if the user later clears the buffer).
|
||||
- Confirm button becomes enabled once file changes have also loaded.
|
||||
On generation failure (network, server, or empty response):
|
||||
- Placeholder changes to `Type a commit message`.
|
||||
- No toast — autogen is best-effort background work, the user can't retry it, and the empty editor plus placeholder already communicate what happened.
|
||||
- Editor buffer stays empty.
|
||||
- Confirm button stays disabled until the user types a non-empty message.
|
||||
### PR title and body autogeneration
|
||||
Both flows generate PR title and body at confirm time, right before `gh pr create` runs:
|
||||
**Standalone "Create PR" dialog:**
|
||||
1. User clicks `Create PR` → dialog goes into loading state (`Creating…`).
|
||||
2. Compute diff vs main branch and collect commit subjects on the current branch.
|
||||
3. Generate PR title via AI.
|
||||
4. Generate PR body via AI.
|
||||
5. Run `gh pr create --title <generated> --body <generated>`.
|
||||
6. On success: standard "PR successfully created." toast with `Open PR` link (unchanged from APP-3922).
|
||||
7. On AI title/body failure: fall back to `gh pr create --fill` so the PR still gets created (with the latest commit's subject/body). On any other step's failure (diff fetch, `gh pr create` itself, etc.): dialog closes, friendly error toast (unchanged from APP-3922 — error mapping is per-call-site log + `user_facing_git_error`).
|
||||
**"Commit and create PR" chain (commit dialog with `CommitAndCreatePr` intent):**
|
||||
1. Commit runs.
|
||||
2. Push runs.
|
||||
3. Same PR-title / PR-body / `gh pr create` sequence as above.
|
||||
4. Same success toast.
|
||||
### Diff input and truncation
|
||||
- Max diff length sent to AI: 16,000 characters. Beyond that, the diff is truncated on a UTF-8 char boundary with a trailing `... (diff truncated)` marker.
|
||||
- For commit message generation with `include_unstaged = true`, untracked files are synthesised into diff hunks so the LLM has context for new-file-only commits. Per-untracked-file cap: 4,000 bytes. Binary files are detected from the first 1,024 bytes and skipped.
|
||||
- For PR generation, the diff is `{base}..origin/{current}` when the remote ref exists, falling back to `{base}..HEAD` otherwise. Commit subjects on the branch are also sent alongside the diff.
|
||||
### Editor interactions and state rules
|
||||
1. Confirm is enabled iff there is at least one file change **and** the commit message editor holds a non-empty (trimmed) string.
|
||||
2. While generation is in flight, the editor is empty, so confirm is implicitly disabled by rule (1); no separate "is autogenerating" flag is exposed.
|
||||
3. The generated draft never overwrites user input. If the user has typed anything by the time generation resolves, the draft is discarded.
|
||||
4. Clearing a successful draft after the fact leaves the placeholder `Type a commit message` visible and disables confirm until the user types.
|
||||
5. There is no confirm-time fallback regeneration for commit messages: once the open-time generation has resolved (success or failure), the user is responsible for the message.
|
||||
6. A generation failure during PR creation falls back to `gh pr create --fill` so the PR is still created (using the latest commit's subject/body). Only a failure in the PR-creation command itself aborts the flow; in that case, for the `CommitAndCreatePr` chain, the commit and push already succeeded but the PR was not created, and the user can retry via the standalone "Create PR" button.
|
||||
## Success criteria
|
||||
1. Opening the commit dialog on a branch with changes kicks off a background AI request within the same frame; the placeholder reads `Generating commit message…` until the request resolves.
|
||||
2. On generation success with an untouched editor, the generated message appears in the editor within a short time (bounded by AI latency) and the confirm button becomes enabled once file changes are loaded.
|
||||
3. On generation success with a user-typed message, the generated message is discarded and the user's text is preserved.
|
||||
4. On generation failure, the placeholder changes to `Type a commit message` (no toast), and confirm stays disabled until the user types.
|
||||
5. The confirm button is disabled whenever the editor's trimmed content is empty, regardless of whether an auto-generation is still in flight.
|
||||
6. Clearing a previously-populated AI draft flips the confirm button back to disabled and shows the `Type a commit message` placeholder.
|
||||
7. Confirming a PR (standalone or via `CommitAndCreatePr`) creates the PR with an AI-generated title and body; the user never types either.
|
||||
8. A network outage that causes PR title or body generation to fail falls back to `gh pr create --fill`; the PR is still created, using the latest commit's subject/body as title/body.
|
||||
9. No user-facing AI-related UI appears outside the commit dialog and the two PR-creation confirm paths.
|
||||
## Validation
|
||||
- Open the commit dialog on a branch with a non-trivial diff and verify the `Generating commit message…` placeholder, followed by a populated editor with a reasonable draft.
|
||||
- Type into the editor before the AI responds; verify the typed text is preserved and the draft is discarded.
|
||||
- Disable networking, open the commit dialog, verify the fallback placeholder (no toast) and that confirm stays disabled until the user types.
|
||||
- Populate the editor via AI, clear it, verify confirm disables and the `Type a commit message` placeholder re-appears.
|
||||
- On a pushed branch with no existing PR, click `Create PR` and verify the created PR has a generated title and body (not `--fill`-derived).
|
||||
- On a branch with pending changes, select `Commit and create PR` in the commit dialog, let it run, and verify commit + push + PR creation, with the PR body and title generated.
|
||||
- Simulate a failure in PR body generation (e.g. mid-flight network drop) for the `CommitAndCreatePr` flow; verify the PR still gets created via `gh pr create --fill` and the usual success toast appears.
|
||||
## Open questions
|
||||
- Should we add a regenerate button for the commit message draft? Currently the user can clear the field and type their own, but they cannot re-trigger AI generation without re-opening the dialog.
|
||||
- Should PR title and body be editable before `gh pr create` fires? Current design sends them blind.
|
||||
- AI settings opt-out and enterprise customer-type guard are explicit follow-ups; see `app/src/ai/generate_code_review_content/mod.rs`.
|
||||
@@ -0,0 +1,184 @@
|
||||
# APP-3923: AI-autogenerated commit messages and PR metadata — Tech Spec
|
||||
Product spec: `specs/APP-3923/PRODUCT.md`
|
||||
Parent stack: APP-3918 (header button) → APP-3920 (commit/push dialog) → APP-3922 (create-PR dialog) → **APP-3923** (this branch).
|
||||
## Problem
|
||||
APP-3922 landed the `GitDialog::CreatePr` mode and the `CommitIntent::CommitAndCreatePr` chain, both of which called `gh pr create --fill`. `--fill` just copies the latest commit subject/body into the PR, and commit messages themselves had to be typed manually. We want AI-generated copy for all three: commit message (at dialog open time), PR title and PR body (at confirm time).
|
||||
The work has three logical layers that needed new plumbing:
|
||||
1. A server endpoint for AI generation of review-adjacent content.
|
||||
2. A client-side block-service method and request/response types.
|
||||
3. Git helpers that produce the LLM input (diff + branch commit messages).
|
||||
Plus editor-state changes in `commit.rs` so that an autogenerated draft is discoverable, overridable, and failure-visible.
|
||||
## Relevant code
|
||||
- `app/src/ai/generate_code_review_content/api.rs` — new `GenerateCodeReviewContentRequest` / `Response` + `OutputType` enum
|
||||
- `app/src/ai/generate_code_review_content/mod.rs` — module root (plus the follow-up TODO)
|
||||
- `app/src/ai/mod.rs:44` — registers the new module
|
||||
- `app/src/server/server_api/block.rs:54,175-196` — new `BlockClient::generate_code_review_content` trait method and `ServerApi` impl, following the pattern of `generate_shared_block_title`
|
||||
- `app/src/util/git.rs:445-527` — `MAX_DIFF_CHARS_FOR_AI`, `MAX_UNTRACKED_FILE_BYTES`, `BINARY_CHECK_BYTES`, `MAX_PR_TITLE_BYTES`, `truncate_on_char_boundary`, `get_diff_for_commit_message`
|
||||
- `app/src/util/git.rs:677-721` — `get_diff_for_pr`, `get_branch_commit_messages`
|
||||
- `app/src/util/git.rs:723-798` — `create_pr(repo_path, Option<&str>, Option<&str>)` with `--fill` fallback when title/body are `None`; `sanitize_pr_title` helper
|
||||
- `app/src/code_review/git_dialog/commit.rs:66-70` — placeholder constants
|
||||
- `app/src/code_review/git_dialog/commit.rs:239-305` — `generate_commit_message` (open-time)
|
||||
- `app/src/code_review/git_dialog/commit.rs:313-319` — `is_ready_to_confirm`
|
||||
- `app/src/code_review/git_dialog/commit.rs:362-473` — `start_confirm` (PR title/body gen for `CommitAndCreatePr`)
|
||||
- `app/src/code_review/git_dialog/pr.rs:101-174` — `start_confirm` + `create_pr_with_ai_content` (shared helper used by both standalone PR and `CommitAndCreatePr`; parallelizes title/body with `futures::try_join!`, falls back to `--fill` on AI failure)
|
||||
- `app/src/code_review/git_dialog/mod.rs:484-496` — `refresh_confirm_enabled` call site updated for the new `is_ready_to_confirm` signature
|
||||
- Server side: `warp-server/router/handlers/generate_code_review_content.go` (already deployed)
|
||||
## Current state
|
||||
Before this branch:
|
||||
- `commit.rs` required a typed commit message; placeholder was `"Leave blank to autogenerate a commit message"` but there was no autogeneration wired — the confirm was disabled on empty.
|
||||
- `pr.rs::start_confirm` called `create_pr(&repo_path)` → `gh pr create --fill`.
|
||||
- `commit.rs::start_confirm` `CommitAndCreatePr` branch likewise called `create_pr(&repo_path)`.
|
||||
- `BlockClient` only had `generate_shared_block_title` as an AI-adjacent method.
|
||||
- `util/git.rs` had no diff-for-AI helpers.
|
||||
The dialog parent (`GitDialog`) owns chrome (title, buttons, loading state) and each mode owns its own state, body renderer, and confirm async; events collapse to `Completed | Cancelled`. That contract is preserved by this branch — all new work lives inside the existing per-mode submodules.
|
||||
## Proposed changes
|
||||
### 1. `generate_code_review_content` module (`app/src/ai/generate_code_review_content/`)
|
||||
Mirrors the shape of `generate_block_title/`: a `mod.rs` that declares `pub(crate) mod api;` and an `api.rs` with request/response types.
|
||||
```rust path=null start=null
|
||||
pub enum OutputType {
|
||||
CommitMessage,
|
||||
PrTitle,
|
||||
PrDescription,
|
||||
}
|
||||
pub struct GenerateCodeReviewContentRequest {
|
||||
pub output_type: OutputType,
|
||||
pub diff: String,
|
||||
#[serde(skip_serializing_if = "String::is_empty", default)]
|
||||
pub branch_name: String,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty", default)]
|
||||
pub commit_messages: Vec<String>,
|
||||
}
|
||||
pub struct GenerateCodeReviewContentResponse {
|
||||
pub content: String,
|
||||
}
|
||||
```
|
||||
A single endpoint + request type is enough because all three output types share the same inputs (diff, optional branch name, optional commit subjects). The server dispatches on `output_type`.
|
||||
### 2. `BlockClient::generate_code_review_content`
|
||||
Added alongside `generate_shared_block_title` in `app/src/server/server_api/block.rs`. The `ServerApi` implementation POSTs to `{server_root_url}/ai/generate_code_review_content` with bearer auth, JSON body, and JSON response decoding — same skeleton as `generate_shared_block_title`. Reusing `BlockClient` keeps this off the GraphQL path (which would require a new mutation and cynic codegen) and matches where block-title gen already lives.
|
||||
### 3. Diff helpers in `app/src/util/git.rs`
|
||||
Four module-scope consts (`MAX_DIFF_CHARS_FOR_AI = 16_000`, `MAX_UNTRACKED_FILE_BYTES = 4_000`, `BINARY_CHECK_BYTES = 1_024`, `MAX_PR_TITLE_BYTES = 200`) plus a `truncate_on_char_boundary` helper and three git-diff helpers, all `#[cfg(feature = "local_fs")]` with wasm stubs to match existing conventions in the file. All byte-length truncation uses `truncate_on_char_boundary` to avoid UTF-8 panics on diffs/source files containing non-ASCII text.
|
||||
- `get_diff_for_commit_message(repo_path, include_unstaged) -> Result<String>`
|
||||
- `git diff HEAD` when `include_unstaged`, else `git diff --cached`.
|
||||
- When `include_unstaged`, iterates `git ls-files --others --exclude-standard -z` (NUL-separated to survive paths with spaces/non-ASCII), skips binaries via `warp_util::file_type::is_buffer_binary(&bytes[..BINARY_CHECK_BYTES])`, and appends synthetic unified-diff hunks for each new file (capped at `MAX_UNTRACKED_FILE_BYTES`) so the LLM sees new-file-only commits.
|
||||
- Final output truncated at `MAX_DIFF_CHARS_FOR_AI` with `\n... (diff truncated)` marker.
|
||||
- `get_diff_for_pr(repo_path) -> Result<String>`
|
||||
- Diffs `{base}..origin/{current}` when `git rev-parse --verify origin/{current}` succeeds, else `{base}..HEAD`.
|
||||
- Same truncation rule as above.
|
||||
- `get_branch_commit_messages(repo_path) -> Result<Vec<String>>`
|
||||
- `git log {base}..HEAD --format=%s`, one subject per vec element.
|
||||
### 4. `create_pr` signature change
|
||||
`create_pr(repo_path, title: Option<&str>, body: Option<&str>) -> Result<PrInfo>` replaces `create_pr(repo_path)`. When both fields are `Some`, invokes `gh pr create --title <t> --body <b>` (title passes through `sanitize_pr_title` to first-line and cap at `MAX_PR_TITLE_BYTES` — GitHub silently collapses newlines in titles otherwise). When either is `None`, falls back to `gh pr create --fill`, used as a last-resort source when AI title/body generation fails so the PR is still created. Both wasm stub and both call sites (`pr.rs` and `commit.rs`) updated.
|
||||
### 5. Commit-dialog open-time autogen (`commit.rs`)
|
||||
Two placeholder constants in `commit.rs`:
|
||||
- `GENERATING_PLACEHOLDER_TEXT = "Generating commit message…"` (shown while gen is in flight; was `"Leave blank to autogenerate a commit message"`).
|
||||
- `FALLBACK_PLACEHOLDER_TEXT = "Type a commit message"` (shown after gen resolves, success or failure).
|
||||
A new private `generate_commit_message(repo_path, branch_name, include_unstaged, ctx)` fires from `new_state` at dialog construction. It:
|
||||
1. Awaits `get_diff_for_commit_message` and `block_client.generate_code_review_content(CommitMessage, ...)`.
|
||||
2. On success: if the editor is still empty (`!buffer_text.trim().is_empty()` is false), `editor.system_reset_buffer_text(generated.trim(), ctx)`; otherwise discards. Placeholder swaps to `FALLBACK_PLACEHOLDER_TEXT`. `refresh_confirm_enabled`.
|
||||
3. On failure: placeholder swaps to `FALLBACK_PLACEHOLDER_TEXT`, `refresh_confirm_enabled`. No toast — the empty editor plus placeholder already communicate that no draft arrived, and the failure isn't retryable. `log::warn!` for the underlying error.
|
||||
No `is_autogenerating` field on `CommitState`. Confirm enablement is purely `!file_changes.is_empty() && commit_message(state, app).is_some()`; while gen is in flight the buffer is empty, so this is false naturally.
|
||||
### 6. Confirm-time PR gen: shared `create_pr_with_ai_content` helper
|
||||
Both flows (standalone `pr::start_confirm` and the `CommitAndCreatePr` branch of `commit::start_confirm`) delegate to `pr::create_pr_with_ai_content(repo_path, branch_name, block_client)`:
|
||||
1. `get_diff_for_pr(repo_path)`.
|
||||
2. `get_branch_commit_messages(repo_path)` (wrapped in `.unwrap_or_default()` — commit subjects are advisory).
|
||||
3. Parallel `block_client.generate_code_review_content` calls (`PrTitle` + `PrDescription`) via `futures::try_join!`, both sharing the same `diff`, `branch_name`, and `commit_messages`.
|
||||
4. On AI success: `create_pr(&repo_path, Some(&pr_title), Some(&pr_body))`.
|
||||
5. On AI failure (either call): `log::warn!` and fall back to `create_pr(&repo_path, None, None)` so the PR still gets created via `gh pr create --fill`.
|
||||
Non-AI errors (diff fetch, `gh pr create` itself) bubble via `?` into the existing `Err` handler, which logs and calls `show_toast(user_facing_git_error(&err.to_string()), ctx)`. AI errors no longer reach that path since they're converted to the `--fill` fallback.
|
||||
### 7. `is_ready_to_confirm` simplification
|
||||
Before: `(state, app)` → required non-empty file changes AND non-empty commit message.
|
||||
Interim (mid-branch): dropped `app`, added `is_autogenerating` flag, made message optional.
|
||||
Final: `(state, app)` → required non-empty file changes AND non-empty commit message again. The `is_autogenerating` flag is gone; the empty-buffer state during gen is what gates confirm.
|
||||
`start_confirm` is correspondingly simplified: `let Some(message) = commit_message(state, ctx) else { return; };` as a defensive guard (handles keyboard-shortcut dispatch that bypasses the button's disabled state), then straight into `run_commit`. The previously-added confirm-time AI fallback branch is deleted.
|
||||
## End-to-end flows
|
||||
### Commit message autogeneration
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant CommitDialog as commit.rs
|
||||
participant Git as util/git.rs
|
||||
participant AI as BlockClient
|
||||
User->>CommitDialog: Open dialog
|
||||
CommitDialog->>CommitDialog: placeholder = "Generating…"; confirm disabled
|
||||
CommitDialog->>Git: get_diff_for_commit_message
|
||||
Git-->>CommitDialog: diff (≤ 16k chars, + synthesised untracked files)
|
||||
CommitDialog->>AI: generate_code_review_content(CommitMessage)
|
||||
alt success
|
||||
AI-->>CommitDialog: draft
|
||||
CommitDialog->>CommitDialog: if editor empty, insert draft<br/>placeholder = "Type a commit message"
|
||||
CommitDialog->>User: editor populated → confirm enabled
|
||||
else failure
|
||||
AI-->>CommitDialog: error
|
||||
CommitDialog->>CommitDialog: placeholder = "Type a commit message"<br/>log::warn + refresh_confirm_enabled
|
||||
CommitDialog->>User: blank editor → confirm still disabled
|
||||
end
|
||||
```
|
||||
### PR title/body autogeneration (both flows)
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Dialog as pr.rs / commit.rs
|
||||
participant Git as util/git.rs
|
||||
participant AI as BlockClient
|
||||
participant GH as gh CLI
|
||||
User->>Dialog: Click Create PR (or Commit and create PR)
|
||||
Dialog->>Dialog: set_loading("Creating…" or intent loading label)
|
||||
note over Dialog,Git: CommitAndCreatePr also runs run_commit + run_push first
|
||||
Dialog->>Git: get_diff_for_pr
|
||||
Git-->>Dialog: diff
|
||||
Dialog->>Git: get_branch_commit_messages
|
||||
Git-->>Dialog: commit subjects
|
||||
Dialog->>AI: generate_code_review_content(PrTitle)
|
||||
AI-->>Dialog: title
|
||||
Dialog->>AI: generate_code_review_content(PrDescription)
|
||||
AI-->>Dialog: body
|
||||
Dialog->>GH: gh pr create --title --body
|
||||
GH-->>Dialog: PrInfo | error
|
||||
Dialog->>User: success toast with Open PR link | friendly error toast
|
||||
```
|
||||
### State machine for the commit message editor
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Generating
|
||||
Generating --> Populated: gen success && editor empty
|
||||
Generating --> Failed: gen error or empty response
|
||||
Generating --> UserTyped: user types during gen
|
||||
Populated --> UserTyped: user edits
|
||||
Populated --> Empty: user clears
|
||||
Failed --> UserTyped: user types
|
||||
Empty --> UserTyped: user types
|
||||
UserTyped --> Empty: user clears
|
||||
Populated --> [*]: confirm
|
||||
UserTyped --> [*]: confirm
|
||||
Failed --> [*]: cancel
|
||||
Empty --> [*]: cancel
|
||||
note right of Failed
|
||||
placeholder = "Type a commit message"
|
||||
no toast (silent)
|
||||
confirm disabled
|
||||
end note
|
||||
note left of Empty
|
||||
placeholder = "Type a commit message"
|
||||
confirm disabled
|
||||
end note
|
||||
```
|
||||
## Risks and mitigations
|
||||
**AI latency blocks the user.** Commit message gen runs at open time in the background, so the user can start typing immediately; gen results are discarded if the user has typed anything. PR title/body gen runs at confirm time, which does extend the `Creating…` loading phase by two sequential AI calls. Mitigation is bounded by server SLA; a failure simply surfaces the existing friendly error toast.
|
||||
**AI errors don't surface to the user.** AI-generation errors are caught inside `create_pr_with_ai_content` and transparently fall back to `gh pr create --fill`, so the user sees a successfully-created PR with latest-commit-derived copy rather than an error toast. Only non-AI errors (diff fetch, `gh pr create` itself) still flow through `user_facing_git_error`, which doesn't know about them specifically and maps them to the generic git fallback. Improving that mapping is an explicit follow-up.
|
||||
**Sequential PR gen calls double the latency.** Addressed in-branch: `create_pr_with_ai_content` now runs `PrTitle` + `PrDescription` concurrently via `futures::try_join!`. The diff payload is cloned across both requests but latency is bounded by the slower of the two.
|
||||
**`CommitAndCreatePr` chain can leave the branch pushed but PR not created.** If PR creation fails after `run_commit` + `run_push` (non-AI failure — AI failures now fall back to `--fill`), the commit and push are real but no PR exists. The header button transitions to the `CreatePr` state on the next diff-metadata refresh, so the user can retry via the standalone dialog. Documented in the product spec; no code-level mitigation in-branch.
|
||||
**Duplicate PR title/body gen code.** The ~25-line PR title + PR body generation block appears verbatim in both `commit.rs::start_confirm` (`CommitAndCreatePr` branch) and `pr.rs::start_confirm`. An extracted helper would sit naturally in `git_dialog/mod.rs`. Deferred to follow-up.
|
||||
**Privacy / opt-out.** `GitOperationsInCodeReview` gates the UI, but does not address AI-specific privacy concerns (sending diffs to an LLM). See the TODO at the top of `app/src/ai/generate_code_review_content/mod.rs` — follow-up work needs to add an `AISettings` toggle (mirroring `is_shared_block_title_generation_enabled`) and a customer-type guard that excludes Enterprise unless on Warp plan or dogfood, matching the pattern in `terminal/share_block_modal.rs::should_send_title_gen_request`.
|
||||
## Testing and validation
|
||||
No automated tests added — the parent branches (APP-3920, APP-3922) also ship without tests and the `git_dialog` module has no test harness yet. Manual validation covers each path in the product spec's **Validation** section.
|
||||
When we add a test harness, the highest-value targets are:
|
||||
- `is_ready_to_confirm` transitions through the editor states (empty → typed → cleared).
|
||||
- `generate_commit_message`'s "user typed before response landed → discard" path.
|
||||
- `get_diff_for_commit_message` truncation and untracked-file synthesis.
|
||||
## Follow-ups
|
||||
- Add `AISettings::is_commit_message_generation_enabled` (or similar) and wire it through `generate_commit_message` and `create_pr_with_ai_content`. Mirror `share_block_modal.rs::should_send_title_gen_request`.
|
||||
- Add customer-type guard for Enterprise users (allow Warp plan + dogfood, deny otherwise).
|
||||
- Route AI errors to dedicated toast copy instead of the git-error mapper. Options explored: typed-error marker + `downcast_ref` (clean but heavier) vs. fall-through-on-unknown-error (simpler but changes behavior for unknown git errors). Pick one and implement.
|
||||
- Extract the `origin/{current}`-or-HEAD resolution helper — duplicated between `get_branch_diff_entries` (APP-3922) and `get_diff_for_pr` (this branch).
|
||||
- Consider a regenerate button for the commit message draft, and a preview/edit UI for PR title/body before `gh pr create` fires.
|
||||
- Update PR #23945 title and description to cover all three generated fields (currently title only mentions commit messages).
|
||||
Reference in New Issue
Block a user