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

The releases list is keyboard-first: j/k move between releases, enter opens one #10360

Merged opened this

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

Merged

scout/forge:scout/10343-the-releases-list-is-keyboard-first-j-k → main 4 commits fork

  • Merged. scout/10343-the-releases-list-is-keyboard-first-j-k was merged into main as 80e1a02.

Review

Sign in to review this change.
3 files changed +67 −8
docs/design/attention-budget.json +8 −8
@@ -13,25 +13,25 @@
1313 },
1414 "repository": {
1515 "390": {
16 "words": 189,
17 "blocks": 28,
16 "words": 197,
17 "blocks": 29,
1818 "colours": 2
1919 },
2020 "1280": {
21 "words": 189,
22 "blocks": 28,
21 "words": 197,
22 "blocks": 29,
2323 "colours": 2
2424 }
2525 },
2626 "app": {
2727 "390": {
28 "words": 189,
29 "blocks": 28,
28 "words": 197,
29 "blocks": 29,
3030 "colours": 2
3131 },
3232 "1280": {
33 "words": 189,
34 "blocks": 28,
33 "words": 197,
34 "blocks": 29,
3535 "colours": 2
3636 }
3737 },
src/web/pages/releases.ts +1
@@ -63,6 +63,7 @@ export async function releasesPage(ctx: Ctx): Promise<Response> {
6363 const repo = r.pullNumber === null ? undefined : repos.get(r.repoId);
6464 rows.push(
6565 row({
66 nav: true,
6667 ...(repo === undefined || r.pullNumber === null ? {} : { href: `/${repo.ownerName}/${repo.name}/pulls/${r.pullNumber}` }),
6768 title: html`${sha(r.sha)} ${when(isLive, () => badge("live", { tone: "success" }))}
6869 ${when(!r.ok, () => badge("preflight failed", { tone: "danger" }))}`,
test/keys-releases.test.ts added +58
@@ -0,0 +1,58 @@
1 /** The releases page joins the shell's j/k row traversal: one `data-nav` row per release, none when empty (#10343). */
2 import { afterAll, beforeAll, expect, setDefaultTimeout, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createRepo } from "../src/actions/repos.ts";
7 import { createApp, type App } from "../src/app.ts";
8 import { createSession } from "../src/core/sessions.ts";
9 import { markVerified } from "../src/core/verify.ts";
10 import { serve, type Servers } from "../src/http/server.ts";
11 import { recordRelease } from "../src/release/record.ts";
12 import { quietLogs } from "./helpers/globals.ts";
13
14 quietLogs();
15 setDefaultTimeout(30_000);
16
17 let root = "";
18 let app: App;
19 let servers: Servers;
20 let cookie = "";
21
22 beforeAll(async () => {
23 root = mkdtempSync(join(tmpdir(), "forge-releases-keys-"));
24 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
25 app.db
26 .query("INSERT INTO user (id, username, email, password_hash, is_admin) VALUES ('u-alice', 'alice', '[email protected]', 'x', 1)")
27 .run();
28 markVerified(app.db, "u-alice", new Date().toISOString());
29 cookie = `forge_session=${createSession(app.db, "u-alice").token}`;
30 const made = await createRepo(app, { id: "u-alice", username: "alice" } as never, { name: "forge" });
31 if (!made.ok) throw new Error(made.error);
32 servers = await serve(app);
33 });
34 afterAll(() => {
35 servers.stop();
36 app.close();
37 rmSync(root, { recursive: true, force: true });
38 });
39
40 const get = async () => (await fetch(`${servers.url}/releases`, { headers: { cookie } })).text();
41
42 test("a page with no rows has no data-nav", async () => {
43 const text = await get();
44 expect(text).toContain("No releases yet");
45 expect(text).not.toContain("data-nav");
46 });
47
48 test("each release row is a data-nav link to its pull", async () => {
49 const repoId = app.db.query<{ id: string }, []>("SELECT id FROM repo WHERE name='forge'").get()!.id;
50 recordRelease(app.db, { sha: "d".repeat(40), repoId, pullNumber: 3, ok: true, line: "ok" });
51 recordRelease(app.db, { sha: "e".repeat(40), repoId, pullNumber: 4, ok: true, line: "ok" });
52 const text = await get();
53 const navs = text.match(/<a [^>]*data-nav[^>]*>/g) ?? [];
54 expect(navs.length).toBe(2);
55 expect(navs.some((a) => a.includes('href="/alice/forge/pulls/3"'))).toBe(true);
56 expect(navs.some((a) => a.includes('href="/alice/forge/pulls/4"'))).toBe(true);
57 expect(navs.every((a) => a.includes('tabindex="-1"'))).toBe(true);
58 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs