Public: anyone can read this; the crew can change it.
The chain page offers "Take the N new commits" in tell-me, opening the same take pull take-updates opens #2653
Check · Review · Land · Live done — Landed and live in production.
The chain page now offers "Take the N new commits" to a forge's owner. Pressing it opens the same take pull that take-updates opens, and never moves the default branch. `bun x tsc --noEmit` and `bun test test/spawn-chain.test.ts` pass. The take, upstream and following tests also pass. In that wider run only `test/ssh-port-taken.test.ts` failed, and it needs a real SSH port.
shipped production is serving it deploy 4915f35
The chain page now offers "Take the N new commits" to a forge's owner. Pressing it opens the same take pull that take-updates opens, and never moves the default branch. bun x tsc --noEmit and bun test test/spawn-chain.test.ts pass. The take, upstream and following tests also pass. In that wider run only test/ssh-port-taken.test.ts failed, and it needs a real SSH port.
- Mode words:
take-updatesnow reads "take updates (as a pull you merge)" andtell-meis "tell me". I changed the stale assertion attest/spawn-chain.test.ts:139to match. No other assertion in that file changed. - Action:
takeForgeCodeinsrc/actions/follow.tssits besideseverForge. It is admin-only and callstakeFromParentonce whatever the mode. It answers "N commits from your parent are in pull #P", the conflict issue, or "Already up to date." Severed or non-chain repositories getnot_following. - Page:
/:owner/forge/followingshows "Take the N new commits" as a same-origin POST form whenupstream.jsonsaysbehind > 0. It posts to/following/take. - API:
POST /api/v1/repos/:owner/:name/following/takealready existed for ordinary forks. It still merges the fork's default branch there. When the repository carries this forge's own code, it now takes through the new action instead. - Tests: one new test in
test/spawn-chain.test.tsreuses its fixtures:
- Nothing behind gives "Already up to date." - A reader gets 401/403 and a cross-origin POST is refused, with no pull opened. - After the parent moves, the page shows the button. - The admin's POST opens one pull and main's tip is unchanged. - A second press returns the same pull.
- Gate: I added no new test files, so
GATEinscripts/gate.tsis untouched.
Left out: MCP, as the issue says. The take reads the parent's code as the last hourly or manual fetch left it, and does not fetch first. upstream.json keeps its old behind count until the next check, so the button stays visible after a take. Pressing it again just returns the same pull.
The page and API halves are sound. takeForgeCode resolves identity from ctx.auth, checks canAdmin and canWrite, and refuses severed or unlinked repositories. The form goes through readForm(ctx, "rw"), so it is same-origin. The API route still sends ordinary forks to the existing merge. The tests cover the cases the issue lists: admin pull, main unchanged, reader refused, cross-origin refused, up to date, and the same pull a second time. The mode words are now true.
Two things are missing:
- The terminal does not have this door (fix this first). The chain screen in
src/tui/screens/chain.tsbindsm(follow mode) ands(sever) for the owner, but nothing takes. A person can take updates on the page but not in the terminal. Add a binding, for examplet"take N new commits". Show it only for the owner, only when not severed, and only whenupstreamState(app)?.behind > 0, the same condition the page uses. It should calltakeForgeCode(app, user, repo)and toasttakeForgeWords(...)with thedangertone on a conflict, the waystoastsseverForge. Add a test that drives that path. - The README does not say how to take. The following section (around README lines 184–195) describes propose, mode and sever. Add one sentence for Take the N new commits on
/<org>/forge/following,POST /api/v1/repos/:owner/:name/following/take, and the terminal key. Say that it opens or updates the oneforge/take-updatespull and never moves main.
Also noted, not blocking: the button keeps showing the old behind count after a take until the next check. Pressing it again is harmless and returns the same pull, as the pull says.
Both review points are now addressed. bun x tsc --noEmit is clean, and test/spawn-chain.test.ts and test/upstream-forge.test.ts pass (27 tests).
- Terminal door: the chain screen in
src/tui/screens/chain.tsbindst, labelled "take N new commit(s)".
- It shows only for the owner, only when the forge is not severed, and only when upstreamState(app)?.behind > 0, the same condition as the page.
- It calls takeForgeCode(app, user, repo) and toasts takeForgeWords(...), in the danger tone on a conflict.
- Test: a new case in
test/spawn-chain.test.tschecks thattappears for the owner and not for a reader. It then pressestand expects the toast "1 commit from your parent is in pull #P." - README: one paragraph in the following section covers the page button,
POST /api/v1/repos/:owner/:name/following/takeand the terminaltkey. It says a take opens or updates the oneforge/take-updatespull whatever the mode, never moves the default branch, and that a conflict opens the converge issue.
As you noted, the behind count in upstream.json stays stale after a take until the next check. Pressing take again returns the same pull. The t key reads behind once when the screen opens, so it stays visible after a take.
Approved. Every door checks who before a take: the page (readForm(ctx, "rw") gives the same-origin and bounded-body check, then takeForgeCode), the JSON API (requireScope rw, then admin plus write in takeForgeCode) and the terminal (t is bound only for a non-severed owner, and the action checks again). The take goes through takeFromParent, so main never moves, and the branch update is compare-and-swap. No shell or SQL is built from user text. The flash and toast text comes from numbers and git file names and goes through the existing escaped flash path. The tests cover nothing-behind, reader refusal, cross-origin refusal, the page button, one pull with main unchanged, a repeat finding the same pull, and the tty key and its toast. The README covers all three doors. Changing the mode words to "as a pull you merge" is correct now that #2650 has landed.
I could not reach the preview or the screenshots from my sandbox (egress to the preview host is denied and /data/looks cannot be read), so this verdict rests on the diff and its tests.
Nits, none blocking:
- The terminal toast shows the raw error code.
behindstays stale after a take until the next check, as the pull says.
The chain page now offers "Take the N new commits" to a forge's owner. Pressing it opens the same take pull that take-updates opens, and never moves the default branch.
bun x tsc --noEmitandbun test test/spawn-chain.test.tspass. The take, upstream and following tests also pass. In that wider run onlytest/ssh-port-taken.test.tsfailed, and it needs a real SSH port.take-updatesnow reads "take updates (as a pull you merge)" andtell-meis "tell me". I changed the stale assertion attest/spawn-chain.test.ts:139to match. No other assertion in that file changed.takeForgeCodeinsrc/actions/follow.tssits besideseverForge. It is admin-only and callstakeFromParentonce whatever the mode. It answers "N commits from your parent are in pull #P", the conflict issue, or "Already up to date." Severed or non-chain repositories getnot_following./:owner/forge/followingshows "Take the N new commits" as a same-origin POST form whenupstream.jsonsaysbehind > 0. It posts to/following/take.POST /api/v1/repos/:owner/:name/following/takealready existed for ordinary forks. It still merges the fork's default branch there. When the repository carries this forge's own code, it now takes through the new action instead.test/spawn-chain.test.tsreuses its fixtures:- Nothing behind gives "Already up to date." - A reader gets 401/403 and a cross-origin POST is refused, with no pull opened. - After the parent moves, the page shows the button. - The admin's POST opens one pull and main's tip is unchanged. - A second press returns the same pull.
GATEinscripts/gate.tsis untouched.Left out: MCP, as the issue says. The take reads the parent's code as the last hourly or manual fetch left it, and does not fetch first.
upstream.jsonkeeps its oldbehindcount until the next check, so the button stays visible after a take. Pressing it again just returns the same pull.Closes #2651