feat: improve PR button UX with push support and conditional visibility#89
Merged
feat: improve PR button UX with push support and conditional visibility#89
Conversation
…al visibility - Hide 'Create PR' button on branches with no commits (hasCommits derived state) - Show 'Push' button when PR exists but branch has unpushed commits - Allow clicking 'Creating PR…' to open the session chat for progress - Add has_unpushed_commits Tauri command (git rev-list origin/branch..HEAD) - Add push_branch_cmd Tauri command with local/remote (Blox) support - Add ws_exec helper to blox module for running commands in workspaces - Add .pushing CSS state for disabled push button styling - Re-check unpushed status reactively via $effect on timeline changes
…lease - Change push_branch_cmd from sync fn to async fn so Tauri doesn't block the UI thread (allows the Push button spinner to render) - Extract store access into a scoped block so the Mutex guard is dropped before the push operations run - Auto-retry with --force-with-lease when a normal push fails with non-fast-forward rejection (e.g. after a rebase) - Apply retry logic to both local and remote (Blox) branch paths
…ch_cmd - Remove force parameter from push_branch_cmd and pushBranch frontend wrapper - Remove auto-retry with --force-with-lease on non-fast-forward rejection - Keep push_branch_cmd as async (needed for UI spinner to render) - Keep scoped store access pattern (needed for async correctness) - Simplify to a straightforward git push -u origin <branch>
- Show error icon in Push button when push fails, clicking opens error dialog - Error dialog displays the error message with Close and Force Push buttons - Close dismisses the dialog and clears the error state - Force Push attempts --force-with-lease push with spinner feedback - Add force parameter to push_branch_cmd (Rust) and pushBranch (TS) - Pass force flag through to git::push_branch for local branches - Add --force-with-lease to remote (Blox) push command when force is true - Add pushError, showPushErrorDialog, forcePushing reactive state - Style push error dialog with modal backdrop, danger icon, and action buttons
823fbf3 to
53dd3c5
Compare
… is hidden Add margin-left: auto to .new-btn-group so the New note and New commit buttons stay on the right side of the card footer even when the Create PR button is conditionally hidden (no commits on the branch). Without this, justify-content: space-between on the footer causes the buttons to shift to the left when there is no left-side element.
- Add fallback polling $effect that checks session status every 5s while prState is 'creating', so the spinner resolves even if the Tauri session-status-changed event is missed (e.g. listener re-registration race) - Improve extractPrUrl to search tool_result messages (not just assistant) since the PR_URL marker or GitHub URL may appear in shell output captured as a tool result; use two-pass search (explicit marker first, then any GitHub PR URL in any message role) - Check PR session status on SessionModal close so the spinner resolves immediately if the session finished while the user was viewing it - Fix ghost button in push error dialog: add showPushErrorDialog guard to the error CSS class and disabled attribute on the PR button
…call Fixes TypeScript error where closedSessionId (string | null) was being passed to commands.getSession() which expects a non-null string. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the direct git push implementation with a session-based approach
that delegates pushing to an agent (local or remote, based on branch
locality). This allows pre-push hooks to throw errors that the agent can
diagnose and fix automatically.
Changes:
- Rewrite push_branch Tauri command to create an agent session with a
prompt instructing it to run git push and handle failures (including
pre-push hook errors and non-fast-forward rejections)
- Pick agent provider based on branch locality (local vs remote/Blox)
- Return session ID so the frontend can track progress
- Update pushBranch TS wrapper: takes optional provider instead of force
flag, returns Promise<string> (session ID)
- Refactor BranchCard.svelte push state from boolean flags to a typed
PushState ('idle' | 'pushing' | 'error' | 'done') mirroring PR pattern
- Add push session completion handling via session-status-changed event
- Add fallback polling for push session (same 5s pattern as PR)
- Allow clicking 'Pushing…' button to open the session chat
- Replace force-push error dialog with a simple Retry/Cancel ConfirmDialog
- Remove modal-backdrop, push-error-modal, btn CSS (no longer needed)
- Handle push session completion when SessionModal is closed
…ejections - Forbid force pushing in the default push agent prompt; instruct the agent to output PUSH_REJECTED: NON_FAST_FORWARD marker and stop if the remote would lose commits - Add force parameter to push_branch (Rust) and pushBranch (TS) that switches to a --force-with-lease prompt when true - Add isPushRejectedNonFastForward helper to scan session messages for the non-fast-forward marker after push session completes - Show ConfirmDialog when non-fast-forward rejection is detected, asking the user whether to force push (overwrites remote with local version) - On confirm, re-run push session with force=true (--force-with-lease) - On cancel, reset push state to idle - Disable PR button and suppress error styling while force push dialog is open
…alog to button click Two push UX bugs fixed: 1. Successful push treated as failure: isPushRejectedNonFastForward was checking ALL message roles including user messages, which contain the prompt instructions that reference the PUSH_REJECTED: NON_FAST_FORWARD marker text. Now only checks assistant and tool_result messages to avoid matching the agent's own instructions. 2. Force push dialog auto-opened on non-fast-forward: Previously handlePushSessionComplete set showForcePushDialog=true directly, which immediately opened the dialog. Now sets pushRejectedNonFastForward flag and goes to error state instead. Clicking the error-state Push button opens the appropriate dialog (force push or generic error) based on the failure type. Additional cleanup: - Add pushRejectedNonFastForward reactive state flag - Reset flag in handleForcePushConfirm and handleForcePushCancel - Simplify class:error binding (no longer needs dialog-open guards) - Disable PR button while any error/force-push dialog is open
…opening Replace the `open` crate with `tauri-plugin-opener` so that the View PR button correctly opens URLs in the user's default browser. The `open::that` function does not work reliably inside Tauri v2's sandboxed environment. Changes: - Replace `open = "5"` dependency with `tauri-plugin-opener = "2"` in Cargo.toml - Register `tauri_plugin_opener::init()` in the Tauri plugin chain - Update `open_url` command to use `OpenerExt::open_url` via the app handle instead of `open::that` - Add `opener:default` permission to capabilities/default.json
The View PR button did nothing when clicked because getPrUrlFromNumber could never reconstruct the URL — it only had the PR number (from the DB) but not the GitHub repo URL, and prUrl (component state) is null after app restart. Two fixes: 1. Add get_pr_url Tauri command that resolves the GitHub owner/repo from the git origin remote URL (via get_github_repo) and constructs https://github.com/{owner}/{repo}/pull/{number} 2. Update handlePrButtonClick to call commands.getPrUrl when prUrl is not cached in component state, caching the result for subsequent clicks Also: - Make get_github_repo pub so lib.rs can call it - Add getPrUrl TS wrapper in commands.ts - Remove dead getPrUrlFromNumber function from BranchCard.svelte
The opener:default permission grants the frontend JS access to the tauri-plugin-opener APIs, but we only use the opener plugin from Rust backend commands (OpenerExt::open_url in open_url). Tauri capabilities only govern frontend access, so this permission is unnecessary.
wesbillman
approved these changes
Feb 13, 2026
taylorkmho
added a commit
that referenced
this pull request
Feb 13, 2026
* origin/main: fix: surface worktree setup errors instead of showing infinite spinner (#106) feat: store data in platform-conventional directories (XDG) (#104) feat(notes): add copy-to-clipboard button in NoteModal (#103) feat: add use github repo based projects to avoid cloning when using remote branches (#102) fix: always branch new worktrees from remote-tracking refs (#100) fix: remove debug console.log statements from BranchCard (#97) feat: improve PR button UX with push support and conditional visibility (#89)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Improves the PR button in the branch card footer with three key enhancements:
Changes
has_unpushed_commitsandpush_branch_cmdTauri commands (supporting both local worktree and remote workspace branches) to detect unpushed commits and push branches.Files changed
staged/src-tauri/src/lib.rs— Newhas_unpushed_commitsandpush_branch_cmdcommandsstaged/src/lib/commands.ts— TypeScript bindings for the new commandsstaged/src/lib/BranchCard.svelte— UI logic for conditional PR button, push state, and unpushed commit detection