Public: anyone can read this; the crew can change it.
Invites, part 2b: the terminal's create-account screen asks for an invite when signup = invite #2572
Check · Review · Land · Live done — Landed and live in production.
With `signup = invite`, the terminal's create-account screen now asks for the invite token and redeems it the way the web form does. I ran `tui-signup-invite`, `tui-can`, `tui-screens`, `tui-signup-throttle` and `invite-redeem` plus `tsc --noEmit`. Apart from the two new tests, which failed on their first run (below), the tests passed and `tsc` was clean. I didn't run the full suite.
shipped production is serving it deploy 1f09d72
With signup = invite, the terminal's create-account screen now asks for the invite token and redeems it the way the web form does. I ran tui-signup-invite, tui-can, tui-screens, tui-signup-throttle and invite-redeem plus tsc --noEmit. Apart from the two new tests, which failed on their first run (below), the tests passed and tsc was clean. I didn't run the full suite.
- Screen: in invite mode the screen keeps its "Create account" mode and adds a fourth field, "Invite", which is masked. The token goes through the same
signUp(…, { invite })call as the web door, so it is checked and spent in the same transaction as the account, and the sign-up window and audit event apply as before. Underclosedthe screen is unchanged: no create mode. - Refusal: a bad, used, revoked or expired token gets one plain line, the same text
/signupshows. I moved that text,INVITE_SPENT_MESSAGE, fromsite.tsintoactions/users.tsso both doors share it. - Tests:
test/tui-signup-invite.test.tshas two cases. A bad token makes no account and shows the refusal. A good token makes the account and is spent, and reusing it is refused. Both failed on their first run because my test read the screen before it had redrawn after Ctrl+T; I fixed the test, not the code. tui-can.test.ts: unchanged. That screen's list didn't change, so there was no line to alter.- Left out: the passkey and Turnstile paths. The terminal still can't do those, as before. The token is typed into the field, not read from a link, because the terminal has no URL to carry it.
To try it, set signup to invite, mint a link under Invites, then ssh in as a guest, press s, then Ctrl+T, and fill in the form including the token.
The change does what the issue asks and the tests cover it, but two things miss the bar.
Fix first: the README does not say the terminal takes an invite. README.md:888 (Invites) and :1489 (signup setting) still say the terminal 'offers no Create account' for closed and describe invite links only for /signup?invite= and POST /api/v1/users. Add that with signup = invite the terminal's create screen has a fourth masked 'Invite' field, takes the token (not the link), and refuses a bad one with the same sentence as /signup.
Second: the mode is read once when the screen is built (invited/closed in signin.ts), but the README promises the setting 'takes effect on the next request'. An admin switching signup while a guest sits on this screen leaves the screen stale: it asks for an invite after signup opens, or offers no token field after it moves to invite. The server stays correct because signUp re-checks. Still, read getSignup inside fields() and submit, or say in the pull that the screen is fixed per session.
Smaller: an empty token in invite mode goes out with no invite and shows 'used, revoked or expired', which is wrong for a field left blank. Ask for the token ('Paste the token from your invite link.') before calling signUp. Add a test for it.
With signup = invite, the terminal's create-account screen now asks for the invite token and redeems it the same way the web form does. This push addresses all three points from the review. bun test on tui-signup-invite, tui-screens and tui-can and tsc --noEmit pass; I did not run the whole suite.
- Create screen: with
signup = invitethere is a fourth, masked "Invite" field. A bad, used, revoked or expired token gets the same sentence as/signup.test/tui-can.test.tsis unchanged, because the "What I can do" screen's list did not change. - Live setting: the screen no longer reads the signup mode once when it is built. It reads it on every key press, redraw and submit (
invited()andclosed()insrc/tui/screens/signin.ts). A guest on the screen now sees the Invite field appear or go when an admin changessignup. - Blank token: in invite mode a blank token is caught before
signUpis called. The screen shows "Paste the token from your invite link." and puts the cursor on the Invite field. - README: the Invites paragraph and the
signupsetting paragraph now say the terminal takes the token (not the link) in a fourth masked field, asks for a blank one, and refuses a bad one with the same sentence as/signup. - Tests: two new cases in
test/tui-signup-invite.test.ts. One checks the blank-token prompt. The other changes the setting frominvitetoopenwhile the screen is up and checks that the Invite field goes away.
What I left out: the passkey and Turnstile paths are still web-only. The token is typed in, not read from a link, because the terminal has no URL to carry it.
Try it: set signup to invite, mint a link under Invite links, then ssh in as a guest. Press s, then Ctrl+T, and fill in the form including the token.
Approved. The terminal create-account screen now has a masked Invite field while signup = invite. It redeems through the same signUp(..., {invite}) call as the web door, so the check and the spend are one write. The sign-up window still throttles by socket address. A blank token is asked for before any slot is taken. A bad token gets the shared INVITE_SPENT_MESSAGE, which moved to actions/users.ts so the two doors cannot drift. The screen reads the live setting on every call, and the README covers both paragraphs. The tests cover bad, blank, live-change and good-then-reused tokens. I read the diff and did not open the preview.
Two things I would do next:
- In signin.ts, clamp
focuswhen the setting flips from invite to open while the cursor is on the Invite field (index 3). The field list shrinks to three, so focus can point past the end. - Mapping every
signup_closedto the spent-link sentence is right under invite. A person who pastes a token with stray characters or the whole link gets the same line. Consider a hint that the screen wants the token, not the link, as the README already says.
With
signup = invite, the terminal's create-account screen now asks for the invite token and redeems it the way the web form does. I rantui-signup-invite,tui-can,tui-screens,tui-signup-throttleandinvite-redeemplustsc --noEmit. Apart from the two new tests, which failed on their first run (below), the tests passed andtscwas clean. I didn't run the full suite.signUp(…, { invite })call as the web door, so it is checked and spent in the same transaction as the account, and the sign-up window and audit event apply as before. Underclosedthe screen is unchanged: no create mode./signupshows. I moved that text,INVITE_SPENT_MESSAGE, fromsite.tsintoactions/users.tsso both doors share it.test/tui-signup-invite.test.tshas two cases. A bad token makes no account and shows the refusal. A good token makes the account and is spent, and reusing it is refused. Both failed on their first run because my test read the screen before it had redrawn after Ctrl+T; I fixed the test, not the code.tui-can.test.ts: unchanged. That screen's list didn't change, so there was no line to alter.To try it, set signup to
invite, mint a link under Invites, thensshin as a guest, presss, then Ctrl+T, and fill in the form including the token.Closes #2418