Skip to content

feat(stack): fzf-style interactive picker for mergify stack open#1704

Merged
mergify[bot] merged 6 commits into
mainfrom
devs/kozlek/feat/stack-open-picker
Jul 3, 2026
Merged

feat(stack): fzf-style interactive picker for mergify stack open#1704
mergify[bot] merged 6 commits into
mainfrom
devs/kozlek/feat/stack-open-picker

Conversation

@kozlek

@kozlek kozlek commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

What

mergify stack open with no <commit> argument on a terminal now shows an fzf-style fuzzy picker over the stack's PRs — type to filter (skim's SkimMatcherV2 via dialoguer::FuzzySelect), matched characters highlighted, leaf (HEAD) preselected, Enter opens the PR in the browser.

The Python CLI had an interactive picker; the Rust port dropped it ("left out of the port for now"). This restores it, upgraded from an arrow-key list to fuzzy filtering.

Behavior

Invocation Behavior
mergify stack open (stdin+stdout+stderr all TTYs) fuzzy picker, HEAD preselected
Esc in picker prints nothing, exit 0
Ctrl-C in picker exit 130 (fzf convention), cursor restored
mergify stack open non-TTY unchanged: silently opens leaf PR
mergify stack open <commit> unchanged: opens that commit's PR, no picker
empty stack unchanged: "No commits in stack", exit 3

Entries without a PR are listed as (no PR) <title> (<sha7>) so the stack shape stays visible; selecting one fails with the existing "run mergify stack push first" error.

Design notes

  • dialoguer stays behind a new mergify_tui::select::fuzzy_select() boundary — command crates never depend on it, and future pickers (checkout, edit, reword) reuse the same entry point. Dep is dialoguer 0.12, default-features = false, features = ["fuzzy-select"].
  • open::Options gains an injectable Selector callback (same testability-by-parameter pattern as queue pause's confirm), so cancel/label logic is unit-tested without a TTY.
  • Ctrl-C: console re-raises SIGINT from its raw-mode key reader, which would kill the process with dialoguer's hidden cursor leaked. A lazily-installed ctrlc handler restores the cursor and exits 130. (console is pinned to the same 0.16 dialoguer uses; single copy in the lockfile.)
  • TTY gate includes stderr because dialoguer renders the list on stderr — without it, mergify stack open 2>/dev/null would sit in raw mode drawing nothing.
  • The commit argument's clap doc was updated; the CLI-schema golden snapshot regenerated (that one description only).

Test plan

  • Unit tests: cancel contract (Interrupted → cancel fallback), label building, leaf-default index, selector error mapping (19 new/updated tests; workspace suite green)
  • Non-TTY smoke: piped stdin keeps the non-interactive path, no hang
  • Manual TTY check: picker/filter/Enter, Esc → $? = 0, Ctrl-C → $? = 130 with cursor visible, 2>/dev/null opens leaf silently

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 2, 2026 13:09
@mergify mergify Bot had a problem deploying to Mergify Merge Protections July 2, 2026 13:09 Failure
@kozlek kozlek temporarily deployed to func-tests-live July 2, 2026 13:09 — with GitHub Actions Inactive
@mergify

mergify Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 All 6 merge protections satisfied — ready to merge.

Show 6 satisfied protections

🟢 🤖 Continuous Integration

  • all of:
    • check-success=ci-gate

🟢 👀 Review Requirements

  • any of:
    • #approved-reviews-by>=2
    • author = dependabot[bot]
    • author = mergify-ci-bot
    • author = renovate[bot]

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert|ui)(?:\(.+\))?:

🟢 🔎 Reviews

  • #changes-requested-reviews-by = 0
  • #review-requested = 0
  • #review-threads-unresolved = 0

🟢 📕 PR description

  • body ~= (?ms:.{48,})

🟢 🚦 Auto-queue

When all merge protections are satisfied, this pull request will be queued automatically.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an interactive, fzf-style fuzzy picker to mergify stack open when no <commit> is provided and all stdio streams are TTYs, while preserving the existing non-interactive “open leaf PR” behavior for scripts/pipes. The picker implementation is introduced behind a new mergify-tui boundary and wired into stack open via an injectable selector callback for testability.

Changes:

  • Introduces mergify_tui::select::fuzzy_select() (dialoguer FuzzySelect) with match highlighting and cancellation mapping.
  • Extends mergify stack open to optionally run an injected selector (TTY-gated in the binary) and adds a Cancelled outcome.
  • Updates CLI schema snapshot/help text and adds new dependencies (dialoguer, ctrlc, console) in the workspace.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
crates/mergify-tui/src/theme.rs Exposes colors_enabled() to allow picker theming to follow the existing color policy.
crates/mergify-tui/src/select.rs New fuzzy-picker wrapper around dialoguer, including SIGINT/cursor-restore logic.
crates/mergify-tui/src/lib.rs Exports the new select module and fuzzy_select() API.
crates/mergify-tui/Cargo.toml Adds dialoguer, ctrlc, and console dependencies for picker functionality.
crates/mergify-stack/src/commands/open.rs Adds selector injection, label building, interactive selection, and Cancelled outcome.
crates/mergify-cli/src/snapshots/mergify__tests__cli_schema_golden.snap Updates the stack open commit help/longHelp text in the schema snapshot.
crates/mergify-cli/src/main.rs Wires TTY gating and injects mergify_tui::fuzzy_select into stack open; maps Cancelled to success.
Cargo.toml Adds shared workspace dependencies for picker support.
Cargo.lock Locks new transitive dependencies pulled in by dialoguer/ctrlc/console.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/mergify-tui/src/select.rs
Comment thread crates/mergify-stack/src/commands/open.rs
Comment thread crates/mergify-stack/src/commands/open.rs
kozlek and others added 5 commits July 2, 2026 17:04
Wraps dialoguer::FuzzySelect behind the mergify-tui boundary:
shared theming policy, Esc/Ctrl-C-as-cancel contract, and no
direct dialoguer dependency in command crates.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: Idde18b02d2d99120dda58e1cf46f2c2d8cf2c844
open::Options gains an injectable Selector (None = today's leaf
default) and Outcome::Cancelled for a dismissed picker. Pure
label/selection helpers are unit-tested without a TTY; the
explicit-commit and non-TTY paths are untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: I7c344cbe182329006802bbbb6221898b47d779ea
With no <commit> argument on a TTY, stack open now shows a fuzzy
picker over the stack (type to filter, HEAD preselected, Esc/Ctrl-C
cancels with exit 0). Non-TTY and explicit-commit invocations are
byte-identical to before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: I540f15b2f6e87fdf5a9f18a90e1d769b242225ee
console re-raises SIGINT from the raw-mode key reader, so Ctrl-C
killed the process at 130 with dialoguer's hidden cursor leaked. A
lazily installed handler now restores the cursor and exits 130 (the
fzf convention). The picker gate also requires stderr to be a TTY —
that's where dialoguer draws — and the redundant max_length cap is
gone along with the terminal_size dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: I6985543c4881a539da3a264bc13e547cf6d88409
Closes the window where a Ctrl-C arriving during the picker's error
path could observe PICKER_ACTIVE already false and skip the cursor
restore. Also retires comments still describing the pre-130 Ctrl-C
contract and the stdout-only TTY gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: I8a8acff6d00307546da9360e75fe0b0f58ac00ad
@kozlek kozlek force-pushed the devs/kozlek/feat/stack-open-picker branch from ac3c3ab to d998d21 Compare July 2, 2026 15:05
@kozlek kozlek temporarily deployed to func-tests-live July 2, 2026 15:05 — with GitHub Actions Inactive
@mergify mergify Bot had a problem deploying to Mergify Merge Protections July 2, 2026 15:06 Failure
Bounds-check the injected selector's index (typed CliError instead
of an index panic) and make the leaf-default arm panic-free if the
empty-stack guard ever moves. Addresses Copilot review on #1704.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Change-Id: I4413404e83ae59af9e0950a088707ad3e03cbbac
@kozlek kozlek temporarily deployed to func-tests-live July 2, 2026 15:09 — with GitHub Actions Inactive
@mergify mergify Bot deployed to Mergify Merge Protections July 2, 2026 15:09 Active
@mergify mergify Bot requested a review from a team July 2, 2026 15:15
@mergify

mergify Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Merge Queue Status

This pull request spent 8 minutes 5 seconds in the queue, including 7 minutes 46 seconds running CI.

Required conditions to merge

@mergify mergify Bot added the queued label Jul 3, 2026
@mergify mergify Bot merged commit 42ac904 into main Jul 3, 2026
22 checks passed
@mergify mergify Bot deleted the devs/kozlek/feat/stack-open-picker branch July 3, 2026 09:09
@mergify mergify Bot removed the queued label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants