Public: anyone can read this; the crew can change it.
Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:2ycx19qxfm was degraded 113 minutes today: trevato/forge: "verify an email from the People screen > an admin verifies the person under the cursor and the row chan… #7812
Check · Review · Land · Live done — Landed and live in production.
mason/forge:mason/7768-sentinel-shared-01m23vygyffx5hg2f5vm8r74 → main 2 commits fork 1 thread · 1 unresolved
3 files
changed +26
−3
src/tui/app/app.ts +3 −1
| @@ -263,6 +263,8 @@ export interface AppOpts extends SessionOpts { | ||
| 263 | 263 | graphics: GraphicsSetting; |
| 264 | 264 | icons: IconSetting; |
| 265 | 265 | }) => void; |
| 266 | /** How long a toast stays up. @default 4000 */ | |
| 267 | readonly toastMs?: number; | |
| 266 | 268 | readonly root: (ctx: Ctx) => Screen; |
| 267 | 269 | readonly commands?: (ctx: Ctx) => Command[]; |
| 268 | 270 | /** Turns the palette into the global search box. */ |
| @@ -554,7 +556,7 @@ export class App { | ||
| 554 | 556 | } |
| 555 | 557 | |
| 556 | 558 | toast(text: string, tone: Tone = "default"): void { |
| 557 | this.toastState = { text, tone, until: Date.now() + TOAST_MS }; | |
| 559 | this.toastState = { text, tone, until: Date.now() + (this.opts.toastMs ?? TOAST_MS) }; | |
| 558 | 560 | this.invalidate(); |
| 559 | 561 | } |
| 560 | 562 |
src/tui/forge.ts +2
| @@ -623,6 +623,8 @@ function askFor( | ||
| 623 | 623 | |
| 624 | 624 | export interface ForgeAppOptions extends SessionOpts { |
| 625 | 625 | readonly terminal: Terminal; |
| 626 | /** How long a toast stays up; a test that reads one after a settle makes it long. */ | |
| 627 | readonly toastMs?: number; | |
| 626 | 628 | } |
| 627 | 629 | |
| 628 | 630 | /** Assemble the terminal app for one identity, theme persistence included. */ |
test/tui-people-verify.test.ts +21 −2
| @@ -42,9 +42,12 @@ describe("verify an email from the People screen", () => { | ||
| 42 | 42 | await rm(dataDir, { recursive: true, force: true }); |
| 43 | 43 | }); |
| 44 | 44 | |
| 45 | async function open(user: User) { | |
| 45 | // A toast lives four wall-clock seconds by default, and a loaded check | |
| 46 | // machine can take longer than that to settle after the key that raised it. | |
| 47 | // These tests read toasts after a settle, so the toast outlasts the test. | |
| 48 | async function open(user: User, toastMs = 10 * 60_000) { | |
| 46 | 49 | const terminal = headlessTerminal(120, 40); |
| 47 | const { app } = createForgeApp(forge, { sshUsername: "alice", remoteAddress: "127.0.0.1", fingerprint: "SHA256:test", user }, { terminal, exitHooks: false }); | |
| 50 | const { app } = createForgeApp(forge, { sshUsername: "alice", remoteAddress: "127.0.0.1", fingerprint: "SHA256:test", user }, { terminal, exitHooks: false, toastMs }); | |
| 48 | 51 | app.start(); |
| 49 | 52 | const press = async (...keys: string[]): Promise<void> => { |
| 50 | 53 | for (const key of keys) app.dispatch({ type: "key", key, ctrl: false, alt: false, shift: false }); |
| @@ -88,6 +91,22 @@ describe("verify an email from the People screen", () => { | ||
| 88 | 91 | } |
| 89 | 92 | }, 30_000); |
| 90 | 93 | |
| 94 | test("a toast is gone once its time has passed, which is why the tests above hold it up", async () => { | |
| 95 | const fred = await createUser(forge.db, { username: "fred", email: "[email protected]", password: "a-long-passphrase", origin: "seed" }); | |
| 96 | if (!fred.ok) throw new Error("signup failed"); | |
| 97 | markUnproven(forge.db, fred.value.id); | |
| 98 | const h = await open({ ...alice }, 1); | |
| 99 | try { | |
| 100 | await h.press("v", "y"); | |
| 101 | expect(emailVerified(forge.db, fred.value.id)).toBe(true); | |
| 102 | await Bun.sleep(20); | |
| 103 | await settleApp(h.app); | |
| 104 | expect(h.text()).not.toContain("fred's email is verified."); | |
| 105 | } finally { | |
| 106 | h.app.stop(); | |
| 107 | } | |
| 108 | }, 30_000); | |
| 109 | ||
| 91 | 110 | test("`r` still changes a role on the list", async () => { |
| 92 | 111 | const h = await open({ ...alice }); |
| 93 | 112 | try { |
new above
offline