Add an interactive submit TUI for customizing each PR's title, description, and draft state#147
Add an interactive submit TUI for customizing each PR's title, description, and draft state#147skarim wants to merge 7 commits into
Conversation
Introduce the internal/tui/submitview package that will back the new interactive `gh stack submit` TUI, and wire its per-PR override contract into the submit command without changing current behavior. - submitview: BranchState model (NEW/OPEN/DRAFT/QUEUED/MERGED/CLOSED) with selectability/editability rules, SubmitNode UI state with edit detection, PRDraft override type, state derivation, title/description prefill, and state-badge/panel/tab styles. - submit: refactor ensurePR/createPR to accept an optional per-branch override map (title/body/draft/include); deselected NEW branches are pushed but get no PR. The override map is nil on the --auto / non-interactive path, so the agent-compat contract is unchanged. Fully unit tested.
Introduce an interactive, single-screen editor for `gh stack submit`,
built on Bubble Tea and Lip Gloss.
The left panel renders the stack as a connected tree down to the trunk.
Every branch without a PR is included by default; deselect one with its
checkbox or `^x`. Because each PR builds on the branch below it,
deselecting a branch also deselects the ones stacked above it, and
re-including a branch re-includes the ones below it that it depends on.
The cursor uses its own cyan accent so it reads distinctly from the green
new/included color; existing PRs are shown dimmed with a no-entry glyph.
The right panel edits the focused branch's PR in web-create-PR order: a
header with the branch name and an include chip ("Creating PR" /
"Skipped"), the title, a scrollable description (Glamour markdown preview
and $EDITOR escape, with a scrollbar and mouse click-to-position), and a
ready to draft segmented toggle (defaulting to ready). A footer strip
shows the PR progress, the next branch, and the editor hints. Skipping a
branch dims its body; branches that already have a PR show a read-only
card linking to the PR.
It shares the gh-stack header (art, title, stack info, and keyboard
shortcuts) with `gh stack view` and `gh stack modify` for a unified look.
Submit every included PR at once with Ctrl+S. Full keyboard and mouse
support throughout.
Launch the submit editor from `gh stack submit` in interactive terminals, collecting per-branch PR drafts and applying them in a single batch. In non-interactive terminals or with --auto, fall back to auto-generated titles and skip the editor. Update the README and CLI reference to describe the single-screen flow.
For existing PRs, the submit TUI showed a commit/template-derived draft instead of the pull request's real title and body. Fetch the actual title and body and render them in the read-only card: - open/draft/queued (tracked) and adopted-open PRs now carry title/body through the existing batch sync (added the fields to the GraphQL queries and PRDetails — no extra round trips), and - merged branches (which skip the live refresh) are filled in by a targeted enrichment step run only when the submit TUI opens. Also align the new-PR defaults with the non-TUI submit's defaultPRTitleBody: - Title: the commit subject only when the branch has exactly one commit, otherwise the humanized branch name (was: the oldest commit's subject even for multi-commit branches). - Description: the PR template, else the single commit's body, else empty (removed the bulleted commit-subject list for multi-commit branches).
Scrolling the mouse wheel while a title or description field was focused
could insert stray characters such as "[<65;54;51M" into the field. The
submit TUI ran the Bubble Tea program with WithMouseAllMotion (mode 1003),
which reports an event on every pointer move. During a wheel scroll that
floods the input stream, and under that volume Bubble Tea splits an SGR
mouse escape sequence ("\x1b[<Cb;Cx;Cy(M|m)") across input reads; the
leftover bytes of a partially-parsed sequence are then emitted as key
runes and inserted into the focused text input.
Two changes fix this:
- Switch to WithMouseCellMotion (mode 1002), which reports clicks, drag,
and wheel but not idle pointer motion. That removes the per-move input
flood, so under a real terminal's reads (up to 256 bytes) the only
fragment that still surfaces is a single, clean burst at each wheel
notch boundary. The TUI never used idle-hover for rendering, so
cell-motion loses nothing.
- Drop any leaked fragments before they reach a field. A split SGR mouse
sequence surfaces as an Alt+"[" (the consumed "\x1b[") followed by
body fragments ("<65;54;5", "1M"), or occasionally the whole body in
one run ("[<65;54;51M"). consumeLeakedMouseKey recognises the start,
swallows the body up to its "M"/"m" terminator, and bails out the
moment a rune does not fit an SGR body, so ordinary typing (including
"<", ";", digits, "M") and bracketed pastes are never eaten.
Tests cover every split point of an SGR sequence, single-run tails,
preserved real typing, a stray Alt+"[", bracketed paste, and that wheel
events never modify the focused field. Verified end-to-end by feeding
1,500 wheel sequences through the real parser under terminal-sized reads
and confirming the field stays empty.
fd8c52b to
e7d9173
Compare
Opening the description in $EDITOR with ^e and then quitting left the mouse unresponsive: clicks and wheel scrolling stopped working while keyboard navigation still did. The editor is launched with tea.ExecProcess, which releases the terminal before running the command and calls Bubble Tea's RestoreTerminal when it returns. RestoreTerminal re-enables the alt-screen, bracketed paste, and focus reporting, but it does not re-enable mouse tracking. The editor (e.g. vim) disables mouse reporting on exit, so once control returns to the TUI the terminal no longer emits mouse events. Re-arm mouse mode when the editor-finished message arrives by batching tea.EnableMouseCellMotion with the handler's command. That re-enables cell-motion and SGR mouse reporting, matching the WithMouseCellMotion option the program starts with, on every editor-return path (success or error).
There was a problem hiding this comment.
Pull request overview
This PR replaces the old single-prompt "title only" flow in gh stack submit with a full interactive, single-screen Bubble Tea TUI that lets the user pick which branches become PRs and draft each PR's title, description (with live Glamour markdown preview and $EDITOR escape), and ready/draft state before a single batch submit. It introduces a self-contained internal/tui/submitview package and wires per-PR overrides (PRDraft) through the existing push/create path in cmd/submit.go, while keeping the --auto/non-interactive behavior unchanged. It also extends the GitHub API layer to fetch PR title/body so existing PRs render as read-only cards.
Changes:
- New
internal/tui/submitviewpackage: data model, state derivation, layout/rendering, left timeline + right editor, mouse handling (with SGR-leak filtering), markdown preview, help overlay, and extensive unit tests. cmd/submit.gointegration:collectPRDraftsruns the TUI (interactive, non---auto), andensurePR/createPRconsume per-branch draft overrides (deselected NEW branches are pushed but not turned into PRs).- Plumbing:
github.PullRequest/PRDetailsgainTitle/Body;syncStackPRs/prDetailsFromPRpropagate them; newenrichPRContentbackfills missing titles; README and CLI docs updated.
Show a summary per file
| File | Description |
|---|---|
internal/tui/submitview/types.go |
Branch state + SubmitNode/PRDraft model (package doc still describes old two-step design). |
internal/tui/submitview/model.go |
Bubble Tea model, Update/View, quit/help flow (contains a few set-but-unused fields). |
internal/tui/submitview/screen.go |
Single-screen layout, navigation, include cascade, left timeline rendering. |
internal/tui/submitview/editor.go |
Right-panel editor/locked-card rendering, scrollbars, draft toggle. |
internal/tui/submitview/mouse.go / mousefilter.go |
Mouse hit-testing and SGR mouse-leak filtering. |
internal/tui/submitview/preview.go |
$EDITOR integration and fixed-style Glamour markdown rendering. |
internal/tui/submitview/data.go / styles.go / render.go / help.go |
State derivation, prefill, styles, shared header, help/quit overlays. |
internal/tui/submitview/*_test.go |
Comprehensive unit tests for the new package. |
cmd/submit.go |
Runs the TUI and threads PRDraft overrides through create path; updated help text (stale draft-toggle wording). |
cmd/utils.go / cmd/utils_test.go |
enrichPRContent + title/body propagation in PR sync. |
internal/github/github.go |
Adds Title/Body to PR types and GraphQL queries. |
cmd/submit_test.go / cmd/submit_tui_test.go |
Command-level integration tests for draft overrides and TUI skip. |
README.md / docs/.../cli.md |
Document the new interactive editor. |
go.mod / go.sum |
Add glamour, promote termenv to direct; transitive deps. |
The issues I found are minor (stale documentation/help text and some set-but-unused model state); the core logic and the submit-flow plumbing are sound and well-covered by tests. Given the size (~5k lines) and the complexity of the terminal UI (mouse hit-testing, scroll math, escape-sequence handling), this warrants a human review pass before approval.
Review details
- Files reviewed: 28/29 changed files
- Comments generated: 3
- Review effort level: Medium
| // All-motion (mode 1003) reports an event on every pointer move, flooding the | ||
| // input; under that volume bubbletea can split an SGR mouse sequence across | ||
| // reads, leaking its bytes as text into a focused title/description field | ||
| // while scrolling. We don't use idle-hover, so cell-motion loses nothing. |
Previously,
gh stack submitonly let you customize each PR's title. The description was either left blank or filled from a default template, and there was no way to choose, per PR, whether to open it as a draft. That made it awkward to submit a well-described stack — you'd typically create the PRs and then edit each one on GitHub afterward.This PR adds a fully interactive TUI to
gh stack submitso you can customize each PR's details — title, description (with a live markdown preview), and ready/draft state — and choose which branches become PRs, all before anything is created. Overall it's a much smoother experience for submitting and creating a stack of PRs.What's new
Running
gh stack submitnow opens a single-screen, keyboard- and mouse-driven TUI:state · #numshown below the nametab(cycle fields & PRs),^xskip/include,^ppreview,^e$EDITOR,^oopen existing PR,^ssubmit,^h/?help,esc/qquit — plus mouse support (click rows, checkboxes, toggles, fields, links, and the footer buttons; wheel scrolling in both panels).^hworks even while editing a field) listing the full keyboard + mouse reference.The header (art, title, stack info lines, shortcut list) is shared with
gh stack view/gh stack modify(introduced in the base PR, #143) for a consistent look.How to review
The change is split into three commits to make review easier:
SubmitNode, branch state, title/description prefill, prefix helpers ininternal/tui/submitview/{types,data}.go) and the plumbing incmd/submit.goto carry a per-PR ready/draft override through to PR creation. No UI yet.internal/tui/submitview/(layout/rendering, the left timeline + right editor, mouse handling, markdown preview, help overlay, styles), with thorough unit tests.gh stack submit— replaces the old prompt flow incmd/submit.gowith the TUI and updates the docs/README.git diffis large (~5k lines) but is mostly the self-containedinternal/tui/submitviewpackage plus its tests;cmd/submit.gois the main integration point.Testing
internal/tui/submitview/*_test.gocovering rendering, navigation/field cycling, the include cascade, mouse click/scroll hit-targets, markdown preview, the help overlay, and the footer actions, pluscmd/submit_tui_test.gofor the command integration.go test ./...passes;go vetandgofmtare clean; each of the three commits builds independently.