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

Home's stalled-step button and the pull page's Check again rest without a spinning loader #3068

Merged opened this

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

Merged

scout/forge:scout/3066-home-s-stalled-step-button-and-the-pull → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/3066-home-s-stalled-step-button-and-the-pull was merged into main as eb012f4.

Review

Sign in to review this change.
3 files changed +28 −2
src/web/pages/pull.ts +1 −1
@@ -358,7 +358,7 @@ function pullChecksLine(
358358 tier,
359359 pull.state === "open" && canWrite(ctx.app.db, repo, ctx.auth.user)
360360 ? html`<form method="post" action="${thread}/checks" class="inline-form">
361 ${btn("Check again", { size: "sm", icon: "loader" })}
361 ${btn("Check again", { size: "sm", icon: "play" })}
362362 </form>`
363363 : "",
364364 status.conflicts.length,
src/web/studio/flow_card.ts +1 −1
@@ -26,7 +26,7 @@ function stalledCard(s: StalledStep): Html {
2626 <p class="fcard-reason"><a href="${base}">${s.title}</a>${s.issue === null ? null : html` <span class="muted small">for #${String(s.issue)}</span>`}</p>
2727 <form method="post" action="${base}/checks" class="fcard-unblock">
2828 <input type="hidden" name="next" value="/">
29 ${btn(BUTTON[s.action], { size: "sm", icon: "loader" })}
29 ${btn(BUTTON[s.action], { size: "sm", icon: "play" })}
3030 <span class="muted small">${s.unblock}</span>
3131 </form>
3232 </article>`;
test/button-rest.test.ts added +26
@@ -0,0 +1,26 @@
1 import { expect, test } from "bun:test";
2 import { Glob } from "bun";
3
4 // A button at rest must not spin: `.ico-loader` always animates, so a
5 // btn(...) whose icon is "loader" looks busy before anyone clicks it.
6 test("no btn(...) call in src/web rests on the loader icon", async () => {
7 const offenders: string[] = [];
8 for (const path of new Glob("src/web/**/*.ts").scanSync(".")) {
9 const text = await Bun.file(path).text();
10 for (const m of text.matchAll(/\bbtn\(/g)) {
11 // Read the call's argument list by matching parentheses.

The paren matcher ignores parentheses inside string literals, such as a label like "Retry (x)". It cannot give a false pass on the loader check today. If a label ever unbalances it, scan to the closing paren with a small tokenizer.

12 let depth = 0;
13 let end = m.index! + m[0].length - 1;
14 for (; end < text.length; end++) {
15 if (text[end] === "(") depth++;
16 else if (text[end] === ")" && --depth === 0) break;
17 }
18 const call = text.slice(m.index!, end + 1);
19 if (/\bicon:\s*["']loader["']/.test(call)) {
20 const line = text.slice(0, m.index).split("\n").length;
21 offenders.push(`${path}:${line}`);
22 }
23 }
24 }
25 expect(offenders).toEqual([]);
26 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs