Public: anyone can read this; the crew can change it.
The builds list is keyboard-first: j/k move between rows, enter opens one (fresh from main) #10673
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/10577-the-builds-list-is-keyboard-first-j-k-mo → main 2 commits fork
2 files
changed +26
−0
src/web/pages/builds.ts +1
| @@ -109,6 +109,7 @@ function buildRow(base: string, n: BuildNode, canPreview: boolean, canDelete: bo | ||
| 109 | 109 | const label = encodeURIComponent(n.pin.label); |
| 110 | 110 | const note = n.pin.note === n.pin.label ? "" : `${n.pin.note} · `; |
| 111 | 111 | return row({ |
| 112 | nav: true, | |
| 112 | 113 | title: html`${when(n.edge === "concept", () => html`<span class="build-concept" title="Concept edge: parent set by hand, not by commit history">┄ </span>`)}<a class="mono" href="${own ? base : `/${n.from}`}/commit/${n.pin.oid}">${n.from === undefined ? null : html`<span class="muted">${n.from}:</span>`}${n.pin.label}</a>`, |
| 113 | 114 | meta: html`${note}${n.pin.source} · ${n.pin.pinnedBy} · ${time(n.pin.pinnedAt)}`, |
| 114 | 115 | data: { depth: String(Math.min(n.depth, 8)) }, |
test/keys-builds.test.ts added +25
| @@ -0,0 +1,25 @@ | ||
| 1 | /** The builds list joins the shell's j/k row traversal, as the bets list does: one data-nav per pin row, none on the empty page. */ | |
| 2 | import { expect, test } from "bun:test"; | |
| 3 | import { buildsList } from "../src/web/pages/builds.ts"; | |
| 4 | import type { BuildNode } from "../src/core/lineage.ts"; | |
| 5 | ||
| 6 | const node = (label: string, depth = 0) => | |
| 7 | ({ | |
| 8 | pin: { label, oid: "a".repeat(40), note: label, source: "commit", pinnedBy: "dev", pinnedAt: 0 }, | |
| 9 | depth, | |
| 10 | edge: "git", | |
| 11 | }) as unknown as BuildNode; | |
| 12 | const NAV = /class="row"[^>]*data-nav tabindex="-1"/; | |
| 13 | ||
| 14 | test("the empty builds page draws no data-nav row", () => { | |
| 15 | const out = buildsList("/dev/lib", [], true).toString(); | |
| 16 | expect(out).toContain("No builds yet."); | |
| 17 | expect(out).not.toMatch(NAV); | |
| 18 | }); | |
| 19 | ||
| 20 | test("each pin is one data-nav row whose first link opens its commit", () => { | |
| 21 | const out = buildsList("/dev/lib", [node("v1"), node("v2", 1), node("v3", 1)], false).toString(); | |
| 22 | const rows = out.split("<li").filter((li) => NAV.test(li)); | |
| 23 | expect(rows.length).toBe(3); | |
| 24 | expect(rows[0]!.match(/<a [^>]*href="([^"]+)"/)![1]).toBe(`/dev/lib/commit/${"a".repeat(40)}`); | |
| 25 | }); |