7 Commits

Author SHA1 Message Date
Sameen Karim 797c62e9b4 Adapt theme colors to light and dark terminals (#149)
* Make the TUIs adapt to light and dark terminal backgrounds

The submit, view, and modify TUIs were tuned for dark terminals. On light
or solarized-light backgrounds the result was hard to read and inverted:
primary text used ANSI white (invisible on white), dim chrome used light
grays (too faint), and accents used bright cyan (low contrast) — so
"active" things looked lighter than "disabled" ones.

Introduce a centralized, background-aware color palette and migrate all
three TUIs to it:

- internal/tui/shared/theme.go: a semantic palette of lipgloss.AdaptiveColor
  values (primary/muted/faint text, chrome/border, accent, PR-state colors,
  badge backgrounds, row shade, button, switch). lipgloss resolves the
  light/dark variant per render from the terminal background, which Bubble
  Tea detects at startup; terminals that don't report it fall back to dark,
  preserving the original look.
- Replace every hardcoded ANSI color in shared/, submitview/, and
  modifyview/ with palette roles. The four pre-rendered status icons now
  render at use-time so their adaptive colors resolve correctly. The submit
  markdown preview picks glamour's light or dark style from the detected
  background.
- GH_STACK_THEME=auto|light|dark forces the palette for terminals that
  mis-detect (some SSH/tmux setups); wired via the root command's
  PersistentPreRun before any render. Documented in the README and CLI docs.

Neutral text/chrome use truecolor hex (GitHub Primer-inspired) for
predictability across themes, including solarized which repurposes ANSI
8-15; lipgloss downsamples on terminals without truecolor.

Tests verify the palette resolves differently for light vs dark and that
GH_STACK_THEME is honored.

* Apply background-aware colors to all command output

Background detection and the GH_STACK_THEME override (added for the TUIs)
only affected the interactive screens. Plain command output -- status
messages and interactive prompts -- went through the mgutz/ansi library
with fixed ANSI palette names (green/red/yellow/cyan/...), so it never
adapted to the terminal background and could read poorly on light or
solarized themes.

Unify everything on the same adaptive palette so all colors react to the
detected background and to GH_STACK_THEME.

- Extract internal/theme, a foundational package with no internal
  dependencies, that owns:
    - the background-aware lipgloss.AdaptiveColor palette (moved out of
      internal/tui/shared),
    - ApplyOverride(), the GH_STACK_THEME=auto|light|dark logic, and
    - non-TUI colorizers (Success/Error/Warning/Blue/Magenta/Cyan/Gray/
      Bold) plus FgSeqs(), which returns the raw start/reset escapes used
      to color the user's echoed prompt input.
- internal/tui/shared/theme.go now re-exports the palette, so the TUI code
  keeps referring to shared.ColorX unchanged.
- internal/config/config.go wires the Config.Color* funcs to the theme
  colorizers and drops mgutz/ansi (now an indirect dependency only).
- cmd/utils.go colors the prompt icon and echoed input via theme.
- cmd/root.go calls theme.ApplyOverride() in PersistentPreRun.

Detection adds no cost: because the command package imports Bubble Tea,
its init() already triggers (and caches) the terminal background query for
every command, so the non-TUI colorizers just read the cached value.
Terminals that don't answer the query fall back to the dark palette;
GH_STACK_THEME=light|dark forces it. Colors are truecolor on capable
terminals and downsample to the nearest ANSI color elsewhere.

Tests: internal/theme covers palette adaptiveness, ApplyOverride, the
colorizers, and FgSeqs; a new internal/config test verifies the wired-up
Config.Color* funcs adapt to the background when color is enabled.

Docs: README and the CLI reference note that GH_STACK_THEME now controls
all colored output, not just the interactive screens.

No behavior change beyond colors.
2026-06-29 20:11:09 -04:00
Sameen Karim b6bcce1bfe Add an interactive submit TUI for customizing each PR's title, description, and draft state (#147)
* Add submitview data model and PR draft override plumbing

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.

* Add single-screen submit TUI

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.

* Wire the single-screen submit TUI into `gh stack submit`

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.

* Use the API PR title/body for existing PRs and fix new-PR defaults

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).

* Stop mouse wheel from leaking escape characters into form fields

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.

* Re-enable mouse tracking after the external editor closes

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).

* dead code cleanup

* support mouse input to move cursor in title field

* use textArea for title to support word wrap for long inputs
2026-06-29 20:11:08 -04:00
Sameen Karim 0efc764f8c Redesign TUI shared header and embed the GitHub logo as an image (#143)
* Redesign the shared header and embed the GitHub logo as an image

Rework the shared gh-stack header (used by `view` and `modify`, and reused
by `submit` later in this stack) for a cleaner, more responsive look, and
replace the braille/ASCII Invertocat with a real image of the GitHub mark.

- Logo: embed the Invertocat PNG with go:embed and draw it via an inline-
  image protocol (kitty or iTerm2). It is image-or-nothing: when no protocol
  is available, stdout is not a TTY, or we are inside tmux/screen, no logo is
  drawn and the text falls back to the normal left padding. Detection is
  environment-based and cached so it never blocks the TUI, and a fixed kitty
  image id lets the header clear or replace the logo in place instead of
  leaving copies behind.
- Layout: place the logo in the top-left corner beside the title and version,
  with the stack-info lines left-aligned beneath it on the same left margin.
  Size the box to its content for each view so there is no trailing empty row.
- Responsiveness: hide the logo progressively — first when the viewport is too
  narrow, then a little before the rest of the header at short heights, where
  a vertical resize could otherwise leave a ghost of the inline image.
- Add unit tests for the header's responsive thresholds.

* Drop the unused HeaderHeight constant and dedupe the header config build

Follow-up to review feedback on the shared-header redesign:

- Remove the HeaderHeight constant. Nothing referenced it (callers compute
  height via HeaderHeightFor), and its doc described a "maximum" that
  HeaderHeightFor does not actually enforce, so the comment was misleading.
- In the view and modify View() methods, build the header config once and reuse
  it for both RenderHeader and the height reservation instead of rebuilding it
  twice per frame. The click/scroll handlers keep deriving the height from the
  same config, so the header's dimensions remain a single source of truth.
2026-06-29 20:11:08 -04:00
dependabot[bot] 5ed5693ed6 Bump github.com/cli/cli/v2 in the go_modules group across 1 directory (#110)
Bumps the go_modules group with 1 update in the / directory: [github.com/cli/cli/v2](https://github.com/cli/cli).


Updates `github.com/cli/cli/v2` from 2.92.0 to 2.93.0
- [Release notes](https://github.com/cli/cli/releases)
- [Changelog](https://github.com/cli/cli/blob/trunk/docs/release-process-deep-dive.md)
- [Commits](https://github.com/cli/cli/compare/v2.92.0...v2.93.0)

---
updated-dependencies:
- dependency-name: github.com/cli/cli/v2
  dependency-version: 2.93.0
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-29 09:25:20 -07:00
dependabot[bot] 54aa630be9 Bump github.com/cli/cli/v2 in the go_modules group across 1 directory (#103)
Bumps the go_modules group with 1 update in the / directory: [github.com/cli/cli/v2](https://github.com/cli/cli).


Updates `github.com/cli/cli/v2` from 2.86.0 to 2.92.0
- [Release notes](https://github.com/cli/cli/releases)
- [Changelog](https://github.com/cli/cli/blob/trunk/docs/release-process-deep-dive.md)
- [Commits](https://github.com/cli/cli/compare/v2.86.0...v2.92.0)

---
updated-dependencies:
- dependency-name: github.com/cli/cli/v2
  dependency-version: 2.92.0
  dependency-type: direct:production
  dependency-group: go_modules
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-22 12:24:37 -04:00
Sameen Karim 7a268fc380 modify command (#72)
* git primitives for modify cmd

* extract reusable TUI parts

* modify cmd

* recreate stack after modify

* add checks to prevent other modifications while modify is applying

* modify continue for resuming after resolving conflicts

* fix bug with duplicate stack entries after modifying

* reuse conflict resolution help msg from rebase

* additional confirmation before overwriting stack on remote

* fix recreate order of operations

Co-authored-by: Copilot <copilot@github.com>

* move base commit instead of cherry picking for fold up

* check to ensure we aren't left with zero branches

* unify and dedupe across view and modify tui

* more detailed help instructions

Co-authored-by: Copilot <copilot@github.com>

* only recommend submit if stack exists on remote

Co-authored-by: Copilot <copilot@github.com>

* tests for modify tui, apply modifications, submit modifications

* refactor submit for regular and pending modifications

* rename recover to abort

Co-authored-by: Copilot <copilot@github.com>

* docs for modify cmd

* tui styling updates

* updated tui screenshot

* addressing review comments

* Fix 4 bugs from code review

Bug 1: Move RevParseMap error check before using originalRefs.
The error from git.RevParseMap() was deferred past iteration of
originalRefs, which could panic on a nil map.

Bug 2: Differentiate cherry-pick vs rebase conflicts in modify.
Cherry-pick conflicts don't save state as 'conflict' phase, so
--continue won't work. Now prints --abort-only instructions for
cherry-pick conflicts.

Bug 3: Unwind now cleans up branches created by renames.
After restoring snapshot branches, Unwind deletes renamed branch
names that don't belong to the original snapshot.

Bug 4: Simplify push message in submit command.
Changed from 'Pushing N branches to remote...' to 'Pushing to
remote...' since individual branches may fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix 7 nit issues from code review

11: Add named constants for phase strings (PhaseApplying, PhaseConflict,
PhasePendingSubmit) in state.go; replace remaining raw literals in
state.go CheckStateGuard.

14: Fix bottomLines comment mismatch — listed 3 items but value is 2.

15: Extract magic number 88 to MinWidthForArt constant in header.go.

16: Remove unused stackview import anchor in model.go — the import
is used via types.go where BranchNode is embedded.

17: Simplify CheckStackLinearity parent resolution — ActiveBaseBranch
already handles skipping merged branches.

18: Fix rename undo matching any rename — add NewName check so only
the specific rename being undone is matched.

20: Add TestUndoRename and TestUndoRename_DoesNotAffectOtherRenames
to validate rename undo behavior.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Make cherry-pick conflicts recoverable via --continue

Previously, cherry-pick conflicts during fold-down operations could only
be resolved with --abort. Now they save full conflict state (phase,
conflict type, fold branch/target, remaining branches) to the state file,
enabling recovery via 'gh stack modify --continue'.

Changes:
- Add ConflictType field to StateFile (rebase or cherry_pick)
- Add FoldBranch/FoldTarget fields for cherry-pick context
- Add CherryPickContinue to git package (cherry-pick --continue)
- Save cherry-pick conflict state in ApplyPlan with remaining branches
- ContinueApply handles both rebase and cherry-pick conflicts
- Unified conflict messaging in cmd/modify.go (both types show --continue)
- Updated test to verify cherry-pick conflict state is saved correctly

* Apply suggestions from code review

Co-authored-by: Luke Ghenco <lukeghenco@github.com>
Co-authored-by: Sameen Karim <skarim@github.com>

---------

Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Luke Ghenco <lukeghenco@github.com>
2026-05-04 22:34:42 -04:00
Sameen Karim b01754e4a9 Initial release 2026-04-10 03:32:08 -04:00