Skip to content

insert branches with modify#107

Open
skarim wants to merge 5 commits into
skarim/modify-only-block-pr-changesfrom
skarim/insert-branch
Open

insert branches with modify#107
skarim wants to merge 5 commits into
skarim/modify-only-block-pr-changesfrom
skarim/insert-branch

Conversation

@skarim
Copy link
Copy Markdown
Collaborator

@skarim skarim commented May 22, 2026

add insert branch operation to modify TUI

Add i (insert below) and I (insert above) key bindings to the
interactive modify view, allowing users to insert new empty branches
into an existing stack. This follows Vim-inspired semantics where
lowercase i inserts below the cursor and uppercase I inserts above.

TUI behavior

When the user presses i or I, the TUI enters an insert input mode
(similar to rename mode) where they type a new branch name. The input
is validated against git ref naming rules, local branch uniqueness, and
in-stack name collisions. On confirm, a placeholder node is inserted at
the correct position in the branch list with a green "✚ insert"
annotation badge and green connector styling.

Insert is a structure operation — it works alongside fold, rename, and
drop, but is mutually exclusive with reorder (consistent with existing
mode exclusivity rules). Undo (z) removes the inserted node cleanly.

Apply engine

At apply time (Step 2 in the pipeline, between renames and folds), the
engine creates the new git branch at the parent branch's tip via
git.CreateBranch and inserts a BranchRef into the stack metadata at
the correct position. If the insertion changes the base of a branch
that has an open PR, affectsPRs is set to trigger a required
gh stack submit afterward.

Header shortcut updates

  • Combined the fold shortcuts into a single line: d/u - fold down/up
  • Added insert shortcuts on their own line: i/I - insert below/above
  • Reordered fold references throughout to list "down" before "up" for
    consistency with the insert shortcut ordering

Files changed

  • types.go: ActionInsertBelow/ActionInsertAbove types, IsInserted field,
    InsertedBranches in ApplyResult
  • model.go: key bindings, insert input mode, undo, mode exclusivity,
    annotation, styling, header shortcuts, effective-index tracking to
    prevent false reorder detection when inserts shift node positions
  • styles.go: green insert badge/branch/connector styles
  • status.go: insert counting in pending change summary
  • help.go: new "Insert below / above" section, reordered fold heading
  • apply.go: BuildPlan and ApplyPlan handle insert actions
  • modify.go: updated command description and success summary
  • README.md: updated keybindings table

Test coverage

  • 16 new TUI tests: insert below/above, top/bottom edges, undo, mode
    exclusivity, merged branch guard, cancel/empty input, duplicate name
    validation, pending summary counting, annotation rendering, mixed
    operations with drop/fold, apply acceptance
  • 4 new apply tests: BuildPlan produces correct insert actions,
    ApplyPlan creates branches and updates stack metadata, insert at
    stack start uses trunk as parent, affectsPRs triggered when inserting
    before a branch with an open PR

Stack created with GitHub Stacks CLIGive Feedback 💬

@skarim skarim force-pushed the skarim/insert-branch branch from 8dd6ae0 to 5466a8a Compare May 23, 2026 17:23
@skarim skarim marked this pull request as ready for review May 23, 2026 21:42
Copilot AI review requested due to automatic review settings May 23, 2026 21:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 “insert branch” operation to the gh stack modify TUI, letting users stage insertion of new empty branches above/below the cursor (Vim-style I/i) and apply them via the modify engine.

Changes:

  • Extend modifyview to support inserting placeholder nodes (modal input, validation, undo behavior, styling/annotations, and pending-change summaries).
  • Teach the apply engine to create inserted branches and update stack metadata during apply.
  • Update CLI/docs/help text and keybinding references to include insert semantics.
Show a summary per file
File Description
README.md Documents insert operation and updates keybinding table.
internal/tui/shared/header.go Adjusts header sizing constants to accommodate updated shortcut text.
internal/tui/modifyview/types.go Adds insert action types and tracks inserted nodes/results.
internal/tui/modifyview/styles.go Adds green styling for inserted nodes/connectors/badges.
internal/tui/modifyview/status.go Counts inserts in the pending change summary and avoids false move detection.
internal/tui/modifyview/model.go Implements insert mode (i/I), validation, rendering, undo semantics, and mode exclusivity updates.
internal/tui/modifyview/model_test.go Adds/updates extensive tests for insert behaviors and related edge cases.
internal/tui/modifyview/help.go Adds insert section and updates help description text.
internal/modify/apply.go Adds insert handling in apply pipeline and updates move detection to account for inserts.
internal/modify/apply_test.go Adds BuildPlan/ApplyPlan tests covering insert behavior and PR-affecting cases.
docs/src/content/docs/reference/cli.md Updates keybindings and apply-phase description to include inserts.
docs/src/content/docs/introduction/overview.md Mentions gh stack modify as the restructuring tool (now including insert).
docs/src/content/docs/guides/workflows.md Updates restructure workflow docs and key list with insert.
docs/src/content/docs/guides/ui.md Clarifies UI vs CLI restructuring and references modify/insert.
docs/src/content/docs/guides/modify.md Adds insert documentation and updates limitations/mode-exclusivity text.
docs/src/content/docs/faq.md Updates FAQ to include insert in modify capabilities/examples.
cmd/modify.go Updates command description and success summary to include inserted branches.
cmd/add.go Improves error guidance, pointing users to gh stack modify for inserting into an existing stack.
cmd/add_test.go Updates test to assert new guidance output is shown.

Copilot's findings

Tip

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

  • Files reviewed: 19/20 changed files
  • Comments generated: 2

Comment thread internal/modify/apply.go
Comment on lines +251 to +262
// Build the active branch order from the stack (as of now, after renames)
activeIdx := 0
for _, b := range s.Branches {
if b.IsMerged() {
continue
}
if b.Branch == newName {
// already added in a previous iteration — skip
break
}
activeIdx++
}
Comment thread internal/modify/apply.go
Comment on lines +298 to +305
// Create the git branch at the parent's tip
if err := git.CreateBranch(newName, parentBranch); err != nil {
unwindErr := Unwind(cfg, gitDir, snapshot, stackIndex, sf, plan)
if unwindErr != nil {
return nil, nil, fmt.Errorf("creating branch %s failed (%v) and unwind failed (%v)", newName, err, unwindErr)
}
return nil, nil, fmt.Errorf("creating branch %s from %s: %w", newName, parentBranch, err)
}
@skarim skarim force-pushed the skarim/modify-only-block-pr-changes branch from abd7102 to 7b1c0c7 Compare May 23, 2026 22:05
@skarim skarim force-pushed the skarim/insert-branch branch 2 times, most recently from 523129b to e0958f9 Compare May 23, 2026 22:33
@skarim skarim force-pushed the skarim/modify-only-block-pr-changes branch from 7b1c0c7 to 8503b93 Compare May 23, 2026 22:33
skarim added 5 commits May 23, 2026 20:36
Add `i` (insert below) and `I` (insert above) key bindings to the
interactive modify view, allowing users to insert new empty branches
into an existing stack. This follows Vim-inspired semantics where
lowercase `i` inserts below the cursor and uppercase `I` inserts above.

## TUI behavior

When the user presses `i` or `I`, the TUI enters an insert input mode
(similar to rename mode) where they type a new branch name. The input
is validated against git ref naming rules, local branch uniqueness, and
in-stack name collisions. On confirm, a placeholder node is inserted at
the correct position in the branch list with a green "✚ insert"
annotation badge and green connector styling.

Insert is a structure operation — it works alongside fold, rename, and
drop, but is mutually exclusive with reorder (consistent with existing
mode exclusivity rules). Undo (`z`) removes the inserted node cleanly.

## Apply engine

At apply time (Step 2 in the pipeline, between renames and folds), the
engine creates the new git branch at the parent branch's tip via
`git.CreateBranch` and inserts a `BranchRef` into the stack metadata at
the correct position. If the insertion changes the base of a branch
that has an open PR, `affectsPRs` is set to trigger a required
`gh stack submit` afterward.

## Header shortcut updates

- Combined the fold shortcuts into a single line: `d/u - fold down/up`
- Added insert shortcuts on their own line: `i/I - insert below/above`
- Reordered fold references throughout to list "down" before "up" for
  consistency with the insert shortcut ordering

## Files changed

- types.go: ActionInsertBelow/ActionInsertAbove types, IsInserted field,
  InsertedBranches in ApplyResult
- model.go: key bindings, insert input mode, undo, mode exclusivity,
  annotation, styling, header shortcuts, effective-index tracking to
  prevent false reorder detection when inserts shift node positions
- styles.go: green insert badge/branch/connector styles
- status.go: insert counting in pending change summary
- help.go: new "Insert below / above" section, reordered fold heading
- apply.go: BuildPlan and ApplyPlan handle insert actions
- modify.go: updated command description and success summary
- README.md: updated keybindings table

## Test coverage

- 16 new TUI tests: insert below/above, top/bottom edges, undo, mode
  exclusivity, merged branch guard, cancel/empty input, duplicate name
  validation, pending summary counting, annotation rendering, mixed
  operations with drop/fold, apply acceptance
- 4 new apply tests: BuildPlan produces correct insert actions,
  ApplyPlan creates branches and updates stack metadata, insert at
  stack start uses trunk as parent, affectsPRs triggered when inserting
  before a branch with an open PR
Fix three bugs with the insert branch feature in the modify TUI, and
adjust rename behavior on inserted nodes.

## Bug 1: False "moved" annotations on existing branches

After inserting a branch, all branches below the insertion point
displayed "↕ moved 1 layer down" annotations. This happened because
`nodeAnnotation` and `toNodeData` compared each node's
`OriginalPosition` against its raw array index, which gets shifted
when an inserted node is added to the slice.

Fix: introduce an `effectiveIdx` parameter that counts only
non-inserted nodes, so position comparisons reflect the original
ordering. The View loop computes effective indices by incrementing
only for non-inserted nodes and passes them to the rendering
functions.

## Bug 2: Header branch count inflated by staged inserts

The branch count in the header ("N branches") included inserted
placeholder nodes, making it appear as though the stack had grown
before changes were applied.

Fix: `buildHeaderConfig` now excludes `IsInserted` nodes from the
branch count. The count reflects only the original branches in the
stack.

## Bug 3: Operations allowed on inserted placeholder nodes

Inserted nodes could be folded into other branches, which makes no
sense for a placeholder with no commits. Additionally, the "last
branch" guard counted inserted nodes as active, allowing users to
drop all original branches and bypass the empty-stack check.

Fix:
- `fold()` rejects inserted nodes with a descriptive error message.
- `toggleDrop()` on an inserted node removes it entirely and pops
  the original insert action from the undo stack (clean cancellation
  rather than a separate undo entry).
- All three "active branch" guards (`toggleDrop`, `fold`, `tryApply`)
  now exclude `IsInserted` nodes, ensuring at least one original
  branch always remains in the stack.

## Rename on inserted branches

Instead of blocking renames on inserted nodes, pressing `r` now
enters rename mode and updates the insert action's name in place.
The node's `Ref.Branch` and `PendingAction.NewName` are both updated
directly — no separate rename action is created in the undo stack.
This lets users fix a typo without having to drop and re-insert.

## Tests added

- `TestInsertDoesNotShowMovedAnnotation` — verifies no false move
  annotations appear on existing branches after an insert
- `TestBranchCountExcludesInserts` — verifies header count stays
  stable after insert
- `TestCannotFoldInsertedBranch` — verifies fold is blocked
- `TestCannotRenameInsertedBranch` — verifies rename updates the
  insert name in place
- `TestDropInsertedBranchRemovesIt` — verifies drop removes the node
- `TestDropInsertedBranchCanBeUndone` — verifies drop pops the
  original insert from the undo stack
- `TestCannotDropAllOriginalBranchesWithInsert` — verifies the
  empty-stack guard excludes inserted nodes
@skarim skarim force-pushed the skarim/insert-branch branch from e0958f9 to e7d5424 Compare May 24, 2026 00:36
@skarim skarim force-pushed the skarim/modify-only-block-pr-changes branch from 8503b93 to 81eaf1d Compare May 24, 2026 00:36
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