Skip to content

feat: improve PR button UX with push support and conditional visibility#89

Merged
matt2e merged 13 commits intomainfrom
matt2e/pr-improvements
Feb 13, 2026
Merged

feat: improve PR button UX with push support and conditional visibility#89
matt2e merged 13 commits intomainfrom
matt2e/pr-improvements

Conversation

@matt2e
Copy link
Contributor

@matt2e matt2e commented Feb 12, 2026

Summary

Improves the PR button in the branch card footer with three key enhancements:

Changes

  • Conditional visibility: The PR button is now hidden when the branch has no commits and no PR activity, reducing visual clutter for empty branches.
  • Push support: When a PR already exists and there are unpushed local commits, the button transforms into a "Push" action, allowing users to push new commits directly without leaving the UI.
  • Backend commands: Adds has_unpushed_commits and push_branch_cmd Tauri commands (supporting both local worktree and remote workspace branches) to detect unpushed commits and push branches.
  • Pushing state UI: Shows a spinner and disabled state while a push is in progress.

Files changed

  • staged/src-tauri/src/lib.rs — New has_unpushed_commits and push_branch_cmd commands
  • staged/src/lib/commands.ts — TypeScript bindings for the new commands
  • staged/src/lib/BranchCard.svelte — UI logic for conditional PR button, push state, and unpushed commit detection

@matt2e matt2e marked this pull request as draft February 12, 2026 23:02
…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
@matt2e matt2e force-pushed the matt2e/pr-improvements branch from 823fbf3 to 53dd3c5 Compare February 12, 2026 23:44
matt2e and others added 8 commits February 12, 2026 15:52
… 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
@matt2e matt2e marked this pull request as ready for review February 13, 2026 17:17
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.
@matt2e matt2e merged commit eeed2ee into main Feb 13, 2026
3 checks passed
@matt2e matt2e deleted the matt2e/pr-improvements branch February 13, 2026 17:22
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants