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

The accounts list is keyboard-first: j/k move between rows, enter opens one #10646

Merged opened this

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

Merged

scout/forge:scout/10430-the-accounts-list-is-keyboard-first-j-k → main 5 commits fork

  • Merged. scout/10430-the-accounts-list-is-keyboard-first-j-k was merged into main as 617a9d4.

Review

Sign in to review this change.
2 files changed +39 −0
src/web/pages/accounts.ts +1
@@ -84,6 +84,7 @@ function personRow(a: AccountRow, sole: readonly string[]): Html {
8484 // Who may be acted on: a person who is not an admin and is still here.
8585 const actionable = a.kind === "user" && !a.isAdmin && a.removedAt === null;
8686 return row({
87 nav: true,
8788 title: html`${userLink(a.username, { mark: markOf(a) })} ${standing(a)}`,
8889 meta: html`joined
8990 ${time(a.createdAt)}${how.length > 0 ? html` · ${how.join(" · ")}` : null}
test/keys-accounts.test.ts added +38
@@ -0,0 +1,38 @@
1 /** The accounts list joins the shell's j/k row traversal: one data-nav per account row. */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createSession } from "../src/core/sessions.ts";
8 import { getUserByName } from "../src/core/users.ts";
9 import { serve, type Servers } from "../src/http/server.ts";
10 import { seedAdmin } from "./helpers/admin.ts";
11
12 let dataDir = "";
13 let app: App, servers: Servers;
14
15 beforeAll(async () => {
16 dataDir = await mkdtemp(join(tmpdir(), "forge-keys-accounts-"));
17 app = createApp({ dataDir, port: 0 });
18 servers = await serve(app);
19 const a = await seedAdmin(app, { username: "alice", email: "[email protected]", password: "correct-horse-battery" });
20 if (!a.ok) throw new Error(a.error);
21 for (const n of ["bob", "carol"])
22 app.db.query("INSERT INTO user (id, username, kind, email, display_name, password_hash) VALUES (?1, ?1, 'user', ?1 || '@x.test', ?1, 'x')").run(n);
23 });
24 afterAll(async () => {
25 await servers.stop();
26 app.close();
27 await rm(dataDir, { recursive: true, force: true });
28 });
29
30 test("every account row is a data-nav row", async () => {
31 const alice = getUserByName(app.db, "alice")!;
32 const cookie = `forge_session=${createSession(app.db, alice.id).token}`;
33 const text = await (await fetch(`${servers.url}/settings/accounts`, { headers: { cookie } })).text();
34 const list = text.slice(text.indexOf('id="accounts-list"'));
35 const end = list.indexOf("</ul>");
36 const nav = list.slice(0, end).match(/class="row"[^>]*data-nav tabindex="-1"/g);
37 expect(nav?.length).toBe(3);
38 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs