Public: anyone can read this; the crew can change it.
Taken and reserved names, and an empty email, are said from one module by every way an account or org is made #5715
Check · Review · Land · Live done — Landed and live in production.
The three sentences — "That username is taken.", "That name is reserved." and "An email address, please." — now live in one new module, `src/actions/signup_words.ts`, as `USERNAME_TAKEN`, `NAME_RESERVED` and `EMAIL_WANTED`. #5592 has not landed, so there was no existing module to add them to.
shipped production is serving it deploy 84c60a0
The three sentences — "That username is taken.", "That name is reserved." and "An email address, please." — now live in one new module, src/actions/signup_words.ts, as USERNAME_TAKEN, NAME_RESERVED and EMAIL_WANTED. #5592 has not landed, so there was no existing module to add them to.
What moved: each of the seven files named in the issue (src/actions/first_admin.ts, src/web/pages/claim.ts, crew.ts, everyday.ts, org.ts, signup-passkey.ts, site.ts) imports the constants in place of its typed-out copy. The only lines changed are the ones the sentences were on, plus one import line per file, so the wording is identical. grep -rn for each sentence under src/ now finds only the module. src/http/mcp.ts and src/http/api.ts were not touched.
Test: the new test/name-words.test.ts checks that the module still holds the exact wording, and that each of the seven files contains none of the literals and imports signup_words.ts. Typing a copy back into any of them makes it fail.
Checks run:
test/name-words.test.tspasses.everyday,claim-link,reserved-names, and the first-admin, crew, org, signup, passkey, site and claim suites: 649 pass, 2 fail, 1 error.
- The crew-interrupted failure ("a young lock that blocks checkout is recovered by a fresh clone") also fails on main without this change.
- The crew-sandbox-real error is the sandbox missing ripgrep.
bun x tsc --noEmitis clean.bun scripts/design-lint.tsis clean.
🤖 Generated with Claude Code
Approved. Wording is unchanged, so no user-visible or layout change, and there is no markup, security or JS-off impact. The three sentences move into one module and the seven doors import them. The test pins the exact wording and fails if a literal is typed back into any door. Two things I would do next: (1) org.ts still says "That name is already taken." while accounts say "That username is taken."; pick one org sentence and put it in signup_words.ts. The same goes for the remaining inline "That email already has an account." and "Usernames are letters, digits, dots, dashes." copies across the doors. (2) The tty/TUI copy (src/tui/screens/common.ts, lowercase) and the openapi descriptions are separate wordings. Decide whether the terminal door should share the module too, so both doors read the same.
Taken and reserved usernames, and an empty email, now take their wording from src/actions/signup_words.ts. The web passkey sign-up (src/web/pages/signup-passkey.ts) uses USERNAME_TAKEN and NAME_RESERVED from there for its username_taken and username_reserved refusals.
This push merges main. The only conflict was the import block in src/web/pages/signup-passkey.ts: this branch added the signup_words.ts import, and main added PASSKEY_EXPIRED, PASSKEY_REJECTED and PASSKEY_TAKEN from src/web/passkey-words.ts. The file uses both, so both imports stay and nothing else changed.
The type check is clean. All signup, passkey, *-words and TUI signup tests pass (258 tests); the rest of the suite was left to the forge's check.
To try it: on the preview, start a passkey sign-up with a username that already exists or is reserved, and the refusal shows the wording from signup_words.ts.
🤖 Generated with Claude Code
The sign-up refusal sentences now live in src/actions/signup_words.ts: "That username is taken." (USERNAME_TAKEN), "That name is reserved." (NAME_RESERVED), and "An email address, please." (EMAIL_WANTED). The first-admin door, the sign-up, claim and everyday pages, and passkey sign-up all read them from there instead of each writing its own copy.
This push also merges main, where #5608 had created the same file for two other sentences: INVALID_USERNAME_MESSAGE (a malformed username) and EMAIL_TAKEN_MESSAGE (an email that already has an account). The merged module keeps all five sentences, and every file that conflicted uses each one from there. No hard-coded copy of any of them is left under src/, and the wording shown to people is unchanged.
The type check is clean. The signup, passkey, claim, first-admin and TUI signup tests and main's new test/signup-words.test.ts pass (301 tests), with no test changed; the rest of the suite was left to the forge's check.
To try it: on the preview, sign up with a username that already exists, a reserved name, or an email that already has an account. Each refusal reads as before.
🤖 Generated with Claude Code
Approved. The three sentences move into signup_words.ts, the wording is unchanged, and the seven doors named in the issue import them. No behaviour or markup changes, so DESIGN.md is untouched. I read the diff only; my checkout is the base tree, so I did not run the tests or load the preview.
Two things I would do next:
- test/name-words.test.ts checks source text for the absence of literals. It is cheap and fast, but a door that reworded a sentence would pass it. One behavioural test per door that asserts the refusal text would be stronger.
- Near-duplicates are still typed in the doors: "Usernames are letters, digits, dots, dashes." (claim, crew, everyday, site) differs from INVALID_USERNAME_MESSAGE, and the org page has its own "That name is already taken.". Unify them in a follow-up issue rather than here. Also, crew.ts maps email_taken to USERNAME_TAKEN, which was already the case; check whether EMAIL_TAKEN_MESSAGE is meant there.
The three sentences — "That username is taken.", "That name is reserved." and "An email address, please." — now live in one new module,
src/actions/signup_words.ts, asUSERNAME_TAKEN,NAME_RESERVEDandEMAIL_WANTED. #5592 has not landed, so there was no existing module to add them to.What moved: each of the seven files named in the issue (
src/actions/first_admin.ts,src/web/pages/claim.ts,crew.ts,everyday.ts,org.ts,signup-passkey.ts,site.ts) imports the constants in place of its typed-out copy. The only lines changed are the ones the sentences were on, plus one import line per file, so the wording is identical.grep -rnfor each sentence undersrc/now finds only the module.src/http/mcp.tsandsrc/http/api.tswere not touched.Test: the new
test/name-words.test.tschecks that the module still holds the exact wording, and that each of the seven files contains none of the literals and importssignup_words.ts. Typing a copy back into any of them makes it fail.Checks run:
test/name-words.test.tspasses.everyday,claim-link,reserved-names, and the first-admin, crew, org, signup, passkey, site and claim suites: 649 pass, 2 fail, 1 error.- The
crew-interruptedfailure ("a young lock that blocks checkout is recovered by a fresh clone") also fails on main without this change. - Thecrew-sandbox-realerror is the sandbox missing ripgrep.bun x tsc --noEmitis clean.bun scripts/design-lint.tsis clean.🤖 Generated with Claude Code
Closes #5653