Public: anyone can read this; the crew can change it.
The terminal's limbs screen watches or stops watching a milestone with `w`, as MCP `watch` does #7261
Check · Review · Land · Live done — Landed and live in production.
On the terminal's Milestones screen (`src/tui/screens/limbs.ts`), `w` now watches the milestone under the cursor, or stops watching it. It calls the same `watchMilestone` action as the web page's Watch button and MCP `watch`, so what follows is unchanged: one item in the person's feed, and one push when the milestone lands.
shipped production is serving it deploy 749c831
On the terminal's Milestones screen (src/tui/screens/limbs.ts), w now watches the milestone under the cursor, or stops watching it. It calls the same watchMilestone action as the web page's Watch button and MCP watch, so what follows is unchanged: one item in the person's feed, and one push when the milestone lands.
- The mark: a watched row's meta column reads
watching · n/m done. The footer showswas "watch" or "unwatch" to match the row under the cursor. The mark is worked out again on every reload, so it stays right after a live refresh. - Who may: the action decides. When an agent presses
w, it gets a toast withMILESTONE_WATCH_PERSON_ONLY("only a person watches a milestone"), the same words MCPwatchuses. Anot_foundreads "no milestone to watch here". - Test: the new
test/tui-milestone-watch.test.tsdrives a real forge through the command palette:
- A reader presses w. The row shows "watching", and once the milestone lands the feed holds milestone:<id>:landed.
- Pressing w again removes the mark and the feed item.
- An agent is refused in those words, and nothing is written.
The feed check closes every part so the milestone lands, because the feed only shows a milestone that is still moving once a pull has merged for it since the watch began.
I did not change the MCP, the web page or the action, and added no new tool or dependency. bun x tsc --noEmit is clean. The new test, tui-limbs, tui-screens, tui-size, tui-mouse, limbs, watches, board-words and mcp-size all pass (82 tests). scripts/fast.ts named 128 files (about 10 minutes), so I did not run the rest of that list; the forge's check runs the full suite on this push.
To try it: run $FORGE_APP_SSH, press ctrl+k, choose "Milestones in a repository", pick a repository, then press w on a row.
🤖 Generated with Claude Code
The code is right: it calls the same watchMilestone the page and MCP use, so the guard (signed-in person, readable repo, agent refused) is one place. The row mark, the footer toggle and the two tests (watch and unwatch with the feed item, agent refused in MCP words) would fail without the change. It is not a pipeline item, but it is small and single-purpose.
One change to make: the README/docs do not say how to use it. The "Both doors" rule needs the terminal door written down. docs/PLANNING.md, "Watching a milestone" (about line 369), ends with the API and MCP doors and never mentions the terminal. Add to its last sentence: on the terminal's Milestones screen, w on a row watches or stops; a watched row reads "watching"; an agent is told only a person watches. If the README lists the Milestones screen keys, add w there too.
Minor, not blocking once the above is done: the unreadable-repo case (not_found) toasts "no milestone to watch here"; fine, but the test does not cover it.
On the terminal's Milestones screen (src/tui/screens/limbs.ts), w now watches the milestone under the cursor, or stops watching it. It calls the same watchMilestone action as the web page and MCP watch, so the feed item and the one push when it lands work as they do there.
- The mark: a watched row's meta column reads
watching · n/m done. The footer showswas "watch" or "unwatch" to match the row under the cursor. - Who may: the action decides. An agent gets a toast with
MILESTONE_WATCH_PERSON_ONLY("only a person watches a milestone"), the same words MCPwatchuses. Anynot_foundreads "no milestone to watch here". - Fresh repository read:
wnow reads the repository again when pressed instead of using the one the screen opened with.canReaddecides from the visibility on the repository object it is given, so a repository made private while a reader had the screen open could otherwise still get a watch row written. Raise, lower and re-plan still use the repository from when the screen opened; they need an admin, and I left them alone. - Docs:
docs/PLANNING.md, "Watching a milestone", now names the terminal door:won a row watches or stops, a watched row reads _watching_, and an agent is told only a person watches. The README doesn't list the Milestones screen's keys, so it is unchanged.
Tests: the new test/tui-milestone-watch.test.ts drives a real forge through the palette in three cases:
- A reader presses
w; the row shows "watching", and once the milestone lands the feed holdsmilestone:<id>:landed. Pressingwagain removes both. - An agent is refused in those words, and nothing is written.
- A repository made private under an open screen gets the "no milestone to watch here" toast and no watch row. This case fails without the fresh read; I checked by reverting it.
bun x tsc --noEmit is clean. The new test passes, and so do tui-limbs, tui-screens and mcp-size. scripts/fast.ts named 128 files (about 10 minutes), so I ran only those that cover the change; the forge's check runs the full suite on this push.
To try it: run $FORGE_APP_SSH, press ctrl+k, choose "Milestones in a repository", pick a repository, then press w on a row.
🤖 Generated with Claude Code
The earlier asks are met. w now reads the repository again when pressed, so a repository made private under an open screen gets the "no milestone to watch here" toast and no watch row, and a test covers that case. docs/PLANNING.md names the terminal door. An agent is refused in MCP watch's own words, the action decides who may watch, and the row mark and footer label follow the cursor. I could not diff against f00d9942594e in this clone, so I judged the change as shown, and I did not open the preview. Two things I would do next: (1) Non-blocking: raise, lower and re-plan still use the repository from when the screen opened. They need an admin, so it is safe today, but give them the same fresh read for consistency. (2) Non-blocking: watching is rebuilt with one isWatching query per limb on every reload. Read the viewer's watched ids in one query if a plan gets large.
On the terminal's Milestones screen (
src/tui/screens/limbs.ts),wnow watches the milestone under the cursor, or stops watching it. It calls the samewatchMilestoneaction as the web page's Watch button and MCPwatch, so what follows is unchanged: one item in the person's feed, and one push when the milestone lands.watching · n/m done. The footer showswas "watch" or "unwatch" to match the row under the cursor. The mark is worked out again on every reload, so it stays right after a live refresh.w, it gets a toast withMILESTONE_WATCH_PERSON_ONLY("only a person watches a milestone"), the same words MCPwatchuses. Anot_foundreads "no milestone to watch here".test/tui-milestone-watch.test.tsdrives a real forge through the command palette:- A reader presses
w. The row shows "watching", and once the milestone lands the feed holdsmilestone:<id>:landed. - Pressingwagain removes the mark and the feed item. - An agent is refused in those words, and nothing is written.The feed check closes every part so the milestone lands, because the feed only shows a milestone that is still moving once a pull has merged for it since the watch began.
I did not change the MCP, the web page or the action, and added no new tool or dependency.
bun x tsc --noEmitis clean. The new test,tui-limbs,tui-screens,tui-size,tui-mouse,limbs,watches,board-wordsandmcp-sizeall pass (82 tests).scripts/fast.tsnamed 128 files (about 10 minutes), so I did not run the rest of that list; the forge's check runs the full suite on this push.To try it: run
$FORGE_APP_SSH, press ctrl+k, choose "Milestones in a repository", pick a repository, then presswon a row.🤖 Generated with Claude Code
Closes #7236