[APP-4261] Add tooltips to disabled git operation buttons (#9234)

## Description

Adds contextual tooltips to disabled git operation buttons so users
understand why they can't click them. Tooltips are intentionally limited
to **disabled states only** — enabled buttons don't get tooltips since
their labels are self-explanatory.

**Changes:**
- Code review header: "No changes to commit" on the disabled Commit
button; "No git actions available" on the disabled chevron
- Commit dialog: "Enter a commit message" on the disabled Confirm button
(only shown once file changes have loaded — silent during the async load
window)
- Bug fix: the PR #N button in the header was staying greyed out after
transitioning from a disabled Commit mode, because `ViewPr` mode never
called `set_disabled(false)`

Linear:
https://linear.app/warpdotdev/issue/APP-4261/git-buttons-should-all-have-tooltips

Warp conversation:
https://staging.warp.dev/conversation/27f5811f-22ea-4c4b-8d8f-47ff48a5b12b

Loom: https://www.loom.com/share/932cfb22039c4566bcc7a7343b624d81

## Testing

Manually verified:
- Commit button with no changes shows "No changes to commit" on hover
- Chevron with no changes shows "No git actions available" on hover
- Commit dialog Confirm button shows "Enter a commit message" once files
load but message is empty; no tooltip shown during async load
- PR #N button is no longer greyed out after committing all changes

No new automated tests added — these are tooltip strings on existing
button state logic that is already exercised by existing tests.

## Server API dependencies

N/A — client-only change.

## Agent Mode
- [x] Warp Agent Mode - This PR was created via Warp's AI Agent Mode

## Changelog Entries for Stable

CHANGELOG-IMPROVEMENT: Git operation buttons now show tooltips
explaining why they're disabled (e.g. "No changes to commit", "Enter a
commit message").

Co-Authored-By: Oz <oz-agent@warp.dev>

---------

Co-authored-by: Oz <oz-agent@warp.dev>
This commit is contained in:
Edward Shao
2026-04-28 12:13:36 -04:00
committed by GitHub
co-authored by Oz
parent c8f652630b
commit 7104a12fcf
3 changed files with 24 additions and 7 deletions
+6 -3
View File
@@ -6798,11 +6798,12 @@ impl CodeReviewView {
match mode { match mode {
PrimaryGitActionMode::Commit => { PrimaryGitActionMode::Commit => {
let has_changes = self.has_uncommitted_changes(ctx); let disabled = !self.has_uncommitted_changes(ctx);
self.git_primary_action_button.update(ctx, |button, ctx| { self.git_primary_action_button.update(ctx, |button, ctx| {
button.set_label("Commit", ctx); button.set_label("Commit", ctx);
button.set_icon(Some(Icon::GitCommit), ctx); button.set_icon(Some(Icon::GitCommit), ctx);
button.set_disabled(!has_changes, ctx); button.set_disabled(disabled, ctx);
button.set_tooltip(disabled.then_some("No changes to commit"), ctx);
button.set_on_click( button.set_on_click(
|ctx| ctx.dispatch_typed_action(CodeReviewAction::OpenCommitDialog), |ctx| ctx.dispatch_typed_action(CodeReviewAction::OpenCommitDialog),
ctx, ctx,
@@ -6810,7 +6811,8 @@ impl CodeReviewView {
button.set_adjoined_side(AdjoinedSide::Right, ctx); button.set_adjoined_side(AdjoinedSide::Right, ctx);
}); });
self.git_operations_chevron.update(ctx, |button, ctx| { self.git_operations_chevron.update(ctx, |button, ctx| {
button.set_disabled(!has_changes, ctx); button.set_disabled(disabled, ctx);
button.set_tooltip(disabled.then_some("No git actions available"), ctx);
}); });
} }
PrimaryGitActionMode::Push => { PrimaryGitActionMode::Push => {
@@ -6849,6 +6851,7 @@ impl CodeReviewView {
self.git_primary_action_button.update(ctx, |button, ctx| { self.git_primary_action_button.update(ctx, |button, ctx| {
button.set_label(label, ctx); button.set_label(label, ctx);
button.set_icon(Some(Icon::Github), ctx); button.set_icon(Some(Icon::Github), ctx);
button.set_disabled(false, ctx);
button.set_on_click( button.set_on_click(
move |ctx| { move |ctx| {
ctx.dispatch_typed_action(CodeReviewAction::ViewPr(url.clone())) ctx.dispatch_typed_action(CodeReviewAction::ViewPr(url.clone()))
+10
View File
@@ -241,6 +241,16 @@ pub(super) fn is_ready_to_confirm(state: &CommitState, app: &AppContext) -> bool
!state.file_changes.is_empty() && commit_message(state, app).is_some() !state.file_changes.is_empty() && commit_message(state, app).is_some()
} }
/// Returns a tooltip to show on the disabled Confirm button when the
/// user needs to take action, or `None` when no tooltip is needed.
pub(super) fn confirm_tooltip(state: &CommitState, app: &AppContext) -> Option<&'static str> {
if !state.file_changes.is_empty() && commit_message(state, app).is_none() {
Some("Enter a commit message")
} else {
None
}
}
/// Kicks off an open-time AI commit-message generation. On success, writes /// Kicks off an open-time AI commit-message generation. On success, writes
/// the result into the message editor (unless the user has already typed /// the result into the message editor (unless the user has already typed
/// something). On failure, silently swaps the placeholder to the manual /// something). On failure, silently swaps the placeholder to the manual
+8 -4
View File
@@ -644,13 +644,17 @@ impl GitDialog {
if self.loading { if self.loading {
return; return;
} }
let disabled = match &self.mode { let (disabled, tooltip) = match &self.mode {
GitDialogMode::Commit(state) => !commit::is_ready_to_confirm(state, ctx), GitDialogMode::Commit(state) => (
GitDialogMode::Push(_) => false, !commit::is_ready_to_confirm(state, ctx),
GitDialogMode::CreatePr(state) => !pr::is_ready_to_confirm(state), commit::confirm_tooltip(state, ctx),
),
GitDialogMode::Push(_) => (false, None),
GitDialogMode::CreatePr(state) => (!pr::is_ready_to_confirm(state), None),
}; };
self.confirm_button.update(ctx, |b, ctx| { self.confirm_button.update(ctx, |b, ctx| {
b.set_disabled(disabled, ctx); b.set_disabled(disabled, ctx);
b.set_tooltip(tooltip, ctx);
}); });
} }