From 06b9e2b97b7f82155a4c70fc13179e84c34aca95 Mon Sep 17 00:00:00 2001 From: John Wang Date: Mon, 26 Oct 2026 04:14:00 -0700 Subject: [PATCH 1/3] docs(guides): add a guide to adding the invite pages to an application Covers where to serve the invite API, styles, routes, which menu links to show to whom, the waitlist link, and testing. The package page links to it. Refs: RMI-SYSTEMFORGEWEB-030 Co-Authored-By: Claude Opus 5.5 --- docs/guides/invite-gated-signup.md | 115 +++++++++++++++++++++++++++++ docs/packages/invites.md | 2 + mkdocs.yml | 1 + 3 files changed, 118 insertions(+) create mode 100644 docs/guides/invite-gated-signup.md 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/mkdocs.yml b/mkdocs.yml index 61d0082..6383576 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 From 0a3bc638913b527c6dd4607d1cbb115578a0f323 Mon Sep 17 00:00:00 2001 From: John Wang Date: Mon, 26 Oct 2026 04:14:01 -0700 Subject: [PATCH 2/3] docs(readme): add invite-gated sign-up Refs: RMI-SYSTEMFORGEWEB-030 Co-Authored-By: Claude Opus 5.5 --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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: From b1c79b8e2b82fea70e1bf25c82a3a8054dedea44 Mon Sep 17 00:00:00 2001 From: John Wang Date: Mon, 26 Oct 2026 04:14:02 -0700 Subject: [PATCH 3/3] docs(release): add v0.5.0 release notes and changelog Refs: RMI-SYSTEMFORGEWEB-030 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.json | 72 +++++++++++++++++++++++++++-------------- CHANGELOG.md | 22 +++++++++---- docs/changelog.md | 22 +++++++++---- docs/releases/v0.5.0.md | 51 +++++++++++++++++++++++++++++ mkdocs.yml | 1 + 5 files changed, 132 insertions(+), 36 deletions(-) create mode 100644 docs/releases/v0.5.0.md 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/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/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 6383576..9acfc26 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -87,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