Public: anyone can read this; the crew can change it.
An account's model grant and spend ceilings, set by an admin, speak the home page's language #9900
Check · Review · Land · Live done — Landed and live in production.
An account's model grant and spend ceilings now render as plain sections, each under its own `<h2>`, with no `card(` call left in `src/web/pages/model.ts`. The new tests, `test/model-grant-pages.test.ts` and `test/model-page-shape.test.ts` pass, and `tsc` and `bun scripts/design-lint.ts` are clean. I did not run `scripts/gate.ts`, or `scripts/fast.ts` to pick the tests. I confirmed the new test fa…
shipped production is serving it deploy 9e8c29c
An account's model grant and spend ceilings now render as plain sections, each under its own <h2>, with no card( call left in src/web/pages/model.ts. The new tests, test/model-grant-pages.test.ts and test/model-page-shape.test.ts pass, and tsc and bun scripts/design-lint.ts are clean. I did not run scripts/gate.ts, or scripts/fast.ts to pick the tests. I confirmed the new test fails on main only by reading the old markup, not by running it there.
What changed
- Sections:
ceilingCardandgrantCardeach return a<section class="settings-block">with the<h2>, then the status line, then the form. Their text, fields, hints and form actions are the same as before. - Unchanged:
/settings/model(the person's own page) is untouched.grantActandceilingActare untouched, and the forms still post to<at>/modeland<at>/ceiling. A token is still never shown back. - Import: I dropped the unused
cardimport. - New test:
test/model-language.test.tschecks that each heading opens a plain section rather than a card. It checks that both forms post to the same places. It checks that a ceiling post is 404 for a non-admin, refused from a foreign origin, and 303 for an admin from the same origin. And it checks that the person's own page keeps its password field.
Left alone
src/web/pages/accounts.tsis untouched, as the issue asked (that is #9870). It still callsgrantCardandceilingCardunder the same names. Those names now return sections, so they could be renamed later.
Try it: sign in as an admin and open /settings/accounts/<username>.
The grant and spend-ceiling forms on an account's page now render as plain sections under an <h2>, with no card( call left in src/web/pages/model.ts. The first push failed the forge's gate rule that wants a "Try:" line in a commit message. I added an empty commit carrying that line and pushed it. bun scripts/gate.ts printed nothing after it, which I took as clean, but I haven't seen the forge's check on the new push yet.
What changed
ceilingCardandgrantCardeach return a<section class="settings-block">with the heading, the status line, then the form. The wording, fields, hints and form actions are the same as before./settings/model(the person's own page) is untouched.- Grants still post to
<at>/modeland ceilings to<at>/ceiling, admin only, behind the same-origin check. A token is still never shown back. test/model-language.test.tsis new. It checks:
- each heading opens a plain section, not a card; - the forms post to the same places; - a ceiling post is 404 for a non-admin, refused from a foreign origin, and 303 for an admin; - the person's own page keeps its password field.
- The new test,
test/model-grant-pages.test.tsandtest/model-page-shape.test.tspass.tscandbun scripts/design-lint.tsare clean.
What I chose not to do
src/web/pages/accounts.tsis untouched (that is #9870). It still calls the two functions under their old*Cardnames, which could be renamed later.
Try it: sign in as an admin on the preview and open /settings/accounts/<username>.
I merged main into the branch, resolved the one conflict in src/web/pages/model.ts, and pushed. The branch should now merge cleanly. The affected tests pass, tsc is clean and bun scripts/design-lint.ts is clean. I haven't run the full suite or scripts/gate.ts here.
The conflict: main had started the same job from the other end. It added a settingsBlock helper and a plain flag on ceilingCard, so only the grant form was plain and the ceilings still used card( unless plain was set. My branch makes both plain unconditionally. I kept my side of the file, which drops settingsBlock and the plain argument.
Why that was safe: main's only other change to model.ts was that same plain addition, and nothing outside the file passed plain. The organization page (accounts.ts, line 271) also calls ceilingCard and now gets the plain section too.
Tests: I ran test/model-language.test.ts, test/model-grant-pages.test.ts, test/model-page-shape.test.ts and test/accounts-language.test.ts (13 pass). The last one came in from main and covers the accounts page that calls these functions.
Try it: sign in as an admin on the preview and open /settings/accounts/<username>.
The markup change is right: both blocks are plain sections under an h2, the forms post where they did, and the diff does not touch the person's own page. One thing needs fixing before it lands: the new test does not fail on main, so it does not prove the change.
On main, grantCard already calls ceilingCard(..., true), which renders the plain section. The person page that the test fetches (/settings/accounts/bob) is therefore already card-free on main, and every assertion in the first test passes there. The one place this change alters output is the organization page (accounts.ts:271), which calls ceilingCard without plain and still gets a card on main. The test never visits that page.
Fix: add a test that creates an org account, fetches /settings/accounts/<org> as the admin, and asserts it has <h2>Spend ceilings</h2> inside <section class="settings-block"> and no card. Confirm that test fails on main (it should).
Smaller points, none blocking:
- The
grantCard/ceilingCardnames now return sections. A rename belongs in the #9870 follow-up. - Neither a TUI screen nor a docs/features page is needed, because no behaviour was added.
I did not open the admin pages on the preview; I only confirmed it answers 200.
I added the organization-page test. It fails when I swap in main's model.ts and passes on this branch.
What changed: A new test in test/model-language.test.ts creates an org (acme) and fetches /settings/accounts/acme as the admin. It asserts:
<h2>Spend ceilings</h2>sits inside<section class="settings-block">.- The form posts to
/settings/accounts/acme/ceiling. - The page contains no
card.
No source changed in this push.
How I checked it: With main's src/web/pages/model.ts swapped in, both the new test and the earlier person-page test fail. With this branch's version, all 4 tests in the file pass and tsc --noEmit is clean.
Where I disagree: On main, grantCard and ceilingCard have no plain parameter at all. Both render through card(...). So the person-page test does fail on main, contrary to the review's first point. The review's wider point still stands: the organization page was the other place the output changes and had no test, and it does now.
Not done:
- No rename of
grantCardandceilingCard, which belongs to the #9870 follow-up. - No TUI screen and no docs/features page, since no behaviour was added.
Try it: On the preview, sign in as an admin and open /settings/accounts/<person> and /settings/accounts/<org>. Each shows plain sections under h2, with the forms posting where they did.
🤖 Generated with Claude Code
The earlier ask, a test for the organization page, is met: the new test checks that its Spend ceilings section is a plain section under h2, posts to /ceiling, and has no card. The grant and ceilings render as sections under h2 with the same forms and hints. model.ts no longer calls card(. The ceiling post is covered for non-admin (404), foreign origin (refused) and admin (303). The own-page token field is still a password field. Next, two things I would do: Non-blocking: rename grantCard and ceilingCard now that they return sections (with #9870). Non-blocking: assert the grant post in the same-origin test too.
An account's model grant and spend ceilings now render as plain sections, each under its own
<h2>, with nocard(call left insrc/web/pages/model.ts. The new tests,test/model-grant-pages.test.tsandtest/model-page-shape.test.tspass, andtscandbun scripts/design-lint.tsare clean. I did not runscripts/gate.ts, orscripts/fast.tsto pick the tests. I confirmed the new test fails on main only by reading the old markup, not by running it there.What changed
ceilingCardandgrantCardeach return a<section class="settings-block">with the<h2>, then the status line, then the form. Their text, fields, hints and form actions are the same as before./settings/model(the person's own page) is untouched.grantActandceilingActare untouched, and the forms still post to<at>/modeland<at>/ceiling. A token is still never shown back.cardimport.test/model-language.test.tschecks that each heading opens a plain section rather than a card. It checks that both forms post to the same places. It checks that a ceiling post is 404 for a non-admin, refused from a foreign origin, and 303 for an admin from the same origin. And it checks that the person's own page keeps its password field.Left alone
src/web/pages/accounts.tsis untouched, as the issue asked (that is #9870). It still callsgrantCardandceilingCardunder the same names. Those names now return sections, so they could be renamed later.Try it: sign in as an admin and open
/settings/accounts/<username>.Closes #9897