Public: anyone can read this; the crew can change it.

small: the crew-readiness danger toasts on /crew are banners: each becomes one line beside the crew it is about #11883

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/11870-small-the-crew-readiness-danger-toasts-o → main 4 commits fork 1 thread · 1 unresolved

  • Merged. scout/11870-small-the-crew-readiness-danger-toasts-o was merged into main as fda2271.

Review

Sign in to review this change.
4 files changed +19 −19
src/web/pages/crew.ts +7 −12
@@ -133,18 +133,15 @@ function panel(body: Html, o: PanelOptions): Html {
133133 */
134134 export function crewReadiness(ctx: Ctx): Html {
135135 const crew = crewFor(ctx.app);
136 // One line each, beside the crew it is about: no banner, no alert role.
137 const line = (word: string, body: Html, id?: string): Html =>
138 html`<div class="small mb" ${id ? html`id="${id}"` : null}>${icon("alert")} <span class="pill" data-tone="danger">${word}</span> ${body}</div>`;
136139 if (crew === undefined && isHostedChild(ctx.app))
137 return html`<div class="toast" data-tone="danger" role="alert">${icon("alert")}<div class="toast-body">This forge is hosted inside another forge's process,
138 which has no crew runner for it: assigned issues will wait. The crew
139 runs once this forge has a process of its own.</div></div>`;
140 return line("No runner", html`This forge is hosted inside another forge's process, which has no crew runner for it: assigned issues will wait.`);
140141 if (crew === undefined)
141 return html`<div class="toast" data-tone="danger" role="alert">${icon("alert")}<div class="toast-body">No crew runner is up on this forge, so assigned issues
142 will wait. Start the forge with apps on (the default) and Claude Code
143 on <span class="mono">PATH</span> or at
144 <span class="mono">FORGE_CLAUDE</span>.</div></div>`;
142 return line("No runner", html`No crew runner is up on this forge, so assigned issues will wait: start it with apps on and Claude Code on <span class="mono">PATH</span> or at <span class="mono">FORGE_CLAUDE</span>.`);
145143 if (!crew.ready)
146 return html`<div class="toast" data-tone="danger" role="alert">${icon("alert")}<div class="toast-body">The crew cannot run: ${crew.detail}. Agents can be
147 created, but nothing picks up their work until this is fixed.</div></div>`;
144 return line("Cannot run", html`The crew cannot run: ${crew.detail}. Nothing picks up agents' work until this is fixed.`);
148145 const funded = fundingOf(ctx.app.config.dataDir);
149146 // A word and a glyph; a forge its parent funds says so on the same line (#10484).
150147 return html`<p class="mb">
@@ -153,9 +150,7 @@ export function crewReadiness(ctx: Ctx): Html {
153150 ${when(
154151 !crew.sandbox.enforced,
155152 () =>
156 html`<div class="toast" data-tone="danger" role="alert" id="crew-unsandboxed">${icon("alert")}<div class="toast-body">Unsandboxed: agents' shells run unrestricted here
157 (${crew.sandbox.reason}). They can read this forge's data and reach
158 any host.</div></div>`,
153 line("Unsandboxed", html`Agents' shells run unrestricted here (${crew.sandbox.reason}): they can read this forge's data and reach any host.`, "crew-unsandboxed"),
159154 )}`;
160155 }
161156
test/calm-banners.test.ts +7
@@ -56,6 +56,13 @@ test("a healthy forge's home and board have no alert, banner or danger notice",
5656 }
5757 });
5858
59 test("/crew's readiness faults are one line each, never an alert or a toast (#11870)", async () => {
60 const body = await get("/settings/crew");
61 expect(body).toContain("crew runner");
62 expect(alarming(body)).toBe(false);
63 expect(body).not.toMatch(/class="toast"[^>]*data-tone="danger"/);
64 });
65
5966 test("with egress enforced, System and Capacity say nothing about it, and neither does the terminal", async () => {
6067 expect(await get("/system")).not.toContain("egress-reported");
6168 expect(await get("/settings/capacity")).not.toContain("egress-reported");
test/crew-language.test.ts +4 −3
@@ -60,9 +60,10 @@ const speaksPlainly = (main: string): void => {
6060 expect(main).not.toContain("card-body");
6161 expect(main).toMatch(/runner is up|cannot run|Ready<\/strong>|hosted inside/);
6262 expect(main).toContain('action="/settings/crew"');
63 // The danger notice (no runner in this test) wears a class the stylesheet really styles.
64 expect(main).toContain('class="toast" data-tone="danger" role="alert"');
65 expect(components).toContain('.toast[data-tone="danger"]');
63 // The danger notice (no runner in this test) is one line wearing a pill the stylesheet really styles (#11870).
64 expect(main).toContain('<span class="pill" data-tone="danger">');
65 expect(main).not.toContain('role="alert"');

Fine as a proof the pill is styled. The real guard belongs in no-alarm-banner.test.ts, which still names /crew as a known exception (box 2).

66 expect(components).toContain('.pill[data-tone="danger"]');
6667 expect(main).not.toContain('class="error');
6768 };
6869
test/no-alarm-banner.test.ts +1 −4
@@ -16,7 +16,6 @@
1616 * history of the running commit, and the stuck, silent, lane and paid-order
1717 * alarms; every flow alarm draws through the same `alarmLines`, which the
1818 * three raised here hold to one line each.
19 * Known exception, tracked in #11870: the crew-readiness danger toasts on /crew.
2019 */
2120 import { afterAll, expect, test } from "bun:test";
2221 import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
@@ -164,7 +163,7 @@ const ALARMS: readonly { readonly alarm: string; readonly says: string; readonly
164163 { alarm: "sentinel finding", says: "The sentinel has 1 open finding", pages: ["/board"] },
165164 { alarm: "egress reported", says: "Egress is reported, not enforced", pages: ["/system"] },
166165 ];
167 const PAGES = ["/", "/alice/forge/next", "/alice/forge", "/board", "/system"];
166 const PAGES = ["/", "/alice/forge/next", "/alice/forge", "/board", "/system", "/settings/crew"];
168167
169168 test("each alarm is raised, said on its page, and no page draws a banner for any of them", async () => {
170169 const w = await open(0);
@@ -205,6 +204,4 @@ test("the detector names a banner and passes the lines the forge really draws",
205204 expect(offenders("/", '<span class="menu-item" aria-disabled="true" title="this host&#39;s disk has 0.0% free">New forge</span>')).toEqual([]);
206205 expect(offenders("/", '<p class="error" role="alert" hidden></p>')).toEqual([]);
207206 expect(offenders("/", '<a class="stat" data-tone="attn" href="/x">Backup overdue</a>')).toEqual([]);
208 // The known exception (#11870): the crew-readiness toast is a banner, and the detector sees it.
209 expect(offenders("/crew", '<div class="toast" data-tone="danger" role="alert"><div class="toast-body">No crew runner is up</div></div>')).toHaveLength(1);
210207 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs