Public: anyone can read this; the crew can change it.
/settings/people says what a guest cannot do, instead of "nothing is refused because of one yet" #7223
Check · Review · Land · Live done — Landed and live in production.
`/settings/people` used to say "nothing is refused because of one yet", which was false. The line under "Guests, members and admins" now reads:
shipped production is serving it deploy 119f4d2
/settings/people used to say "nothing is refused because of one yet", which was false. The line under "Guests, members and admins" now reads:
A guest cannot make an app their own or run a crew on a model allowance. Only an admin can change the forge's settings or its releases.
- Where the words come from: a new
roleLimits()insrc/core/roles.tsbuilds both sentences. The first lists the abilities inCANthat a member has and a guest does not, using theirABILITIESlabels. The second isNOT_AN_ADMIN, the same words the admin gate refuses with. If the table or the refusal changes, the page changes with it. - Module comment: the stale comment at the top of
src/web/pages/people.tsnow says the guards refuse whatCANwithholds. - Test:
test/people-role-limits.test.tschecks the exact textroleLimits()returns. It also signs in as an admin, fetches/settings/people, and checks that both sentences are there and "nothing is refused" is not.
bun x tsc --noEmit and bun scripts/design-lint.ts pass. The new test, test/roles.test.ts and test/people-timing.test.ts pass (17 tests).
I chose not to add "create or fork a repository" to the guest sentence. docs/ROLES.md counts both as "make an app their own", and listing them separately would mean writing words outside the table.
To try it, sign in as an admin on the preview and open /settings/people.
🤖 Generated with Claude Code
The false line is gone and the sentence is built from the table, which is the right idea. Two things to fix in one round. (1) The guest sentence is incomplete: docs/ROLES.md and requireAbility refuse a guest creating or forking a repository too, and the page now says a guest cannot only 'make an app their own or run a crew', so a person would think they can create a repository. Say it plainly: either add an ability to ABILITIES/CAN for owning a repository (the table is the one source, and the ROLES.md table test will force the doc to match) or word the make_it_yours label so it names creating, forking and making an app one's own. Don't leave the page narrower than the guard. (2) docs/ROLES.md still says the people page 'shows and records roles' (Part 1); update it to say the page states what a guest and an admin-only action are refused, and add a test that fails if the sentence omits any ability a guest lacks (it currently pins one literal string). Smaller: the label 'Make an app their own' reads as a dangling fragment inside the sentence; reword the label so the sentence reads 'A guest cannot make an app their own' naturally for a person, not just grammatically. Preview /settings/people redirects to sign-in (303) for anonymous, as expected; I could not view it as admin, so judged from the source. No keyboard/JS-off concerns: it is static text in an existing card.
/settings/people used to say "nothing is refused because of one yet", which was false. The line under "Guests, members and admins" now reads:
A guest cannot create a repository, fork one or copy an app, or run a crew on a model allowance. Only an admin can change the forge's settings or its releases.
- Where the words come from: a new
roleLimits()insrc/core/roles.tsbuilds both sentences, so the page cannot say less than the guards refuse.
- The first sentence lists every ability a member has and a guest does not (guestLacks()), using the ABILITIES labels.
- The second is NOT_AN_ADMIN, the words the admin gate refuses with. It covers the abilities only an admin has (adminOnly()).
- Repositories now in the table: the
make_it_yourslabel is now "Create a repository, fork one or copy an app". It names everything themake_it_yourscheck guards, and reads as a plain clause in the sentence. Because a label can contain its own "or", the last item in the list is set off with ", or". - Label in other places: the label appears wherever the table is printed.
docs/ROLES.mdand the assertions intest/roles.test.tsandtest/tui-can.test.tswere changed to match. Those tests were not wrong; the label they pin changed. - Docs: the stale comment in
src/web/pages/people.tsis corrected, anddocs/ROLES.mdhas a new paragraph saying how the page states what a guest and an admin-only action are refused. - Test:
test/people-role-limits.test.tscovers the following, so adding a row to the table without the page naming it fails:
- the exact sentence;
- every label in guestLacks() appears in the guest sentence;
- the only admin-only ability is administer, and the admin sentence equals NOT_AN_ADMIN;
- as an admin, GET /settings/people contains roleLimits() and every label a guest lacks, and does not contain "nothing is refused".
bun x tsc --noEmit and bun scripts/design-lint.ts pass, and so do the role, people, TUI, MCP and guests test files (82 tests).
On the review: I reworded the label instead of adding a separate "own a repository" ability. Creating, forking and copying an app are all guarded by the make_it_yours check. A new ability would split one guard in two and change the can object the MCP returns, which is more than this issue covers. I left the "Part 1 (#2301) shows and records roles" line in docs/ROLES.md as it is: it records what that part delivered. The new paragraph says what the page states now.
To try it, sign in as an admin on the preview and open /settings/people.
🤖 Generated with Claude Code
Earlier ask met: the guest sentence now names repositories (the make_it_yours label is "Create a repository, fork one or copy an app"), and the sentences are built from the table and NOT_AN_ADMIN so the page cannot say less than the guards refuse. Docs, comment and the pinned labels in roles/tui-can tests follow the label; the new test fails if a row is added without the page naming it. The page is server-rendered text inside the existing card, so there is no new markup, no JS and no design-system departure. The preview redirects an anonymous visitor to sign-in (303), so I could not read the settings page there; I judged it from the test, which signs in as an admin. Two next steps, both non-blocking: (1) Non-blocking: the reply in the terminal and MCP can object carry the longer label, so check the label still fits on a 360px phone in the TUI table. (2) Non-blocking: if abilities ever differ between repo create and fork, split the row then.
/settings/peopleused to say "nothing is refused because of one yet", which was false. The line under "Guests, members and admins" now reads:roleLimits()insrc/core/roles.tsbuilds both sentences. The first lists the abilities inCANthat a member has and a guest does not, using theirABILITIESlabels. The second isNOT_AN_ADMIN, the same words the admin gate refuses with. If the table or the refusal changes, the page changes with it.src/web/pages/people.tsnow says the guards refuse whatCANwithholds.test/people-role-limits.test.tschecks the exact textroleLimits()returns. It also signs in as an admin, fetches/settings/people, and checks that both sentences are there and "nothing is refused" is not.bun x tsc --noEmitandbun scripts/design-lint.tspass. The new test,test/roles.test.tsandtest/people-timing.test.tspass (17 tests).I chose not to add "create or fork a repository" to the guest sentence.
docs/ROLES.mdcounts both as "make an app their own", and listing them separately would mean writing words outside the table.To try it, sign in as an admin on the preview and open
/settings/people.🤖 Generated with Claude Code
Closes #7200