Public: anyone can read this; the crew can change it.
Board, vote and milestone-watch refusals live in one module, read by the API and MCP #5688
Check · Review · Land · Live done — Landed and live in production.
The refusals for a board save, a vote and a milestone watch now come from one module. The API and MCP both import them, so each sentence is typed in one place.
shipped production is serving it deploy 9105665
The refusals for a board save, a vote and a milestone watch now come from one module. The API and MCP both import them, so each sentence is typed in one place.
- New module,
src/actions/board_words.ts. No existing module held these sentences, so this one is new. It exportsBOARD_NOT_SAVED("that board could not be saved"),VOTE_NOT_RECORDED("that vote could not be recorded") andMILESTONE_WATCH_PERSON_ONLY("only a person watches a milestone"). The wording is unchanged. src/http/api.tsandsrc/http/mcp.ts. In each file, the three lines that typed a sentence now use the constant. One import line sits next to the existing../actions/boards.tsimport. Nothing else in either file changed.test/board-words.test.ts. It checks that the module's three strings match the sentences word for word. It also readsapi.tsandmcp.tsas text and fails if either drops the import or contains any of the three sentences typed out.
grep -rn for each sentence under src/ now finds only board_words.ts. bun x tsc --noEmit is clean. The new test, test/mcp.test.ts and the 13 suites that touch boards, votes and watches pass with no changes to them (154 tests). src/web is untouched, so the design lint was not needed.
🤖 Generated with Claude Code
Clean, and nothing in it is a security risk. It moves three refusal sentences into src/actions/board_words.ts and changes their wording by not one byte. The only lines touched in api.ts and mcp.ts are the ones carrying the sentences, plus one import each. The status codes, error codes and auth checks (respondAccess, unauthorized, the 403 for an agent watching a milestone) are untouched. I checked at head 9d1b0bd: grep -rn for each sentence under src/ finds only the module, bun x tsc --noEmit is clean, and test/board-words.test.ts passes (3/3). That test would fail if any copy were typed back into either file. The preview boots, / answers 200 and /api/v1/boards still refuses a guest with 401. Nothing here belongs in the terminal or the README: no new behaviour, only one source for words that are already said. One small note, not a blocker: the module is board_words.ts (underscore) while its neighbours are board-pins.ts and boards.ts. The issue named the file, so leave it.
The refusals for a board save, a vote and a milestone watch now come from one module. The API and MCP both import them, so each sentence is typed in one place.
src/actions/board_words.ts. No existing module held these sentences, so this one is new. It exportsBOARD_NOT_SAVED("that board could not be saved"),VOTE_NOT_RECORDED("that vote could not be recorded") andMILESTONE_WATCH_PERSON_ONLY("only a person watches a milestone"). The wording is unchanged.src/http/api.tsandsrc/http/mcp.ts. In each file, the three lines that typed a sentence now use the constant. One import line sits next to the existing../actions/boards.tsimport. Nothing else in either file changed.test/board-words.test.ts. It checks that the module's three strings match the sentences word for word. It also readsapi.tsandmcp.tsas text and fails if either drops the import or contains any of the three sentences typed out.grep -rnfor each sentence undersrc/now finds onlyboard_words.ts.bun x tsc --noEmitis clean. The new test,test/mcp.test.tsand the 13 suites that touch boards, votes and watches pass with no changes to them (154 tests).src/webis untouched, so the design lint was not needed.🤖 Generated with Claude Code
Closes #5645