Skip to content

Fix bw ready: surface frontier in claimed-but-open epics and under deferred parents#132

Merged
jallum merged 2 commits into
mainfrom
bw-lm4/ready-frontier-fix
May 31, 2026
Merged

Fix bw ready: surface frontier in claimed-but-open epics and under deferred parents#132
jallum merged 2 commits into
mainfrom
bw-lm4/ready-frontier-fix

Conversation

@jallum

@jallum jallum commented May 30, 2026

Copy link
Copy Markdown
Owner

What

bw ready builds a subtree overlay that suppresses everything beneath a "display root." Drill-in (revealing the next-step frontier) only fired for in_progress/in_review nodes. Two reachable states were left with a hidden work frontier:

1. Starting a child directly orphans the frontier

bw start <child> moves only that issue to in_progress — it never promotes the parent epic. So the epic stays open, remains a display root, and its whole subtree (including open, unblocked siblings) is suppressed. bw ready collapses to just the epic.

epic (open) ├─ B ├─ C        # all open
$ bw start B                 # natural: start the task, not the epic
$ bw ready   →  ○ epic       # before: C is invisible, B gone
$ bw ready   →  ○ epic       # after:  drills in
                   ○ C        #          surfaces the live frontier

2. A deferred parent hides its open children

A future-deferred epic acted as a display root and suppressed its open, unblocked children, so they dropped off the global ready frontier (scoped bw ready <epic> still showed them).

epic (deferred → 2099) ├─ E ├─ F    # E,F open + unblocked
$ bw ready   →  "no ready issues"   # before
$ bw ready   →  ❄ epic              # after
                   ○ E   ○ F

Fix

Both fixes are in the walk inside buildSubtreeOverlay (internal/issue/blocking.go) — display-layer only, no data mutation:

  • An open node with any in_progress/in_review descendant is treated as underway: drill past it to surface the frontier, and mark the node itself suppressed.
  • A future-deferred node (unexpired defer date) is drilled past so its open children surface; the node stays hidden (the deferred loop won't re-add it while unexpired).

An open epic with no claimed work inside still collapses to itself as before — you start it to reveal its children.

Tests

  • TestReadyDrillsPastOpenRootWithClaimedChild — frontier surfaces after starting a child directly
  • TestReadyOpenRootNoClaimedWorkStillCollapses — guard: unstarted epic still collapses
  • TestReadyDeferredParentSurfacesOpenChildren — open children surface under a deferred epic

Full suite green.

Closes bw-lm4.

…ferred parents (bw-lm4)

The ready subtree overlay only drilled past in_progress/in_review nodes.
Two reachable states were left with a hidden work frontier:

1. Starting a child directly (bw start <child>) leaves the parent epic
   open, but work has begun inside it. The open epic stayed a display
   root, so its whole subtree — including open, unblocked siblings — was
   suppressed and ready collapsed to just the epic. Now an open node with
   any in_progress/in_review descendant is treated as underway: we drill
   past it to surface the live frontier and suppress the node itself.

2. A future-deferred epic acted as a display root and suppressed its open,
   unblocked children, so they vanished from the global ready frontier
   (scoped ready still showed them). Now we drill past a future-deferred
   node so its open children surface.

Display-layer only; no data mutation. Adds regression tests for both
states plus a guard that an unstarted epic with no claimed work still
collapses as before.
@jallum jallum requested a review from iautom8things May 30, 2026 12:29

@iautom8things iautom8things left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, otherwise. Just need to add tests for the recursive bit.

t.Errorf("in_progress child B should not appear in ready, got %v", ids)
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// When work is claimed two levels down (a grandchild started while both the
// epic and the intermediate node stay open), Ready() must still recognize the
// subtree as underway: suppress the open epic AND the open intermediate, and
// surface the open sibling as the frontier. This exercises the recursive
// descent in subtreeHasClaimedWork
func TestReadyDrillsPastOpenRootWithTransitivelyClaimedChild(t *testing.T) {
env := testutil.NewEnv(t)
defer env.Cleanup()
epic, _ := env.Store.Create("Epic", issue.CreateOpts{Type: "epic"})
mid, _ := env.Store.Create("Mid", issue.CreateOpts{Parent: epic.ID})
leaf, _ := env.Store.Create("Leaf", issue.CreateOpts{Parent: mid.ID})
sib, _ := env.Store.Create("Sib", issue.CreateOpts{Parent: epic.ID})
env.CommitIntent("setup")
// Start the grandchild directly; epic and mid are left open.
if _, err := env.Store.Start(leaf.ID, ""); err != nil {
t.Fatalf("Start leaf: %v", err)
}
env.CommitIntent("start " + leaf.ID)
ready, err := env.Store.Ready()
if err != nil {
t.Fatalf("Ready: %v", err)
}
ids := make(map[string]bool)
for _, r := range ready {
ids[r.ID] = true
}
if !ids[sib.ID] {
t.Errorf("open sibling should surface as the frontier, got %v", ids)
}
if ids[epic.ID] {
t.Errorf("open epic should be suppressed (transitively claimed work), got %v", ids)
}
if ids[mid.ID] {
t.Errorf("open intermediate node should be suppressed (claimed child below it), got %v", ids)
}
if ids[leaf.ID] {
t.Errorf("in_progress leaf should not appear in ready, got %v",
}
}

Addresses review feedback on #132: exercise the recursive descent in
subtreeHasClaimedWork via a grandchild started while the epic and the
intermediate node both stay open. Verifies both open ancestors are
suppressed and the open sibling surfaces as the frontier.
@jallum

jallum commented May 31, 2026

Copy link
Copy Markdown
Owner Author

Added TestReadyDrillsPastOpenRootWithTransitivelyClaimedChild (your suggested test, with the truncated final t.Errorf completed) in 44c25d8. It starts a grandchild while both the epic and the intermediate node stay open, and asserts both open ancestors are suppressed while the open sibling surfaces — exercising the recursive descent in subtreeHasClaimedWork. Full package green.

@iautom8things iautom8things self-requested a review May 31, 2026 12:06
@jallum jallum merged commit 8db0870 into main May 31, 2026
1 check passed
@jallum jallum deleted the bw-lm4/ready-frontier-fix branch May 31, 2026 15:38
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