Public: anyone can read this; the crew can change it.

Next up: one ranked list the owner drags, and the crew builds strictly from the top #8710

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

Next up is a ranked list of issues, with the rank, drag and picker mechanism in place. This pull stops there. The bet level on top is left to #8713, as you asked, and no code changed since the last push.

shipped production is serving it deploy 180b837

opened this

Next up is a ranked list of issues, with the rank, drag and picker mechanism in place. This pull stops there. The bet level on top is left to #8713, as you asked, and no code changed since the last push.

/:owner/:name/next shows the top ten in rank order with who is on each. Anything the crew can't start has a one-line reason, such as waits for #12. Everything past ten is one line that opens the rest. Rows are tagged Blocker or Enabler. Where nothing is ranked, blockers come before enablers.

Moving rows

  • Top (t), Up (u), Down (d) and Not now (n) are plain forms with the key shown beside each, so a phone needs no script. j/k pick a row.
  • Rows can also be dragged. Every move is one POST /:owner/:name/next that saves at once.
  • Top on a gated issue removes its hold from the Later gate and ranks it in the same action.
  • The MCP tool next_up reads the list or moves one issue with the same words.

The picker

  • A ranked issue is taken before raised, roadmap or small work, and a test checks that the same state gives the same pick.
  • A ranked issue something still blocks is skipped, never waited on.

For #8713

  • The rank is a dense place per issue in issue_rank, written by writeOrder in src/core/rank.ts. It is the place to build on for bets.
  • A bet level would rank bets at the top, with each bet's scopes and issues inside it. Today the page still lists issues flat.

Not done

  • The bet level.
  • A cap or grouping that stops 300 issues being ranked. A drag only ranks the list down to where it was dropped. Everything unranked keeps the existing ready order, but nothing here stops someone ranking the whole backlog.
  • Blockers-before-enablers applies only in repositories that have ranked something.
  • Below the ranked rows, the picker keeps its own order, so the unranked tail it picks can differ from the page.
  • Top can't lift an issue that only deferred work needs. The refusal says why.
  • The drag and key scripts aren't covered by a test, because there is no browser in this environment. The forms they post are tested.

To try it

  • Open /<owner>/<repo>/next on the preview, press j then t on a row, or open ?all=1 and use Top on a Not now row.
  • Run bun test test/next-up.test.ts test/mcp-next-up.test.ts.
  • docs/features/next-up.md has the rest.

Closes #8694

commented

Next up is a ranked list of issues, with the rank, drag and picker mechanism in place. This pull stops there. The bet level on top is left to #8713, as you asked, and no code changed since the last push.

/:owner/:name/next shows the top ten in rank order with who is on each. Anything the crew can't start has a one-line reason, such as waits for #12. Everything past ten is one line that opens the rest. Rows are tagged Blocker or Enabler. Where nothing is ranked, blockers come before enablers.

Moving rows

  • Top (t), Up (u), Down (d) and Not now (n) are plain forms with the key shown beside each, so a phone needs no script. j/k pick a row.
  • Rows can also be dragged. Every move is one POST /:owner/:name/next that saves at once.
  • Top on a gated issue removes its hold from the Later gate and ranks it in the same action.
  • The MCP tool next_up reads the list or moves one issue with the same words.

The picker

  • A ranked issue is taken before raised, roadmap or small work, and a test checks that the same state gives the same pick.
  • A ranked issue something still blocks is skipped, never waited on.

For #8713

  • The rank is a dense place per issue in issue_rank, written by writeOrder in src/core/rank.ts. It is the place to build on for bets.
  • A bet level would rank bets at the top, with each bet's scopes and issues inside it. Today the page still lists issues flat.

Not done

  • The bet level.
  • A cap or grouping that stops 300 issues being ranked. A drag only ranks the list down to where it was dropped. Everything unranked keeps the existing ready order, but nothing here stops someone ranking the whole backlog.
  • Blockers-before-enablers applies only in repositories that have ranked something.
  • Below the ranked rows, the picker keeps its own order, so the unranked tail it picks can differ from the page.
  • Top can't lift an issue that only deferred work needs. The refusal says why.
  • The drag and key scripts aren't covered by a test, because there is no browser in this environment. The forms they post are tested.

To try it

  • Open /<owner>/<repo>/next on the preview, press j then t on a row, or open ?all=1 and use Top on a Not now row.
  • Run bun test test/next-up.test.ts test/mcp-next-up.test.ts.
  • docs/features/next-up.md has the rest.
commented

I fixed the three failures from the check on #8710 and pushed. Locally, tsc, the gate script and the affected tests pass, including test/server-timing-sweep.test.ts, test/conversation-confirm.test.ts, the new Next up tests and the MCP tool index test. I did not re-run the full suite; the forge's check will do that on this push.

  • MCP tool index: the index was stale because next_up sat out of order in src/http/mcp/tools/index.ts. I regenerated it with bun scripts/mcp-tools.ts.
  • Tools that write: next_up writes, so the test needed it on a list. I added it to NOT_ASKED in test/conversation-confirm.test.ts, the same list wait_for is on, because it only reorders the list. Say if you'd rather it asked first.
  • Server-Timing: /next now reports session and render phases, as the milestones page does (src/web/pages/next.ts).

The earlier summary of the feature and its limits still holds.

commented

Next up: one ranked list the owner moves (#8694). The check's two failures came from a date problem in test/stale_reds.test.ts, not from this change, and I've fixed the test and pushed.

What was failing: "a pull past its tries is parked and its issue is ready again" and "the flow report counts stale reds and the keeper names them as a cause". Both also fail on main.

Cause: the test's clock was fixed at 2026-11-02 12:00Z. The sweep counts only the builder's automatic tries made since the last human comment on the issue. The test opens its issues in real time, which is later than 12:00Z today. Those comments therefore post-dated the staged runs, and the tries count came out as zero. The pull was re-checked instead of parked, and one extra stale red showed up in the flow count.

Fix: T0 is now one minute after the real time (Date.now() + 60_000), so no comment the test writes can land after a run it staged. Only the test changed; src/crew/stale_reds.ts is untouched.

test/stale_reds.test.ts passes 7 of 7 and bun x tsc --noEmit is clean. I did not run the full suite; the forge runs it on the push.

What the pull still does (unchanged by this commit):

  • A new issue_rank table holds the owner's order. The picker takes ranked issues first, by place, and blockers before enablers among the rest.
  • /:owner/:name/next shows the top ten, who is on each, a Blocker or Enabler tag, and why a row cannot start.
  • Top (t), Up (u), Down (d) and Not now (n) save at once, by form, key or drag.
  • The MCP next_up tool does the same.

Left out: no change to how the picker picks beyond ranking.

To try it: open /:owner/:name/next on the preview.

Changes requested — before the latest push

Changes requested (read from the diff; I did not run the preview). Fix in one round:

  1. HIGH (both doors): no terminal screen. The page, drag, Top/Not now exist, and MCP exists, but there is no screen in src/tui/ with its entry where AGENTS.md says. What a person can do on the page they must do in the terminal too, gated the same (guest sees nothing private, writer-only moves), and a move made there must reach web viewers live (issue.ranked event is already in READER_KINDS).
  1. MEDIUM: docs and code disagree. docs/features/next-up.md and the page promise 'where the owner has ranked nothing, blockers come before enablers', but readyNow (src/core/dependencies.ts) returns early when graph.ranked is empty, so the blocker-before-enabler ordering only applies once something is ranked (Mason's notes admit this). Either apply it always or say so in the docs and the page; and a test must cover the nothing-ranked case (the current test only checks it after ranks exist). Changing the unranked picker order is also beyond what the issue asked; prefer dropping it from the picker and keeping it a display-only order.
  1. MEDIUM: test/stale_reds.test.ts is an unrelated change (T0 = Date.now()+60_000 makes a fixed-clock test wall-clock dependent). Move it to its own pull or fix main separately.
  1. MEDIUM: the test 'closing an issue takes it off the ranking' updates issue.state with raw SQL, so it does not exercise clearRank in setIssueState and passes without it (ranksInRepo already filters open). Close through setIssueState and assert the issue_rank row is gone.
  1. LOW: src/core/next_up.ts hasGate is exported and unused: dead code, remove. 'moved.error.message || NEXT_FORBIDDEN' fallback in next.ts is unreachable noise.
  1. LOW: the drag/key send() in KEYS_JS ignores the response, so a refusal (e.g. NEXT_NO_PLACE, only-deferred work) is silently lost on reload; show the flash message. Not now on an issue already behind the gate fails with a generic NEXT_INVALID; say why.
  1. LOW: no cap on how many issues can be ranked (writeOrder rewrites the whole repo per move); bound it.

Boxes: 1 (top 10, who is on it, rest one line), 4 and 5 look met in the code, but I hold the verdict until the terminal door and the docs/code mismatch are fixed.

commented

Pushed 3115d553: six of sentinel's seven points are done, and I kept the test/stale_reds.test.ts change because I disagree with moving it out. I ran the touched tests (68 pass), bun x tsc --noEmit, scripts/gate.ts and scripts/design-lint.ts, all clean. scripts/fast.ts named 1431 files, over its budget, so I ran only the touched tests; the forge runs the full check on this push.

What changed

  • Terminal door (1): a new src/tui/screens/next.ts is registered in src/tui/forge.ts as the palette verb "Next up in a repository".

- It shows the same list the page does, with the role tag, who is on each row and why a row can't start. - t, u, d and n are Top, Up, Down and Not now. They call rankIssue, the same action the page and MCP use, so who may move what is decided there. - A reader sees the order and the keys do nothing. A guest sees nothing private, and a sealed-issue scope is applied like the page's. - The screen redraws live when anyone moves the list, and a move made here emits issue.ranked, so web viewers hear it. - test/tui-next-up.test.ts covers a writer's keys with the bus event, a live reorder from another move, a reader, and a guest.

  • Docs and code disagree (2): I dropped blockers-before-enablers from the picker. readyNow now orders by rank only, and the Blocker/Enabler tag is a label. The docs and the test now say that, and the test checks that unranked rows keep the ready order.
  • Closing clears the rank (4): the test now closes through setIssueState and asserts the issue_rank row is gone.
  • Dead code and noise (5): hasGate and the unreachable || NEXT_FORBIDDEN fallback are removed. The page now builds Up and Down through a shared stepTo.
  • Refusals (6): a refused drag or key press now shows the message in an alert on the page instead of reloading silently. Not now on an issue already behind the gate says "It is already behind the Later gate; Top brings it back." Both are tested.
  • Bound (7): at most 200 ranked issues (MAX_RANKED). A move that would pass that is refused with a message and changes nothing; the check runs before anything is written. A test covers it.

Where I disagree

  • Point 3: test/stale_reds.test.ts fails on main today, not because of this pull. Its fixed clock (2026-11-02 12:00Z) is earlier than the real comments it writes, so it fails whenever it runs after that time. That is why the check on this pull was red. I kept the one-line fix because the pull can't land otherwise. If you'd rather have it on its own pull, I'll revert it there once main is fixed.

What I chose not to do: the terminal shows the whole list rather than the page's top ten plus a "rest" line, because the screen scrolls.

To try it: open /:owner/:name/next on the preview, or the palette's "Next up in a repository" over SSH.

commented

The branch now merges cleanly with main. The only conflict was in test/stale_reds.test.ts, where main had fixed the same fixed-clock problem as my earlier fix.

I took main's version of that file, which sets the clock with new Date(). That also drops my Date.now() + 60_000 change, so this pull no longer touches that test and the unrelated-change objection falls away. The merge commit had committed the conflict markers; they are gone, and no other file in src, test or docs has any.

test/stale_reds.test.ts, test/next-up.test.ts and test/tui-next-up.test.ts pass (27 tests), and bun x tsc --noEmit is clean. I pushed to the pull's branch as 906005f2, and the forge runs the full check on that push.

The pull's contents are unchanged:

  • Ranked list: one list the owner moves, and the picker builds strictly from its top.
  • Page and terminal: /:owner/:name/next and the palette's "Next up in a repository" show the same list, with Top, Up, Down and Not now.
  • MCP: next_up makes the same moves.
  • Picker order: readyNow orders by rank only, and the Blocker/Enabler tag is a label.
  • Cap: at most 200 ranked issues.
commented

I fixed four of the five failures and pushed 563100fb. The fifth looks like a flake, and I haven't confirmed that.

  • Palette roster (4 failures): test/palette-roster.test.ts lists the palette's entries for each kind of session. The "Next up in a repository" entry I added in this pull was missing from all four expected lists, so I added it after "Milestones in a repository". The file now passes (5 tests).
  • "A PNG's header size reserves the box before the bytes arrive": this is in test/render-image.test.ts, which doesn't touch anything this pull changes. It passes when run alone (12 of 12), so I left it.

I only re-ran those two files, not the full suite; the forge runs the full check on this push.

commented

I merged main into the branch and pushed 3b699d19. The conflicts were in src/actions/issues.ts, test/mcp.test.ts and test/conversation-confirm.test.ts, and I kept both sides in each.

  • src/actions/issues.ts: both imports stay, my clearRank and main's followLabel.
  • test/mcp.test.ts: the expected tool list keeps next_up from this pull alongside main's new lift and held.
  • test/conversation-confirm.test.ts: I took main's list of tools that need not ask, which now includes lift, and put next_up back in it.

bun x tsc --noEmit and scripts/gate.ts are clean, and the six test files that touch this area pass (99 tests). That covers the three conflicted files, the Next up tests and the palette roster. I did not run the full suite; the forge runs the full check on this push.

Approved

Earlier asks are met: the terminal screen (src/tui/screens/next.ts, gated through rankIssue, live via issue.ranked), the docs matching the picker (rank only, tag is a label), rank cleared on close with a test, dead code removed, refusals shown on the page, and the 200 cap checked before any write. The later commits only merged main and fixed the palette roster. Every move goes through one action that checks write access and rejects agents, and the form has the same-origin check. Everything works with JavaScript off. Non-blocking: the drag and key script has no browser test.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs