Public: anyone can read this; the crew can change it.

A used sign-up link says who joined with it and when, on the terminal's Sign-ups screen and Settings › Limits #8172

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A used sign-up link now says who joined with it and when: `used by <name> on <yyyy-mm-dd>`. It shows on the terminal's Sign-ups screen and in Settings › Limits' Invite links list, where the name links to the person's profile.

shipped production is serving it deploy 0583edb

opened this

A used sign-up link now says who joined with it and when: used by <name> on <yyyy-mm-dd>. It shows on the terminal's Sign-ups screen and in Settings › Limits' Invite links list, where the name links to the person's profile.

  • Core (src/core/invites.ts): Invite now has usedAt and usedBy. Every invite read gets the name from the same query, by joining the account on used_by. usedBy is null when the link is unused or the account is gone; in that case the row reads used on <date> with no name. Nothing else changes shape.
  • Terminal (src/tui/screens/invites.ts): a used row's second line is built by a small usedWords helper.
  • Page (src/web/pages/admin-limits.ts): the name is escaped by the html template, and the link to /<username> follows the existing pattern in ui.ts.
  • Who can read it: no new reader. Everything goes through actions/invites.ts list() and its admin() gate. The MCP invites tool and GET /api/v1/admin/invites were not edited and are still admin-only. Because they pass the list straight through, both now return the two new fields as well. test/openapi-oa-acct.test.ts has no strict invite schema and still passes, so I did not remove the fields from the API view.

Tests (each fails without the change):

  • test/invites-core.test.ts: covers a redeemed invite with its user and date, one whose account is gone (name null), and an open one (both null). Its test database now also creates a minimal user table for the join.
  • test/tui-invites.test.ts: the Sign-ups screen shows used by bob on 2026-10-05.
  • test/admin-limits-timing.test.ts: /admin/limits renders used by <a href="/alice">alice</a> on 2026-10-03.

Checks run: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts all pass. bun test test/mcp-size.test.ts passes. scripts/fast.ts named 1456 test files, too many to run here, so I ran the 35 files that mention invites: 301 pass, 0 fail. During that run the sandbox blocked one outbound request to kai--forge.test:443.

To try it: as a forge admin, make a link, sign up with it, then open Settings › Limits, or the terminal's command palette › Invite links.

🤖 Generated with Claude Code

Closes #8080

Changes requested — before the latest push

Works as designed and is well tested (core, page, terminal). One miss against the list: the README says how to use the Invite links screen (README.md ~line 196) but does not mention that a used link now shows who joined and when, on the terminal and in Settings › Limits. Add one sentence there. Also consider: the web row renders the name as a link while the terminal row shows plain text, which is fine, but the web test only asserts the happy path; add a case for a used invite whose account is gone (renders used on DATE with no link) so the page branch is covered, as the core test already covers the data.

commented

A used sign-up link now says who joined with it and when: used by <name> on <yyyy-mm-dd>. It shows on the terminal's Invite links screen and in Settings › Limits, where the name links to the person's profile.

  • Core (src/core/invites.ts): Invite now has usedAt and usedBy. Every invite read gets the name from the same query, by joining the account on used_by. usedBy is null when the link is unused or the account is gone; in that case the row reads used on <date> with no name or link. Nothing else changes shape.
  • Terminal (src/tui/screens/invites.ts): a used row shows the words in plain text, built by a small usedWords helper.
  • Page (src/web/pages/admin-limits.ts): the name is escaped by the html template and links to /<username>, as ui.ts already does.
  • README: the Invite links paragraph now has one sentence saying what a used link shows, in the terminal and in Settings › Limits.
  • Who can read it: no new reader. Everything goes through actions/invites.ts list() and its admin() gate. The MCP invites tool and GET /api/v1/admin/invites were not edited and are still admin-only. Because they pass the list straight through, both now return the two new fields as well. test/openapi-oa-acct.test.ts has no strict invite schema and still passes, so I did not remove the fields from the API view.

Tests (each fails without the change):

  • test/invites-core.test.ts: covers a redeemed invite with its user and date, one whose account is gone (name null), and an open one (both null). Its test database now also creates a minimal user table for the join.
  • test/tui-invites.test.ts: the screen shows used by bob on 2026-10-05.
  • test/admin-limits-timing.test.ts: /admin/limits renders used by <a href="/alice">alice</a> on 2026-10-03. Since review it also covers an invite whose account is gone: the row reads used on 2026-10-04 with no name and no link.

Checks run: bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts and test/mcp-size.test.ts passed before the first push; tsc passed again before the second. scripts/fast.ts named 1456 test files, too many to run here, so before the first push I ran the 35 files that mention invites: 301 pass, 0 fail. During that run the sandbox blocked one outbound request to kai--forge.test:443. Since review I re-ran only test/admin-limits-timing.test.ts: 3 pass.

To try it: as a forge admin, make a link, sign up with it, then open Settings › Limits, or the terminal's command palette › Invite links.

🤖 Generated with Claude Code

Approved

Approved. The earlier ask, a test for a used invite whose account is gone, is met: the row reads used on <date> with no name or link. The join is done in one query. The name is escaped by the html template and linked to the profile. The terminal and the page show the same words, and the README says so. Both are gated by the existing admin list. The preview answers 200. I could not diff against 058dce2916e4 because the commit is not in the clone, so I read the full change instead.

Non-blocking: 1) The MCP and API invite views now also return usedAt and usedBy; say so in the openapi docs if they are listed there. 2) Show the used date in the page's <time> element, as Made and expires do, so it is formatted the same way.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs