diff --git a/CHANGELOG.json b/CHANGELOG.json
index 2471975..fb88222 100644
--- a/CHANGELOG.json
+++ b/CHANGELOG.json
@@ -6,6 +6,53 @@
"commitConvention": "conventional",
"maintainers": ["grokify", "johncwang@gmail.com"],
"releases": [
+ {
+ "version": "v0.5.0",
+ "date": "2026-10-26",
+ "highlights": [
+ {
+ "description": "`@plexusone/invites` 0.1.0: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people",
+ "commit": "bd2c35a"
+ }
+ ],
+ "added": [
+ {
+ "description": "`createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`; requests carry the browser's cookies",
+ "commit": "597b1d1"
+ },
+ {
+ "description": "`InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke",
+ "commit": "bd2c35a"
+ },
+ {
+ "description": "`InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones",
+ "commit": "39ff502"
+ }
+ ],
+ "tests": [
+ {
+ "description": "Cover the invite API client and both pages",
+ "commit": "36d10c1"
+ },
+ {
+ "description": "Cover a new link surviving a failed reload, the copy note following the code, removed waitlist addresses leaving the selection, and requests sending cookies",
+ "commit": "6ceeb55"
+ }
+ ],
+ "documentation": [
+ {
+ "description": "`@plexusone/invites` package page",
+ "commit": "68d625f"
+ },
+ {
+ "description": "Doc comments on the package's components and helpers",
+ "commit": "1685338"
+ },
+ {
+ "description": "Adding invite pages guide, README section, and v0.5.0 release notes"
+ }
+ ]
+ },
{
"version": "v0.4.0",
"date": "2026-10-20",
@@ -483,28 +530,5 @@
}
]
}
- ],
- "unreleased": {
- "highlights": [
- {
- "description": "`@plexusone/invites`: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people"
- }
- ],
- "added": [
- {
- "description": "`createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`"
- },
- {
- "description": "`InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke"
- },
- {
- "description": "`InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones"
- }
- ],
- "documentation": [
- {
- "description": "`@plexusone/invites` package page"
- }
- ]
- }
+ ]
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4c439d5..67eea10 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,19 +9,28 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
## [Unreleased]
+## [v0.5.0] - 2026-10-26
+
### Highlights
-- `@plexusone/invites`: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people
+- `@plexusone/invites` 0.1.0: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people
### Added
-- `createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`
-- `InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke
-- `InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones
+- `createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`; requests carry the browser's cookies ([`597b1d1`](https://github.com/plexusone/systemforge-web/commit/597b1d1))
+- `InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke ([`bd2c35a`](https://github.com/plexusone/systemforge-web/commit/bd2c35a))
+- `InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones ([`39ff502`](https://github.com/plexusone/systemforge-web/commit/39ff502))
### Documentation
-- `@plexusone/invites` package page
+- `@plexusone/invites` package page ([`68d625f`](https://github.com/plexusone/systemforge-web/commit/68d625f))
+- Doc comments on the package's components and helpers ([`1685338`](https://github.com/plexusone/systemforge-web/commit/1685338))
+- Adding invite pages guide, README section, and v0.5.0 release notes
+
+### Tests
+
+- Cover the invite API client and both pages ([`36d10c1`](https://github.com/plexusone/systemforge-web/commit/36d10c1))
+- Cover a new link surviving a failed reload, the copy note following the code, removed waitlist addresses leaving the selection, and requests sending cookies ([`6ceeb55`](https://github.com/plexusone/systemforge-web/commit/6ceeb55))
## [v0.4.0] - 2026-10-20
@@ -246,7 +255,8 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
- Shared pnpm workflows from grokify/.github ([`bb07c1d`](https://github.com/plexusone/systemforge-web/commit/bb07c1d))
-[unreleased]: https://github.com/plexusone/systemforge-web/compare/v0.4.0...HEAD
+[unreleased]: https://github.com/plexusone/systemforge-web/compare/v0.5.0...HEAD
+[v0.5.0]: https://github.com/plexusone/systemforge-web/compare/v0.4.0...v0.5.0
[v0.4.0]: https://github.com/plexusone/systemforge-web/compare/v0.3.1...v0.4.0
[v0.3.1]: https://github.com/plexusone/systemforge-web/compare/v0.3.0...v0.3.1
[v0.3.0]: https://github.com/plexusone/systemforge-web/compare/v0.2.3...v0.3.0
diff --git a/README.md b/README.md
index f79bdbb..391b673 100644
--- a/README.md
+++ b/README.md
@@ -45,6 +45,23 @@ function App() {
}
```
+## Invite-gated sign-up
+
+`@plexusone/invites` adds the pages for launching invite-only with
+SystemForge: operators issue invite codes and email invites and work through
+the waitlist, and members invite people from their allowance.
+
+```tsx
+import { createInvitesApi, InviteAdminPage, InvitePeoplePage } from '@plexusone/invites';
+
+const invites = createInvitesApi('/invites/api');
+
+} />
+} />
+```
+
+See [Adding invite pages](docs/guides/invite-gated-signup.md).
+
## Telemetry with ProductGraph
Track user behavior, journeys, and performance with ProductGraph integration:
diff --git a/docs/changelog.md b/docs/changelog.md
index 4c439d5..67eea10 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -9,19 +9,28 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
## [Unreleased]
+## [v0.5.0] - 2026-10-26
+
### Highlights
-- `@plexusone/invites`: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people
+- `@plexusone/invites` 0.1.0: pages for SystemForge invite-gated sign-up, an operator page for invite codes, email invites, the waitlist and member allowances, and a member page to invite people
### Added
-- `createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`
-- `InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke
-- `InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones
+- `createInvitesApi`: a typed client for SystemForge's invite API, with snake_case wire types and `ApiError`; requests carry the browser's cookies ([`597b1d1`](https://github.com/plexusone/systemforge-web/commit/597b1d1))
+- `InviteAdminPage`: codes, email invites, waitlist, allowances and joined accounts, with new codes and links shown once and inline revoke ([`bd2c35a`](https://github.com/plexusone/systemforge-web/commit/bd2c35a))
+- `InvitePeoplePage`: a member's allowance, create and copy an invite link, and revoke unused ones ([`39ff502`](https://github.com/plexusone/systemforge-web/commit/39ff502))
### Documentation
-- `@plexusone/invites` package page
+- `@plexusone/invites` package page ([`68d625f`](https://github.com/plexusone/systemforge-web/commit/68d625f))
+- Doc comments on the package's components and helpers ([`1685338`](https://github.com/plexusone/systemforge-web/commit/1685338))
+- Adding invite pages guide, README section, and v0.5.0 release notes
+
+### Tests
+
+- Cover the invite API client and both pages ([`36d10c1`](https://github.com/plexusone/systemforge-web/commit/36d10c1))
+- Cover a new link surviving a failed reload, the copy note following the code, removed waitlist addresses leaving the selection, and requests sending cookies ([`6ceeb55`](https://github.com/plexusone/systemforge-web/commit/6ceeb55))
## [v0.4.0] - 2026-10-20
@@ -246,7 +255,8 @@ and this changelog is generated by [Structured Changelog](https://github.com/gro
- Shared pnpm workflows from grokify/.github ([`bb07c1d`](https://github.com/plexusone/systemforge-web/commit/bb07c1d))
-[unreleased]: https://github.com/plexusone/systemforge-web/compare/v0.4.0...HEAD
+[unreleased]: https://github.com/plexusone/systemforge-web/compare/v0.5.0...HEAD
+[v0.5.0]: https://github.com/plexusone/systemforge-web/compare/v0.4.0...v0.5.0
[v0.4.0]: https://github.com/plexusone/systemforge-web/compare/v0.3.1...v0.4.0
[v0.3.1]: https://github.com/plexusone/systemforge-web/compare/v0.3.0...v0.3.1
[v0.3.0]: https://github.com/plexusone/systemforge-web/compare/v0.2.3...v0.3.0
diff --git a/docs/guides/invite-gated-signup.md b/docs/guides/invite-gated-signup.md
new file mode 100644
index 0000000..6474fba
--- /dev/null
+++ b/docs/guides/invite-gated-signup.md
@@ -0,0 +1,115 @@
+# Adding invite pages
+
+An application that launches invite-only, with SystemForge's invite-gated
+sign-up, needs two pages: one for operators to issue invites and work through
+the waitlist, and one for members to invite people from their allowance.
+`@plexusone/invites` provides both. This guide wires them into an
+application; SystemForge's
+[invite-gated sign-up guide](https://plexusone.github.io/systemforge/guides/invite-gated-signup/)
+covers turning it on in SystemAuth and what an invited person sees.
+
+## 1. Choose where the invite API runs
+
+The pages call SystemForge's invite API with the browser's cookies, so the
+API must accept the session of the person using the page. There are two
+places to serve it:
+
+| Where | Base URL | Use when |
+| ---------------------------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
+| **Behind the application's own session** | A path on the application, such as `/invites/api` | The application embeds SystemAuth and has its own sign-in. The pages stay same-origin, so cookies just work |
+| **On the SystemAuth host** | `https://auth.example.com/signup-invites/api` | The person is signed in to SystemAuth itself (`Options.SignupAPI`) |
+
+Serving the API behind the application's session is the usual choice. In
+the host binary, `Service.SignupAPI` takes a function that names the person
+signed in to the application:
+
+```go
+api, err := svc.SignupAPI(func(r *http.Request) (uuid.UUID, error) {
+ return mySession.PrincipalID(r)
+})
+mux.Handle("/invites/api/", http.StripPrefix("/invites/api", api))
+```
+
+On the SystemAuth host, the page and the API are on different origins: the
+API must allow credentialed requests from the application's origin, and the
+SystemAuth session cookie must reach it.
+
+## 2. Install and add the styles
+
+```bash
+pnpm add @plexusone/invites @plexusone/ui
+```
+
+The pages use Tailwind classes with `@plexusone/ui`'s theme names. Set up
+Tailwind as in [@plexusone/ui](../packages/ui.md), then add the package's
+built files to Tailwind's sources so their classes are generated:
+
+```css
+@source "../node_modules/@plexusone/invites/dist";
+```
+
+## 3. Route the pages
+
+Create one client and route both pages where they belong in the
+application. They are plain components, not an `AppModule`, because invites
+are platform-wide rather than organization-scoped.
+
+```tsx
+import { createInvitesApi, InviteAdminPage, InvitePeoplePage } from '@plexusone/invites';
+
+const invites = createInvitesApi('/invites/api');
+
+} />
+} />
+```
+
+`InviteAdminPage` is for platform administrators. Anyone else who reaches it
+sees the API's refusal, so the route needs no guard of its own, but the menu
+should not offer it to them.
+
+## 4. Show the menu links to the right people
+
+`settings()` says whether the person is an administrator, and `myInvites()`
+gives their allowance. Offer **Invites** to administrators, and **Invite
+people** to members who have an allowance:
+
+```tsx
+const [links, setLinks] = useState<{ admin: boolean; invite: boolean }>();
+
+useEffect(() => {
+ void Promise.all([invites.settings(), invites.myInvites()]).then(([s, mine]) =>
+ setLinks({ admin: s.is_admin, invite: mine.allowance > 0 })
+ );
+}, []);
+```
+
+A member with no allowance can still open `/invite`; the page tells them they
+have no invites to give yet.
+
+## 5. Point the sign-in page at the waitlist
+
+A refused sign-up is not told why. Link the application's sign-in page to
+SystemAuth's waitlist, `https://auth.example.com/signup/waitlist`, so a
+person without an invite has somewhere to go, and link it from any public
+page that announces the launch.
+
+## What the pages do
+
+- **Invite people** shows how many people the member can still invite,
+ creates an invite link and shows it once with a copy button, and revokes a
+ link nobody has used, which gives the slot back.
+- **Invites** has a section each for invite codes, email invites, the
+ waitlist, member allowances, and the accounts that joined. New codes and
+ links are shown once, because only a hash is stored; destructive actions
+ are confirmed inline.
+
+See [@plexusone/invites](../packages/invites.md) for each page's sections
+and props, and the API client's methods.
+
+## Testing
+
+The pages fetch with the global `fetch`, so a test can stub it and answer
+each route with JSON, as the package's own tests do. Answer `GET /settings`
+and `GET /me/invites` for the member page, and the `/admin/...` routes for
+the operator page. Use `vi.stubGlobal('fetch', ...)` in Vitest, and restore
+it after each test.
diff --git a/docs/packages/invites.md b/docs/packages/invites.md
index 184edd6..e219a8b 100644
--- a/docs/packages/invites.md
+++ b/docs/packages/invites.md
@@ -4,6 +4,8 @@ Pages for SystemForge's invite-gated sign-up: an operator page to manage
invite codes, email invites, the waitlist and member allowances, and a member
page to invite people. Both call SystemForge's invite API (`signupapi`); see
SystemForge's [invite-gated sign-up guide](https://plexusone.github.io/systemforge/guides/invite-gated-signup/).
+[Adding invite pages](../guides/invite-gated-signup.md) walks through wiring
+them into an application.
```bash
pnpm add @plexusone/invites @plexusone/ui
diff --git a/docs/releases/v0.5.0.md b/docs/releases/v0.5.0.md
new file mode 100644
index 0000000..7bce7be
--- /dev/null
+++ b/docs/releases/v0.5.0.md
@@ -0,0 +1,51 @@
+# v0.5.0 Release Notes
+
+**Release Date:** 2026-10-26
+
+Adds `@plexusone/invites` at 0.1.0. The other packages are unchanged.
+
+## Highlights
+
+- **Pages for launching invite-only.** `@plexusone/invites` gives an
+ application the two pages SystemForge's invite-gated sign-up needs.
+ `InviteAdminPage` lets platform administrators create invite codes with a
+ use limit and expiry, invite people by email address, invite people from
+ the waitlist, set how many people each member may invite, and see who
+ joined with which invite. `InvitePeoplePage` lets a member create invite
+ links from their allowance and revoke ones nobody has used. See
+ [@plexusone/invites](../packages/invites.md).
+- **Shown once.** SystemForge stores only a hash of each code, so a new code
+ or link is shown once, with a copy button, and the page says so.
+- **A typed client.** `createInvitesApi` covers every route of SystemForge's
+ invite API, with snake_case wire types as the API sends them and
+ `ApiError` carrying the HTTP status and the API's message.
+- **A guide.** [Adding invite pages](../guides/invite-gated-signup.md) covers
+ where to serve the invite API, styles, routes, which menu links to show to
+ whom, and testing.
+
+## Requirements
+
+- `@plexusone/invites` needs `@plexusone/ui` and React 19.
+- systemforge v0.22.0 or later serves the invite API (`signupapi`), behind
+ the application's own session with `Service.SignupAPI` or on the SystemAuth
+ host with `Options.SignupAPI`.
+
+## Installation
+
+```bash
+pnpm add @plexusone/invites @plexusone/ui
+```
+
+Add the package's built files to Tailwind's sources:
+
+```css
+@source "../node_modules/@plexusone/invites/dist";
+```
+
+## Upgrading
+
+Nothing to change: no existing package moved.
+
+## Not in this release
+
+- Nothing is emailed. Operators and members share invite links themselves.
diff --git a/mkdocs.yml b/mkdocs.yml
index 61d0082..9acfc26 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -50,6 +50,7 @@ nav:
- Quick Start: getting-started/quickstart.md
- Guides:
- Writing an Application: guides/writing-an-application.md
+ - Adding Invite Pages: guides/invite-gated-signup.md
- Releasing: guides/releasing.md
- Packages:
- Overview: packages/index.md
@@ -86,6 +87,7 @@ nav:
- Tasks: specs/initiatives/invention/productgraph/TASKS.md
- Releases:
- Changelog: changelog.md
+ - v0.5.0: releases/v0.5.0.md
- v0.4.0: releases/v0.4.0.md
- v0.3.1: releases/v0.3.1.md
- v0.3.0: releases/v0.3.0.md