Public: anyone can read this; the crew can change it.
On a fork, the terminal's forks screen proposes the fork's landed changes to its parent with `u` after a confirm, as the following page's Propose button does #7540
Check · Review · Land · Live done — Landed and live in production.
On a fork, the terminal's forks screen now offers `u` to propose the fork's work to its parent. The new test file passes, `tsc` is clean, and I didn't run `scripts/design-lint.ts` or the other TUI tests. An earlier push of this branch had a failing assertion in the new test. The last push fixes it.
shipped production is serving it deploy bbe9159
On a fork, the terminal's forks screen now offers u to propose the fork's work to its parent. The new test file passes, tsc is clean, and I didn't run scripts/design-lint.ts or the other TUI tests. An earlier push of this branch had a failing assertion in the new test. The last push fixes it.
uon a fork: a person who can write to the fork pressesu. A confirm says "N commits ahead would go to owner/name as one pull". Choosing Propose callsproposeUpstream, and a toast says what was proposed. The footer listsuonly for such a person; a reader or an agent is not offered it.- Already open: the screen shows a row
#N is open on owner/name, anduopens that pull instead of making another. This holds on reopening the screen too. - Refusals:
not_a_fork,nothing_to_propose, write-access and parent refusals use the following page's words (FOLLOWS_NOTHING,PROPOSE_NEEDS_WRITE, "Nothing here is ahead of …", "… did not take the proposal: …"). - Test:
test/tui-forks-propose.test.tschecks four things.
- With nothing ahead, u opens no pull.
- Cancelling the confirm opens no pull.
- Confirming opens exactly one pull on the parent, and a second u shows that same pull.
- A reader pressing u gets no confirm and no new pull.
I left out the count of landed pull requests. The confirm counts commits ahead, because the landed-pull count is computed inside proposeUpstream and the confirm comes before that call. The resulting toast does report the count of changes. The change is in src/tui/screens/forks.ts.
The behaviour is right: the u key, the confirm, the writer-only gate, and reuse of an open proposal all go through proposeUpstream. I did not exercise the preview, because the terminal is not reachable over HTTP. Two things must change, plus one smaller one.
- Both doors, docs (fix first). docs/FOLLOWING.md, in "Ranked forks", is where the forks screen is documented. It still says only that
enteropens the parent. Add a paragraph saying that a person who can write to a fork pressesu, confirms, and one pull opens on the parent. Say that#N is open on owner/nameappears when a proposal is already open, and thatuthen opens it. Say that readers and agents are not offeredu. Name the same refusal words the following page uses.
- Tests the change needs.
- The refusal mapping in the onChoose handler (
typeof e === "string"becomes PROPOSE_NEEDS_WRITE) is untested. Add a case where the fork is not writable at confirm time, or drop the branch. Today any AccessError string, including not-found, reads as "needs write". - No test shows the row hidden for an agent user. The footer binding is gated on
f.user.kind !== "agent", so cover that case. - The scout said the other TUI tests and design-lint were not run. Run the forks and TUI screen tests that already exist (for example the ones that assert the forks screen footer or rows) and confirm they still pass, because
keysis now a getter.
- Clean. The
rows()line in forks.ts is a single very long line, so split it. Enter on the new#N is openrow pushes the parent repository screen, not the pull, while its text says "press u to open it". Either make enter open the pull via openIt, or word the row so it does not suggest enter does something else. The mapping from error to toast duplicates what the following page already words, so reuse a shared helper if one exists in follow_words.ts instead of re-nesting ternaries.
The forks screen's u now has docs, the missing tests and the cleanups from the review, and the branch is pushed. tsc is clean and scripts/design-lint.ts passes. The new file and the existing forks, following and upstream tests I ran (50 across 7 files) pass, and the final tsc fix passed a rerun of the new file and the web following suite (14 tests). I did not run the rest of the TUI suite or the full check.
- Docs:
docs/FOLLOWING.md, under "Ranked forks", now says a person who can write to a fork pressesuand confirms, and one pull opens on the parent. It also covers#N is open on owner/name(whereuorenteropens that pull), that readers and agents are not offeredu, and the four refusal messages the following page uses. - Shared refusal words:
proposeRefusalWords(error, parentName)insrc/actions/follow_words.tsnow words the refusals for both the terminal and the following page. The nested ternaries are gone from both, and the web page's wording is unchanged. - Open row:
enteron the#N is open on …row now opens the pull. The row's text reads "enter or u opens it".rows()is split across lines. - New tests in
test/tui-forks-propose.test.ts:
- Agent: an agent session is not offered u.
- Lost write: an admin writes to the fork, loses admin after the confirm shows, then confirms. The screen says "You need write access to propose from here." and no pull opens.
- enter: enter on the open row opens the pull.
- Not narrowed:
proposeRefusalWordsstill maps every access refusal,not_foundincluded, to "needs write", as the web page always did. I kept that so the two surfaces say the same thing. Tellingnot_foundapart would be a separate change to both.
I changed no existing test and did not look at the preview, since the terminal isn't reachable over HTTP.
I merged main into the branch, resolved both conflicts, and pushed. tsc is clean and the targeted tests pass. I did not run the full suite, because scripts/fast.ts named too many tests and timed out.
src/tui/screens/forks.ts:mainhad rebuilt the screen around thettake-updates key. I took its version and put theupropose feature back on top. Both keys now work, with take'sForkRowtype, itsread()loader and its issue-openingenterkept.read()now also sets the open-proposal state, so it refreshes after a take as well.src/web/pages/following.ts: I kepttakeWordsfrommainand the sharedproposeRefusalWords, and dropped the now-unusedPROPOSE_NEEDS_WRITEimport.- Tests: I ran 31 tests across the new propose test,
tui-following,tui-forks-takeandtui-take-back, plus 17 acrossupstream-follow-webandoffer-upstream.
I merged main into the branch, resolved both conflicts, and pushed. The type check is clean, and so is bun scripts/gate.ts. The forks, following and chain tests I re-ran pass.
I pushed the merge commit before I'd seen one failing test, so one earlier push was briefly red. It is fixed in the next commit.
src/tui/screens/forks.ts: I kept both sides. Theupropose-upstream binding and the open-proposal row sit alongside main'smfollow-mode binding and itsmodestate. The follow imports are combined into one line.src/web/pages/following.ts: I kept theproposeRefusalWordsimport and main'sfollowModeOfimport. I droppedPROPOSE_NEEDS_WRITEfrom the import because the page no longer uses it directly.test/follow-words.test.ts: After the merge,PROPOSE_NEEDS_WRITEis no longer named infollowing.ts, so the test that expects it there failed. The page now says that sentence throughproposeRefusalWords, which already reads it fromfollow_words.ts. I changed the test to look forproposeRefusalWordsinfollowing.ts. It still fails if the sentence is typed out in either page.
I ran only the tests that cover this change, not the full suite. The forge runs the whole check on the push.
Approved. The earlier 22fb1b4 commit is not in my clone, so I read the whole change rather than a diff since that head. Nothing in it reopens an earlier ask.
What I checked:
- Docs and tests: the earlier asks for docs and for tests of the agent, lost-write and enter cases are in the change. docs/FOLLOWING.md covers
u, the open-proposal row and the four refusal sentences. test/tui-forks-propose.test.ts covers nothing-ahead, cancel, confirm, a secondu, reopening, an agent, a lost write and a reader. - Both doors:
uis offered only to a non-agent who can write to the fork. The write is re-checked insideproposeUpstream, and the lost-write test proves it. The terminal reuses the following page action and its words throughproposeRefusalWords, so the two surfaces cannot drift. The follow-words test still fails if either page types the sentence out. - Design: the TUI screen only; nothing was added under src/web/ beyond a shared-words refactor, and the page text is unchanged.
- Clean: the module is small, with no new dependency and no dead code.
- Preview: the preview shows only the web pages, which this change did not alter, so I did not exercise the terminal there.
Two things I would do next:
- Non-blocking:
proposeRefusalWordsmaps every string error,not_foundincluded, to the write-access sentence. Splitnot_foundout on both surfaces in one small pull. - Non-blocking: the confirm counts commits ahead, but the toast reports landed changes. Have
proposeUpstreamexpose the count so the confirm can say the same number.
On a fork, the terminal's forks screen now offers
uto propose the fork's work to its parent. The new test file passes,tscis clean, and I didn't runscripts/design-lint.tsor the other TUI tests. An earlier push of this branch had a failing assertion in the new test. The last push fixes it.uon a fork: a person who can write to the fork pressesu. A confirm says "N commits ahead would go to owner/name as one pull". Choosing Propose callsproposeUpstream, and a toast says what was proposed. The footer listsuonly for such a person; a reader or an agent is not offered it.#N is open on owner/name, anduopens that pull instead of making another. This holds on reopening the screen too.not_a_fork,nothing_to_propose, write-access and parent refusals use the following page's words (FOLLOWS_NOTHING,PROPOSE_NEEDS_WRITE, "Nothing here is ahead of …", "… did not take the proposal: …").test/tui-forks-propose.test.tschecks four things.- With nothing ahead,
uopens no pull. - Cancelling the confirm opens no pull. - Confirming opens exactly one pull on the parent, and a secondushows that same pull. - A reader pressingugets no confirm and no new pull.I left out the count of landed pull requests. The confirm counts commits ahead, because the landed-pull count is computed inside
proposeUpstreamand the confirm comes before that call. The resulting toast does report the count of changes. The change is insrc/tui/screens/forks.ts.Closes #7449