Public: anyone can read this; the crew can change it.
A new forge's admin is asked for an everyday account in one plain block on home, not a boxed card #10002
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/9957-a-new-forge-s-admin-is-asked-for-an-ever → main 2 commits fork 1 thread · 1 unresolved
2 files
changed +68
−2
src/web/pages/everyday.ts +6 −2
| @@ -43,10 +43,14 @@ function suggestedName(ctx: Ctx, admin: User): string { | ||
| 43 | 43 | return getUserByName(ctx.app.db, name) === undefined ? name : ""; |
| 44 | 44 | } |
| 45 | 45 | |
| 46 | /** The card, for an admin with no everyday account and no recorded choice. */ | |
| 46 | /** The home section, for an admin with no everyday account and no recorded choice. */ | |
| 47 | 47 | export function everydayCard(ctx: Ctx, user: User): Html | null { |
| 48 | 48 | if (!askForEveryday(ctx.app.db, user)) return null; |
| 49 | return html`<div class="mb">${everydayForm(ctx, user, true)}</div>`; | |
| 49 | return html`<section class="settings-block" id="everyday"> | |
| 50 | <h2>Make your everyday account</h2> | |
| 51 | <p>${EVERYDAY_WHY}</p> | |
| 52 | ${everydayFields(ctx, user, true)} | |
| 53 | </section>`; | |
| 50 | 54 | } |
| 51 | 55 | |
| 52 | 56 | /** |
test/everyday-language.test.ts added +62
| @@ -0,0 +1,62 @@ | ||
| 1 | /** The everyday-account ask on home speaks the home page's language: a block, not a card (#9957). */ | |
| 2 | import { afterAll, beforeAll, expect, test } from "bun:test"; | |
| 3 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 4 | import { tmpdir } from "node:os"; | |
| 5 | import { join } from "node:path"; | |
| 6 | import { createApp, type App } from "../src/app.ts"; | |
| 7 | import { getUserByName } from "../src/core/users.ts"; | |
| 8 | import { createSession } from "../src/core/sessions.ts"; | |
| 9 | import { serve, type Servers } from "../src/http/server.ts"; | |
| 10 | import { EVERYDAY_WHY } from "../src/core/everyday.ts"; | |
| 11 | import { seedAdmin } from "./helpers/admin.ts"; | |
| 12 | ||
| 13 | let dataDir = ""; | |
| 14 | let app: App, servers: Servers; | |
| 15 | let admin = ""; | |
| 16 | ||
| 17 | beforeAll(async () => { | |
| 18 | dataDir = await mkdtemp(join(tmpdir(), "forge-everyday-lang-")); | |
| 19 | app = createApp({ dataDir, port: 0 }); | |
| 20 | servers = await serve(app); | |
| 21 | const a = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" }); | |
| 22 | if (!a.ok) throw new Error(a.error); | |
| 23 | admin = `forge_session=${createSession(app.db, getUserByName(app.db, "alice")!.id).token}`; | |
| 24 | }); | |
| 25 | afterAll(async () => { | |
| 26 | servers.stop(); | |
| 27 | app.close(); | |
| 28 | await rm(dataDir, { recursive: true, force: true }); | |
| 29 | }); | |
| 30 | ||
| 31 | const main = (page: string) => page.slice(page.indexOf("<main"), page.indexOf("</main>")); | |
| 32 | const home = async () => main(await (await fetch(`${servers.url}/`, { headers: { cookie: admin } })).text()); | |
| 33 | ||
| 34 | test("home asks for an everyday account in a plain section, no card", async () => { | |
| 35 | const m = await home(); | |
| 36 | const at = m.indexOf('id="everyday"'); | |
| 37 | expect(at).toBeGreaterThan(0); | |
| 38 | const block = m.slice(m.lastIndexOf("<section", at), m.indexOf("</section>", at)); | |
| 39 | expect(block).toContain('class="settings-block"'); | |
| 40 | expect(block).toMatch(/<h2>Make your everyday account<\/h2>/); | |
| 41 | expect(block).toContain(EVERYDAY_WHY); | |
| 42 | expect(block).not.toContain('class="card'); | |
| 43 | expect(block).toContain('action="/settings/everyday"'); | |
| 44 | expect(block).toContain('action="/settings/everyday/keep"'); | |
| 45 | }); | |
| 46 | ||
| 47 | test("dismissing is a same-origin POST, and home stops asking", async () => { | |
| 48 | const cross = await fetch(`${servers.url}/settings/everyday/keep`, { | |
| 49 | method: "POST", | |
| 50 | headers: { cookie: admin, origin: "https://evil.example", "content-type": "application/x-www-form-urlencoded" }, | |
| 51 | redirect: "manual", | |
| 52 | }); | |
| 53 | expect(cross.status).toBe(403); | |
| 54 | expect(await home()).toContain('id="everyday"'); | |
| 55 | const ok = await fetch(`${servers.url}/settings/everyday/keep`, { | |
| 56 | method: "POST", | |
| 57 | headers: { cookie: admin, origin: servers.url, "content-type": "application/x-www-form-urlencoded" }, | |
| 58 | redirect: "manual", | |
| 59 | }); | |
| 60 | expect(ok.status).toBeLessThan(400); | |
| 61 | expect(await home()).not.toContain('id="everyday"'); | |
| 62 | }); |
Principle 1: this h2 and the EVERYDAY_WHY sentence repeat what everydayForm writes for the boxed card. Share one helper. Also update the file header, which still says card.