Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/strata/__e2e__/keyboard.spec.ts

Raw
import { expect, type Page, test } from "@playwright/test";
import { startReviewServer } from "../server.js";
import type {
	AskRequest,
	Draft,
	Feedback,
	Review,
	ReviewServer,
} from "../types.js";

const longLine = `const horizontal = "${"x".repeat(360)}";`;

function makeReview(id = "keyboard-snapshot"): Review {
	return {
		snapshot: {
			id,
			repoRoot: "/safe/keyboard-project",
			source: { kind: "working" },
			base: "keyboard-base",
			head: "keyboard-head",
			hunks: [
				{
					id: "alpha-long",
					path: "src/alpha-long.ts",
					header: "@@ -10,112 +10,112 @@ alpha",
					lines: [
						{ kind: "delete", text: "const mode = 'old';", oldLine: 10 },
						{ kind: "add", text: "const mode = 'new';", newLine: 10 },
						{
							kind: "context",
							text: "const firstNeedle = 'Needle';",
							oldLine: 11,
							newLine: 11,
						},
						{
							kind: "context",
							text: longLine,
							oldLine: 12,
							newLine: 12,
						},
						...Array.from({ length: 108 }, (_, index) => ({
							kind: "context" as const,
							text: `alpha filler ${String(index).padStart(3, "0")}`,
							oldLine: index + 13,
							newLine: index + 13,
						})),
					],
				},
				{
					id: "alpha-literal",
					path: "src/alpha-literal.ts",
					header: "@@ -200,2 +200,3 @@ literal",
					lines: [
						{
							kind: "context",
							text: "export function literal() {",
							oldLine: 200,
							newLine: 200,
						},
						{
							kind: "add",
							text: '  return "[literal.*]";',
							newLine: 201,
						},
						{
							kind: "context",
							text: "}",
							oldLine: 201,
							newLine: 202,
						},
					],
				},
				{
					id: "beta-change",
					path: "src/beta-change.ts",
					header: "@@ -300,2 +300,2 @@ beta",
					lines: [
						{ kind: "delete", text: "const beta = false;", oldLine: 300 },
						{ kind: "add", text: "const beta = true;", newLine: 300 },
						{
							kind: "context",
							text: 'const secondNeedle = "needle";',
							oldLine: 301,
							newLine: 301,
						},
					],
				},
				{
					id: "beta-final",
					path: "src/beta-final.ts",
					header: "@@ -400 +400,2 @@ final",
					lines: [
						{
							kind: "context",
							text: "export const final = true;",
							oldLine: 400,
							newLine: 400,
						},
						{
							kind: "add",
							text: 'export const thirdNeedle = "NEEDLE";',
							newLine: 401,
						},
					],
				},
			],
			skipped: Array.from({ length: 24 }, (_, index) => ({
				path: `generated/omitted-${String(index).padStart(2, "0")}.bin`,
				reason: "binary",
			})),
		},
		plan: {
			summary: "Review every keyboard fixture change.",
			cohorts: [
				{
					title: "Alpha",
					layers: [
						{
							id: "alpha",
							title: "Alpha layer",
							summary: "A long hunk followed by a literal-search hunk.",
							hunks: [
								{
									id: "alpha-long",
									summary: Array.from(
										{ length: 35 },
										(_, index) => `Long explanation line ${index + 1}.`,
									).join("\n"),
								},
								{
									id: "alpha-literal",
									summary: "Literal punctuation search.",
								},
							],
							flow: Array.from(
								{ length: 18 },
								(_, index) => `Alpha step ${index + 1}`,
							),
						},
						{
							id: "beta",
							title: "Beta layer",
							summary: "Two compact hunks with separate search matches.",
							hunks: [
								{
									id: "beta-change",
									summary: "Changes the beta switch.",
								},
								{
									id: "beta-final",
									summary: "Adds the final mixed-case search match.",
								},
							],
						},
					],
				},
			],
		},
		draft: {
			reviewed: ["alpha-literal", "beta-final"],
			findings: [],
			notes: "",
		},
	};
}

function makeEmptyReview(): Review {
	const review = makeReview("keyboard-empty");
	review.snapshot.hunks = [];
	review.snapshot.skipped = [];
	review.plan.cohorts = [];
	review.draft = { reviewed: [], findings: [], notes: "" };
	return review;
}

function makeMetadataReview(): Review {
	const review = makeReview("keyboard-metadata");
	review.snapshot.hunks = [
		{
			id: "metadata-first",
			path: "assets/first-mode.sh",
			header: "old mode 100644\nnew mode 100755",
			lines: [{ kind: "meta", text: "old mode 100644" }],
		},
		{
			id: "code-before",
			path: "src/before.ts",
			header: "@@ -1 +1 @@",
			lines: [{ kind: "add", text: "export const before = 1;", newLine: 1 }],
		},
		{
			id: "metadata-middle",
			path: "assets/middle-mode.sh",
			header: "old mode 100644\nnew mode 100755",
			lines: [{ kind: "meta", text: "new mode 100755" }],
		},
		{
			id: "code-after",
			path: "src/after.ts",
			header: "@@ -1 +1 @@",
			lines: [{ kind: "add", text: "export const after = 1;", newLine: 1 }],
		},
	];
	review.plan.cohorts = [
		{
			title: "Metadata",
			layers: [
				{
					id: "metadata",
					title: "Metadata and code",
					summary: "Review metadata-only and text hunks together.",
					hunks: review.snapshot.hunks.map((hunk) => ({
						id: hunk.id,
						summary: `Review ${hunk.path}.`,
					})),
				},
			],
		},
	];
	review.draft = { reviewed: ["code-before"], findings: [], notes: "" };
	return review;
}

function makeViewportReview(): Review {
	const review = makeReview("keyboard-viewport");
	const hunk = (id: string, lineCount: number) => ({
		id,
		path: `src/${id}.ts`,
		header: `@@ -1,${lineCount} +1,${lineCount} @@`,
		lines: Array.from({ length: lineCount }, (_, index) => ({
			kind: "context" as const,
			text: `${id} line ${index + 1}`,
			oldLine: index + 1,
			newLine: index + 1,
		})),
	});
	review.snapshot.hunks = [
		hunk("compact-first", 1),
		hunk("huge-middle", 140),
		hunk("compact-last", 1),
	];
	review.plan.cohorts = [
		{
			title: "Viewport",
			layers: [
				{
					id: "viewport",
					title: "Viewport geometry",
					summary: "Compact hunks surround one huge hunk.",
					hunks: review.snapshot.hunks.map((item) => ({
						id: item.id,
						summary: item.path,
					})),
				},
			],
		},
	];
	review.draft = { reviewed: [], findings: [], notes: "" };
	return review;
}

function currentLine(page: Page) {
	return page.locator(".diff-line.keyboard-current");
}

async function expectCurrentLine(
	page: Page,
	hunkId: string,
	side: "old" | "new",
	line: number,
) {
	const current = currentLine(page);
	await expect(current).toHaveCount(1);
	await expect(current).toHaveAttribute("data-hunk-id", hunkId);
	await expect(current).toHaveAttribute("data-side", side);
	await expect(current).toHaveAttribute("data-line", String(line));
}

async function pressSequence(page: Page, prefix: string, continuation: string) {
	await page.keyboard.press(prefix);
	await page.keyboard.press(continuation);
}

async function closeKeyboardHelp(page: Page) {
	await page.locator("#keyboard-help").evaluate((dialog) => {
		dialog.removeAttribute("data-close-observed");
		dialog.addEventListener(
			"close",
			() => dialog.setAttribute("data-close-observed", "true"),
			{ once: true },
		);
	});
	await page.keyboard.press("Escape");
	await expect(page.locator("#keyboard-help")).toHaveAttribute(
		"data-close-observed",
		"true",
	);
}

async function expectNoViewportOverflow(page: Page) {
	expect(
		await page.evaluate(
			() => document.documentElement.scrollWidth <= window.innerWidth,
		),
	).toBe(true);
}

let server: ReviewServer | undefined;

test.afterEach(() => {
	server?.close();
	server = undefined;
});

test("exposes contextual keyboard UI and complete cancellable prefix help", async ({
	page,
}) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.goto(server.url);

	await expect(page.locator("#status")).toHaveText("Review ready");
	await expect(page.locator("#keyboard-status")).toContainText("NORMAL");
	await expect(page.locator("#keyboard-status")).toContainText("line 10");
	await expect(page.locator("#keyboard-help-button")).toBeVisible();
	await expect(page.locator("#keyboard-help")).toHaveJSProperty("open", false);
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect(page.locator('[data-keyboard-pane="layers"]')).toHaveCount(1);
	await expect(page.locator('[data-keyboard-pane="diff"]')).toHaveCount(1);
	await expect(page.locator('[data-keyboard-pane="drawer"]')).toHaveCount(1);
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await expect(
		page.getByRole("button", { name: "Mark reviewed", exact: true }).first(),
	).toBeVisible();
	await expect(
		page
			.getByRole("button", { name: "Mark reviewed", exact: true })
			.first()
			.locator("kbd"),
	).toHaveText(["Space", "r"]);
	await expect(page.locator("#mark-layer-reviewed")).toBeDisabled();
	await expect(page.locator(".layer-nav kbd")).toHaveCount(0);

	for (const [prefix, continuations] of [
		[
			"Space",
			[
				"Review",
				"Writ",
				"Workspace",
				"r",
				"R",
				"c",
				"a",
				"n",
				"b",
				"d",
				"s",
				"f",
				"q",
			],
		],
		["g", ["d", "a", "f"]],
		["[", ["c", "l", "u"]],
		["]", ["c", "l", "u"]],
	] as const) {
		await page.keyboard.press(prefix);
		await expect(page.locator("#keyboard-guide")).toBeVisible();
		for (const continuation of continuations) {
			await expect(page.locator("#keyboard-guide")).toContainText(continuation);
		}
		await page.keyboard.press("Escape");
		await expect(page.locator("#keyboard-guide")).toBeHidden();
	}

	await page.keyboard.press("g");
	await page.keyboard.press("x");
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect(page.locator("#keyboard-status")).toContainText("NORMAL");

	await page.keyboard.press("?");
	const help = page.locator("#keyboard-help");
	await expect(help).toHaveAttribute("open", "");
	const helpKeys = await help.locator("kbd").allTextContents();
	for (const key of [
		"j",
		"k",
		"h",
		"l",
		"Ctrl+d",
		"Ctrl+u",
		"gg",
		"G",
		"]c",
		"[c",
		"]l",
		"[l",
		"]u",
		"[u",
		"/",
		"n",
		"N",
		"Space r",
		"Space R",
		"Space c",
		"Space a",
		"Space b",
		"gd",
		"ga",
		"gf",
		"Space d",
		"Space s",
		"Space f",
		"Space q",
	]) {
		expect(helpKeys).toContain(key);
	}
	await expect(help).toContainText(/toggle.*hunk/i);
	await expect(help).toContainText(/toggle.*layer/i);
	await expect(help).toContainText(/comment/i);
	await expect(help).toContainText(/notes/i);
	await expect(help).toContainText(/refresh/i);
	await expect(help).toContainText(/close/i);
	for (let index = 0; index < 20; index++) await page.keyboard.press("Tab");
	expect(
		await page.evaluate(
			() => document.activeElement?.closest("#keyboard-help")?.id,
		),
	).toBe("keyboard-help");
	await page.keyboard.press("Escape");
	await expect(help).not.toHaveAttribute("open", "");

	await page.locator("#keyboard-help-button").press("Enter");
	await expect(help).toHaveAttribute("open", "");
	await page.keyboard.press("Escape");

	await page.keyboard.press("[");
	await expect(page.locator("#keyboard-guide")).toContainText("previous hunk");
	await expect(page.locator("#keyboard-guide")).toContainText("previous layer");
	await expect(page.locator("#keyboard-guide")).not.toContainText("next hunk");
	await page.keyboard.press("Escape");
});

test("navigates lines, hunks, layers, unreviewed work, and large hunks within bounds", async ({
	page,
}) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.goto(server.url);

	await expectCurrentLine(page, "alpha-long", "old", 10);
	await page.keyboard.press("k");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await page.keyboard.press("j");
	await expectCurrentLine(page, "alpha-long", "new", 10);
	await page.keyboard.press("k");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "g", "g");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await page.keyboard.press("G");
	await expectCurrentLine(page, "beta-final", "new", 401);
	await page.keyboard.press("j");
	await expectCurrentLine(page, "beta-final", "new", 401);

	await pressSequence(page, "]", "l");
	await expect(page.locator("#diff-heading")).toHaveText("Alpha layer");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "[", "l");
	await expect(page.locator("#diff-heading")).toHaveText("Alpha layer");
	await pressSequence(page, "]", "c");
	await expectCurrentLine(page, "alpha-literal", "new", 200);
	await expect(page.locator("#current-path")).toHaveText(
		"src/alpha-literal.ts",
	);
	await expect(page.locator("#ask-focus")).toContainText(
		"src/alpha-literal.ts",
	);
	await page.waitForTimeout(650);
	await expectCurrentLine(page, "alpha-literal", "new", 200);
	await pressSequence(page, "]", "c");
	await expectCurrentLine(page, "alpha-literal", "new", 200);
	await pressSequence(page, "[", "c");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await page.waitForTimeout(650);
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "[", "c");
	await expectCurrentLine(page, "alpha-long", "old", 10);

	await pressSequence(page, "]", "l");
	await expect(page.locator("#diff-heading")).toHaveText("Beta layer");
	await pressSequence(page, "]", "l");
	await expect(page.locator("#diff-heading")).toHaveText("Beta layer");
	await pressSequence(page, "[", "l");
	await expect(page.locator("#diff-heading")).toHaveText("Alpha layer");

	await page.locator("#all-changes").click();
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "]", "u");
	await expectCurrentLine(page, "beta-change", "old", 300);
	await pressSequence(page, "[", "u");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "[", "u");
	await expectCurrentLine(page, "alpha-long", "old", 10);
});

test("navigates and reviews first and middle metadata-only hunks", async ({
	page,
}) => {
	const saved: Draft[] = [];
	server = await startReviewServer({
		review: makeMetadataReview(),
		isCurrent: async () => true,
		refresh: async () => makeMetadataReview(),
		ask: async () => "answer",
		save: (draft) => saved.push(structuredClone(draft)),
		submit: async () => {},
	});
	await page.goto(server.url);

	await expect(page.locator("#current-path")).toHaveText(
		"assets/first-mode.sh",
	);
	await expect(currentLine(page)).toHaveCount(0);
	await pressSequence(page, "Space", "c");
	await expect(page.locator("#comment-dialog")).not.toHaveAttribute("open", "");
	await pressSequence(page, "Space", "r");
	await expect.poll(() => saved.length).toBe(1);
	expect(saved[0].reviewed).toEqual(["code-before", "metadata-first"]);

	await pressSequence(page, "]", "c");
	await expectCurrentLine(page, "code-before", "new", 1);
	await pressSequence(page, "]", "c");
	await expect(page.locator("#current-path")).toHaveText(
		"assets/middle-mode.sh",
	);
	await expect(currentLine(page)).toHaveCount(0);
	await pressSequence(page, "[", "c");
	await expectCurrentLine(page, "code-before", "new", 1);
	await pressSequence(page, "[", "c");
	await expect(page.locator("#current-path")).toHaveText(
		"assets/first-mode.sh",
	);
	await expect(currentLine(page)).toHaveCount(0);

	await pressSequence(page, "]", "u");
	await expect(page.locator("#current-path")).toHaveText(
		"assets/middle-mode.sh",
	);
	await expect(currentLine(page)).toHaveCount(0);
	await pressSequence(page, "Space", "r");
	await expect.poll(() => saved.length).toBe(2);
	expect(saved[1].reviewed).toEqual([
		"code-before",
		"metadata-first",
		"metadata-middle",
	]);
});

test("updates scroll observations after explicit-jump suppression by visible area", async ({
	page,
}) => {
	server = await startReviewServer({
		review: makeViewportReview(),
		isCurrent: async () => true,
		refresh: async () => makeViewportReview(),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.setViewportSize({ width: 1100, height: 520 });
	await page.goto(server.url);

	await page.locator("#hunk-huge-middle").click();
	await pressSequence(page, "]", "c");
	await expectCurrentLine(page, "compact-last", "new", 1);
	await page.waitForTimeout(600);
	await page.locator(".diff-region").hover();
	await page.mouse.wheel(0, -180);
	await expect(page.locator("#current-path")).toHaveText("src/huge-middle.ts");
	await expect(currentLine(page)).toHaveAttribute(
		"data-hunk-id",
		"huge-middle",
	);

	await page.locator(".diff-region").evaluate((element) => {
		element.scrollTop = 0;
	});
	await expect(page.locator("#current-path")).toHaveText("src/huge-middle.ts");
});

test("scrolls and focuses only visible panes", async ({ page }) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.setViewportSize({ width: 1100, height: 520 });
	await page.goto(server.url);

	const horizontalBefore = await page
		.locator('.hunk[data-hunk-id="alpha-long"] .diff-scroll')
		.evaluate((element) => element.scrollLeft);
	await page.keyboard.press("l");
	await expect
		.poll(() =>
			page
				.locator('.hunk[data-hunk-id="alpha-long"] .diff-scroll')
				.evaluate((element) => element.scrollLeft),
		)
		.toBeGreaterThan(horizontalBefore);
	await page.keyboard.press("h");
	await expect
		.poll(() =>
			page
				.locator('.hunk[data-hunk-id="alpha-long"] .diff-scroll')
				.evaluate((element) => element.scrollLeft),
		)
		.toBeLessThan(horizontalBefore + 1);

	await pressSequence(page, "g", "g");
	const diffPane = page.locator('[data-keyboard-pane="diff"]');
	await page.keyboard.press("Control+d");
	await expect
		.poll(() => diffPane.evaluate((element) => element.scrollTop))
		.toBeGreaterThan(0);
	const halfPage = await diffPane.evaluate((element) => element.scrollTop);
	await page.keyboard.press("Control+u");
	await expect
		.poll(() => diffPane.evaluate((element) => element.scrollTop))
		.toBeLessThan(halfPage);

	await pressSequence(page, "Space", "h");
	const layersPane = page.locator(
		'[data-keyboard-pane="layers"].keyboard-focused',
	);
	await expect(layersPane).toBeVisible();
	await page.keyboard.press("G");
	await expect
		.poll(() => layersPane.evaluate((element) => element.scrollTop))
		.toBeGreaterThan(0);
	await pressSequence(page, "g", "g");
	await expect
		.poll(() => layersPane.evaluate((element) => element.scrollTop))
		.toBe(0);
	await page.keyboard.press("j");
	await expect
		.poll(() => layersPane.evaluate((element) => element.scrollTop))
		.toBeGreaterThan(0);
	const layerScroll = await layersPane.evaluate((element) => element.scrollTop);
	await page.keyboard.press("k");
	await expect
		.poll(() => layersPane.evaluate((element) => element.scrollTop))
		.toBeLessThan(layerScroll);

	await pressSequence(page, "Space", "l");
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
	await pressSequence(page, "Space", "l");
	const drawerPane = page.locator(
		'[data-keyboard-pane="drawer"].keyboard-focused',
	);
	const drawerScroll = page.locator("#details-panel");
	await expect(drawerPane).toBeVisible();
	await page.keyboard.press("G");
	await expect
		.poll(() => drawerScroll.evaluate((element) => element.scrollTop))
		.toBeGreaterThan(0);
	await pressSequence(page, "g", "g");
	await expect
		.poll(() => drawerScroll.evaluate((element) => element.scrollTop))
		.toBe(0);

	await pressSequence(page, "g", "a");
	await expect(page.getByRole("tab", { name: "Ask" })).toHaveAttribute(
		"aria-selected",
		"true",
	);
	await pressSequence(page, "g", "f");
	await expect(page.getByRole("tab", { name: "Feedback" })).toHaveAttribute(
		"aria-selected",
		"true",
	);
	await pressSequence(page, "g", "d");
	await expect(page.getByRole("tab", { name: "Details" })).toHaveAttribute(
		"aria-selected",
		"true",
	);
	await drawerPane.focus();

	await pressSequence(page, "Space", "d");
	await expect(page.locator('[data-keyboard-pane="drawer"]')).toBeHidden();
	await expect(
		page.locator('[data-keyboard-pane="drawer"].keyboard-focused'),
	).toHaveCount(0);
	await expect(
		page.locator("[data-keyboard-pane].keyboard-focused:visible"),
	).toHaveCount(1);
	await pressSequence(page, "Space", "l");
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
	await pressSequence(page, "Space", "h");
	await expect(
		page.locator('[data-keyboard-pane="layers"].keyboard-focused'),
	).toBeVisible();
	await pressSequence(page, "Space", "b");
	await expect(page.locator('[data-keyboard-pane="layers"]')).toBeHidden();
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
});

test("searches visible diff text literally and repeats with wrapping", async ({
	page,
}) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.goto(server.url);

	await page.keyboard.press("/");
	await expect(page.locator("#keyboard-search")).toBeVisible();
	await expect(page.locator("#keyboard-search")).toBeFocused();
	await expect(page.locator("#keyboard-status")).toContainText("TEXT");
	await page.locator("#keyboard-search").fill("[literal.*]");
	await page.locator("#keyboard-search").press("Enter");
	await expectCurrentLine(page, "alpha-literal", "new", 201);
	await page.keyboard.press("n");
	await expectCurrentLine(page, "alpha-literal", "new", 201);

	await page.keyboard.press("/");
	await page.locator("#keyboard-search").fill("needle");
	await page.locator("#keyboard-search").press("Enter");
	const first = await currentLine(page).getAttribute("data-hunk-id");
	await page.keyboard.press("n");
	const second = await currentLine(page).getAttribute("data-hunk-id");
	await page.keyboard.press("n");
	const third = await currentLine(page).getAttribute("data-hunk-id");
	expect(new Set([first, second, third])).toEqual(
		new Set(["alpha-long", "beta-change", "beta-final"]),
	);
	await page.keyboard.press("n");
	await expect(currentLine(page)).toHaveAttribute("data-hunk-id", first ?? "");
	await page.keyboard.press("N");
	await expect(currentLine(page)).toHaveAttribute("data-hunk-id", third ?? "");

	await page.locator('[data-layer-id="alpha"]').click();
	const beforeMissingSearch = await currentLine(page).getAttribute("data-line");
	await page.keyboard.press("/");
	await page.locator("#keyboard-search").fill("secondNeedle");
	await page.locator("#keyboard-search").press("Enter");
	await expect(page.locator("#status")).toContainText("No matches");
	await expect(currentLine(page)).toHaveAttribute(
		"data-line",
		beforeMissingSearch ?? "",
	);

	await page.keyboard.press("/");
	await page.locator("#keyboard-search").fill("unfinished");
	await page.locator("#keyboard-search").press("Escape");
	await expect(page.locator("#keyboard-search")).toBeHidden();
	await expect(page.locator("#keyboard-status")).toContainText("NORMAL");
});

test("routes line, hunk, and whole-layer actions through persisted scope-safe drafts", async ({
	page,
}) => {
	const saved: Draft[] = [];
	let rejectSave = false;
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: (draft) => {
			if (rejectSave) throw new Error("persist blocked");
			saved.push(structuredClone(draft));
		},
		submit: async () => {},
	});
	await page.goto(server.url);
	await expect(page.locator("#status")).toHaveText("Review ready");
	await pressSequence(page, "]", "l");
	await expect(page.locator("#mark-layer-reviewed")).toBeEnabled();
	await expect(
		page.getByRole("button", { name: "Mark layer reviewed", exact: true }),
	).toHaveCount(1);
	await expect(page.locator("#mark-layer-reviewed kbd")).toHaveText([
		"Space",
		"R",
	]);

	await pressSequence(page, "Space", "n");
	await expect(page.locator("#notes")).toBeFocused();
	await page
		.locator("#notes")
		.fill("Keep this note across every review action.");
	await expect(page.locator("#status")).toHaveText("Draft saved");
	await page.keyboard.press("Escape");

	await pressSequence(page, "Space", "a");
	await expect(page.locator("#question")).toBeFocused();
	await page.locator("#question").fill("Alpha unsent question");
	await page.keyboard.press("Escape");
	await pressSequence(page, "]", "l");
	await pressSequence(page, "Space", "a");
	await page.locator("#question").fill("Beta unsent question");
	await page.keyboard.press("Escape");
	await pressSequence(page, "[", "l");
	await pressSequence(page, "Space", "a");
	await expect(page.locator("#question")).toHaveValue("Alpha unsent question");
	await page.keyboard.press("Escape");

	await pressSequence(page, "g", "g");
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await pressSequence(page, "Space", "c");
	await expect(page.locator("#comment-dialog")).toHaveAttribute("open", "");
	await expect(page.locator("#comment-anchor")).toContainText("old:10");
	await page.locator("#comment-text").fill("Old-side keyboard finding.");
	await page.getByRole("button", { name: "Add comment", exact: true }).click();
	await expect(page.locator(".finding")).toContainText(
		"Old-side keyboard finding.",
	);
	await currentLine(page).focus();
	await page.keyboard.press("j");
	await expectCurrentLine(page, "alpha-long", "new", 10);
	await pressSequence(page, "Space", "c");
	await expect(page.locator("#comment-anchor")).toContainText("new:10");
	await page.getByRole("button", { name: "Cancel", exact: true }).click();

	const beforeHunkToggle = saved.length;
	await pressSequence(page, "Space", "r");
	await expect.poll(() => saved.length).toBe(beforeHunkToggle + 1);
	expect(saved.at(-1)?.reviewed).toEqual([
		"alpha-literal",
		"beta-final",
		"alpha-long",
	]);
	await pressSequence(page, "Space", "r");
	await expect.poll(() => saved.length).toBe(beforeHunkToggle + 2);
	expect(saved.at(-1)?.reviewed).toEqual(["alpha-literal", "beta-final"]);

	const beforeWholeLayer = saved.length;
	await pressSequence(page, "Space", "R");
	await expect.poll(() => saved.length).toBe(beforeWholeLayer + 1);
	expect(new Set(saved.at(-1)?.reviewed)).toEqual(
		new Set(["alpha-long", "alpha-literal", "beta-final"]),
	);
	await expect(
		page.getByRole("button", { name: "Unmark layer reviewed", exact: true }),
	).toHaveCount(1);
	await pressSequence(page, "Space", "R");
	await expect.poll(() => saved.length).toBe(beforeWholeLayer + 2);
	expect(saved.at(-1)?.reviewed).toEqual(["beta-final"]);

	rejectSave = true;
	await pressSequence(page, "Space", "r");
	await expect(page.locator("#status")).toHaveText("persist blocked");
	await expect(
		page.getByRole("button", { name: "Mark reviewed", exact: true }).first(),
	).toBeVisible();
	await pressSequence(page, "g", "f");
	await expect(page.locator("#notes")).toHaveValue(
		"Keep this note across every review action.",
	);
	await expect(page.locator(".finding")).toContainText(
		"Old-side keyboard finding.",
	);
	await pressSequence(page, "Space", "a");
	await expect(page.locator("#question")).toHaveValue("Alpha unsent question");

	await page.keyboard.press("Escape");
	await page.locator("#all-changes").click();
	await expect(page.locator("#mark-layer-reviewed")).toBeDisabled();
	const savesAtAllChanges = saved.length;
	await pressSequence(page, "Space", "R");
	await page.waitForTimeout(100);
	expect(saved).toHaveLength(savesAtAllChanges);
});

test("preserves the latest optimistic draft across delayed saves and flushes", async ({
	page,
}) => {
	const saved: Draft[] = [];
	const submitted: Feedback[] = [];
	let refreshes = 0;
	let requestCount = 0;
	const releases = new Map<number, () => void>();
	const gates = new Map<number, Promise<void>>();
	for (const index of [1, 2, 4, 5]) {
		gates.set(
			index,
			new Promise<void>((resolve) => releases.set(index, resolve)),
		);
	}
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => {
			refreshes++;
			return makeReview("keyboard-refreshed");
		},
		ask: async () => "answer",
		save: (draft) => saved.push(structuredClone(draft)),
		submit: async (feedback) => {
			submitted.push(structuredClone(feedback));
		},
	});
	await page.route("**/api/draft", async (route) => {
		const index = ++requestCount;
		const response = await route.fetch();
		await gates.get(index);
		await route.fulfill({ response });
	});
	await page.goto(server.url);

	await page.locator('.mark-reviewed[data-hunk-id="alpha-long"]').click();
	await expect.poll(() => requestCount).toBe(1);
	await page.locator('.mark-reviewed[data-hunk-id="alpha-literal"]').click();
	releases.get(1)?.();
	await expect.poll(() => requestCount).toBe(2);
	await page.locator('.mark-reviewed[data-hunk-id="beta-change"]').click();
	releases.get(2)?.();

	await page.getByRole("tab", { name: "Feedback" }).click();
	page.once("dialog", (dialog) => dialog.accept());
	await page.getByRole("button", { name: "Send feedback to Pi" }).click();
	await expect(page.locator("#status")).toHaveText("Feedback sent to Pi");
	expect(new Set(submitted[0].reviewed)).toEqual(
		new Set(["beta-final", "alpha-long", "beta-change"]),
	);

	await page.locator('.mark-reviewed[data-hunk-id="alpha-long"]').click();
	await expect.poll(() => requestCount).toBe(4);
	await page.locator('.mark-reviewed[data-hunk-id="alpha-literal"]').click();
	releases.get(4)?.();
	await expect.poll(() => requestCount).toBe(5);
	await page.locator('.mark-reviewed[data-hunk-id="beta-change"]').click();
	releases.get(5)?.();

	page.once("dialog", (dialog) => dialog.accept());
	await page.getByRole("button", { name: "Refresh", exact: true }).click();
	await expect(page.locator("#source-label")).toContainText("keyboard-ref");
	expect(refreshes).toBe(1);
	expect(new Set(saved.at(-1)?.reviewed)).toEqual(
		new Set(["alpha-long", "alpha-literal", "beta-change"]),
	);
});

test("guards native controls, text entry, composition, and unrelated modifiers", async ({
	page,
}) => {
	const requests: AskRequest[] = [];
	const saved: Draft[] = [];
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async (request) => {
			requests.push(structuredClone(request));
			return "answer";
		},
		save: (draft) => saved.push(structuredClone(draft)),
		submit: async () => {},
	});
	await page.goto(server.url);
	await expectCurrentLine(page, "alpha-long", "old", 10);

	for (const event of [
		{ key: "j", altKey: true },
		{ key: "j", ctrlKey: true },
		{ key: "J", shiftKey: true },
		{ key: "j", metaKey: true },
	]) {
		await currentLine(page).evaluate((element, init) => {
			element.dispatchEvent(
				new KeyboardEvent("keydown", { ...init, bubbles: true }),
			);
		}, event);
		await expectCurrentLine(page, "alpha-long", "old", 10);
	}

	await currentLine(page).evaluate((element) => {
		element.dispatchEvent(
			new KeyboardEvent("keydown", {
				key: "?",
				code: "Slash",
				shiftKey: true,
				bubbles: true,
			}),
		);
	});
	await expect(page.locator("#keyboard-help")).toHaveAttribute("open", "");
	await page.keyboard.press("Escape");
	for (const event of [
		{ key: "/", shiftKey: true },
		{
			key: "/",
			altKey: true,
			ctrlKey: true,
			modifierAltGraph: true,
		},
	]) {
		await currentLine(page).evaluate((element, init) => {
			element.dispatchEvent(
				new KeyboardEvent("keydown", { ...init, bubbles: true }),
			);
		}, event);
		await expect(page.locator("#keyboard-search")).toBeVisible();
		await page.keyboard.press("Escape");
	}
	await currentLine(page).evaluate((element) => {
		element.dispatchEvent(
			new KeyboardEvent("keydown", {
				key: "[",
				altKey: true,
				ctrlKey: true,
				modifierAltGraph: true,
				bubbles: true,
			}),
		);
	});
	await expect(page.locator("#keyboard-guide")).toContainText("previous hunk");
	await page.keyboard.press("Escape");

	await page.locator(".diff-region").focus();
	await page.keyboard.press("Space");
	await expect(page.locator("#keyboard-guide")).toBeVisible();
	await page.keyboard.press("Tab");
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect(page.locator(".diff-region")).not.toBeFocused();

	await page.locator(".diff-region").focus();
	await page.locator(".diff-region").evaluate((element) => {
		element.scrollTop = 0;
	});
	await page.keyboard.press("g");
	await page.keyboard.press("Control+d");
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect
		.poll(() =>
			page.locator(".diff-region").evaluate((element) => element.scrollTop),
		)
		.toBeGreaterThan(0);
	await page.keyboard.press("g");
	await page.keyboard.press("Alt+j");
	await expect(page.locator("#keyboard-guide")).toBeHidden();

	await pressSequence(page, "Space", "a");
	const question = page.locator("#question");
	await expect(question).toBeFocused();
	await question.pressSequentially("jkG? / n Space r");
	await question.press("Enter");
	await question.press("Space");
	await expect(question).toHaveValue("jkG? / n Space r\n ");
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect(page.locator("#keyboard-help")).not.toHaveAttribute("open", "");
	await expectCurrentLine(page, "alpha-long", "old", 10);

	await question.evaluate((element) => {
		element.dispatchEvent(
			new KeyboardEvent("keydown", {
				key: "j",
				bubbles: true,
				isComposing: true,
			}),
		);
		element.dispatchEvent(
			new KeyboardEvent("keydown", {
				key: "Escape",
				bubbles: true,
				isComposing: true,
			}),
		);
		element.dispatchEvent(
			new KeyboardEvent("keydown", {
				key: "Enter",
				shiftKey: true,
				bubbles: true,
				isComposing: true,
			}),
		);
	});
	await expect(question).toBeFocused();
	expect(requests).toHaveLength(0);
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await question.press("Escape");
	await expect(page.locator("#keyboard-status")).toContainText("NORMAL");
	await expect(currentLine(page)).toBeFocused();

	await page.evaluate(() => {
		const editable = document.createElement("div");
		editable.id = "keyboard-editable-fixture";
		editable.contentEditable = "true";
		editable.tabIndex = 0;
		document.body.append(editable);
		editable.focus();
	});
	await page.keyboard.type("jG?");
	await expect(page.locator("#keyboard-editable-fixture")).toHaveText("jG?");
	await expectCurrentLine(page, "alpha-long", "old", 10);

	await page
		.locator("#flow .flow-step")
		.first()
		.evaluate((element) => {
			element.dataset.retainedWithinHunk = "true";
		});
	await currentLine(page).focus();
	await page.keyboard.press("j");
	await expectCurrentLine(page, "alpha-long", "new", 10);
	await expect(
		page.locator('#flow .flow-step[data-retained-within-hunk="true"]'),
	).toHaveCount(1);

	const mark = page.locator('.mark-reviewed[data-hunk-id="alpha-long"]');
	await mark.focus();
	const beforeNativeSpace = saved.length;
	await mark.press("Space");
	await expect.poll(() => saved.length).toBe(beforeNativeSpace + 1);
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await expect(mark).toHaveAttribute("aria-pressed", "true");
	await expect(mark).toBeFocused();
	await page.keyboard.press("Tab");
	await expect(mark).not.toBeFocused();
	await expect(mark).toHaveAttribute("aria-pressed", "true");
	await expect(page.locator("#keyboard-guide")).toBeHidden();

	await page.locator("#keyboard-help-button").focus();
	await page.keyboard.press("Tab");
	await expect(page.locator("#keyboard-guide")).toBeHidden();
	await page.locator("#keyboard-help-button").press("Enter");
	await expect(page.locator("#keyboard-help")).toHaveAttribute("open", "");
	await page.keyboard.press("Escape");
	await currentLine(page).focus();

	await pressSequence(page, "Space", "a");
	await question.fill("Shift Enter still submits");
	await question.press("Shift+Enter");
	await expect.poll(() => requests.length).toBe(1);
	expect(requests[0].question).toBe("Shift Enter still submits");
});

test("uses shared confirmations for mouse and keyboard submit, refresh, and close", async ({
	page,
}) => {
	const submitted: Feedback[] = [];
	let refreshes = 0;
	let closes = 0;
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => {
			refreshes++;
			return makeReview("keyboard-refreshed");
		},
		ask: async () => "answer",
		save: () => {},
		submit: async (feedback) => {
			submitted.push(structuredClone(feedback));
		},
		onClose: () => closes++,
	});
	await page.goto(server.url);
	await pressSequence(page, "Space", "n");
	await page.locator("#notes").fill("Confirm before sending this note.");
	await expect(page.locator("#status")).toHaveText("Draft saved");
	await page.keyboard.press("Escape");

	let submitPrompt = "";
	page.once("dialog", async (dialog) => {
		submitPrompt = dialog.message();
		await dialog.dismiss();
	});
	await page.getByRole("button", { name: "Send feedback to Pi" }).click();
	expect(submitPrompt).toMatch(/send feedback/i);
	expect(submitted).toHaveLength(0);
	await expect(page.locator("#notes")).toHaveValue(
		"Confirm before sending this note.",
	);

	page.once("dialog", (dialog) => dialog.accept());
	await pressSequence(page, "Space", "s");
	await expect(page.locator("#status")).toHaveText("Feedback sent to Pi");
	expect(submitted).toHaveLength(1);
	await expect(page.locator("#notes")).toHaveValue("");

	let refreshPrompt = "";
	page.once("dialog", async (dialog) => {
		refreshPrompt = dialog.message();
		await dialog.dismiss();
	});
	await pressSequence(page, "Space", "f");
	expect(refreshPrompt).toMatch(/refresh/i);
	expect(refreshes).toBe(0);
	await expectCurrentLine(page, "alpha-long", "old", 10);

	page.once("dialog", (dialog) => dialog.accept());
	await page.getByRole("button", { name: "Refresh", exact: true }).click();
	await expect(page.locator("#source-label")).toContainText("keyboard-ref");
	expect(refreshes).toBe(1);
	await expectCurrentLine(page, "alpha-long", "old", 10);
	await expect(page.locator("#keyboard-status")).toContainText("line 10");

	let closePrompt = "";
	page.once("dialog", async (dialog) => {
		closePrompt = dialog.message();
		await dialog.dismiss();
	});
	await page.getByRole("button", { name: "Close", exact: true }).click();
	expect(closePrompt).toMatch(/close/i);
	expect(closes).toBe(0);
	await page.keyboard.press("j");
	await expectCurrentLine(page, "alpha-long", "new", 10);

	page.once("dialog", (dialog) => dialog.accept());
	await pressSequence(page, "Space", "q");
	await expect(page.locator("#status")).toContainText("Review closed");
	await expect.poll(() => closes).toBe(1);
});

test("blocks stale keyboard actions while busy and handles an empty review", async ({
	page,
}) => {
	let answer!: (value: string) => void;
	let askStarted!: () => void;
	const didStart = new Promise<void>((resolve) => {
		askStarted = resolve;
	});
	const saved: Draft[] = [];
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () =>
			new Promise<string>((resolve) => {
				answer = resolve;
				askStarted();
			}),
		save: (draft) => saved.push(structuredClone(draft)),
		submit: async () => {},
	});
	await page.goto(server.url);
	await pressSequence(page, "Space", "a");
	await page.locator("#question").fill("Hold the server busy");
	await page.locator("#question").press("Shift+Enter");
	await didStart;

	const waitingPage = await page.context().newPage();
	await waitingPage.goto(server.url);
	await expect(waitingPage.locator("#workspace")).toHaveAttribute(
		"aria-busy",
		"true",
	);
	await expectCurrentLine(waitingPage, "alpha-long", "old", 10);
	const savesWhileBusy = saved.length;
	await waitingPage.keyboard.press("j");
	await pressSequence(waitingPage, "]", "c");
	await pressSequence(waitingPage, "Space", "r");
	await expectCurrentLine(waitingPage, "alpha-long", "old", 10);
	expect(saved).toHaveLength(savesWhileBusy);
	await expect(waitingPage.locator("#keyboard-guide")).toBeHidden();
	answer("finished");
	await expect(page.locator("#status")).toHaveText("Answer received");
	await expect(waitingPage.locator("#workspace")).toHaveAttribute(
		"aria-busy",
		"false",
	);
	await waitingPage.close();
	server.close();

	server = await startReviewServer({
		review: makeEmptyReview(),
		isCurrent: async () => true,
		refresh: async () => makeEmptyReview(),
		ask: async () => "answer",
		save: (draft) => saved.push(structuredClone(draft)),
		submit: async () => {},
	});
	await page.goto(server.url);
	await expect(page.locator("#status")).toHaveText("Review ready");
	await expect(page.locator(".diff-line.keyboard-current")).toHaveCount(0);
	await expect(page.locator("#keyboard-status")).toContainText("NORMAL");
	await expect(page.locator("#keyboard-status")).toContainText("no lines");
	await expect(page.locator("#mark-layer-reviewed")).toBeDisabled();
	const savesBeforeEmptyActions = saved.length;
	await page.keyboard.press("j");
	await page.keyboard.press("G");
	await pressSequence(page, "]", "c");
	await pressSequence(page, "]", "u");
	await pressSequence(page, "Space", "r");
	await pressSequence(page, "Space", "c");
	expect(saved).toHaveLength(savesBeforeEmptyActions);
	await expect(page.locator("#comment-dialog")).not.toHaveAttribute("open", "");
});

test("keeps writes disabled when readiness polling exhausts", async ({
	page,
}) => {
	let answer!: (value: string) => void;
	let askStarted!: () => void;
	const didStart = new Promise<void>((resolve) => {
		askStarted = resolve;
	});
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () =>
			new Promise<string>((resolve) => {
				answer = resolve;
				askStarted();
			}),
		save: () => {},
		submit: async () => {},
	});
	await page.goto(server.url);
	await pressSequence(page, "Space", "a");
	await page.locator("#question").fill("Keep the server busy past retries");
	await page.locator("#question").press("Shift+Enter");
	await didStart;

	const waitingPage = await page.context().newPage();
	await waitingPage.goto(server.url);
	await waitingPage.waitForTimeout(4_300);
	await expect(waitingPage.locator("#workspace")).toHaveAttribute(
		"aria-busy",
		"true",
	);
	await expect(waitingPage.locator("#status")).toContainText(
		"Reload this page to retry",
	);
	await expect(
		waitingPage.getByRole("button", { name: "Refresh" }),
	).toBeDisabled();
	await expect(
		waitingPage.getByRole("button", { name: "Mark reviewed" }).first(),
	).toBeDisabled();
	await expect(
		waitingPage.getByRole("button", { name: "Close" }),
	).toBeEnabled();

	answer("finished");
	await expect(page.locator("#status")).toHaveText("Answer received");
	await waitingPage.reload();
	await expect(waitingPage.locator("#status")).toHaveText("Review ready");
	await expect(
		waitingPage.getByRole("button", { name: "Refresh" }),
	).toBeEnabled();
	await waitingPage.close();
});

test("help close cannot steal focus from a newer question", async ({
	page,
}) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview(),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.goto(server.url);
	await page.keyboard.press("?");
	await expect(page.locator("#keyboard-help")).toHaveAttribute("open", "");
	await closeKeyboardHelp(page);
	await pressSequence(page, "Space", "a");
	await expect(page.locator("#question")).toBeFocused();
	await page
		.locator("#keyboard-help")
		.evaluate((dialog) => dialog.dispatchEvent(new Event("close")));
	await expect(page.locator("#question")).toBeFocused();
	await expect(page.locator("#keyboard-status")).toContainText("TEXT");
	await expect(page.locator("#keyboard-return-hint")).toBeVisible();
});

test("renders focused normal, leader, help, and typing states responsively", async ({
	page,
}, testInfo) => {
	server = await startReviewServer({
		review: makeReview(),
		isCurrent: async () => true,
		refresh: async () => makeReview("keyboard-refreshed"),
		ask: async () => "answer",
		save: () => {},
		submit: async () => {},
	});
	await page.setViewportSize({ width: 1280, height: 800 });
	await page.emulateMedia({ colorScheme: "light" });
	await page.goto(server.url);
	const lightBackground = await page
		.locator("body")
		.evaluate((element) => getComputedStyle(element).backgroundColor);
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
	await expect(currentLine(page)).toBeVisible();
	await page.screenshot({
		path: testInfo.outputPath("keyboard-desktop-normal-light.png"),
	});

	await page.keyboard.press("Space");
	await expect(page.locator("#keyboard-guide")).toBeVisible();
	await page.screenshot({
		path: testInfo.outputPath("keyboard-desktop-leader-light.png"),
	});
	await page.keyboard.press("Escape");
	await page.keyboard.press("?");
	await expect(page.locator("#keyboard-help")).toHaveAttribute("open", "");
	await page.screenshot({
		path: testInfo.outputPath("keyboard-desktop-help-light.png"),
	});
	await closeKeyboardHelp(page);
	await currentLine(page).focus();
	await pressSequence(page, "Space", "a");
	await expect(page.locator("#question")).toBeFocused();
	await expect(page.locator("#keyboard-status")).toContainText("TEXT");
	await expect(page.locator("#keyboard-context")).toBeHidden();
	await expect(page.locator("#keyboard-return-hint")).toBeVisible();
	await expect(page.locator("#keyboard-return-hint")).toContainText(
		"return to diff",
	);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-desktop-typing-light.png"),
	});
	await page.keyboard.press("Escape");
	await expect(page.locator("#keyboard-return-hint")).toBeHidden();

	await page.emulateMedia({ colorScheme: "dark" });
	const darkBackground = await page
		.locator("body")
		.evaluate((element) => getComputedStyle(element).backgroundColor);
	expect(darkBackground).not.toBe(lightBackground);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-desktop-normal-dark.png"),
	});

	await page.setViewportSize({ width: 390, height: 760 });
	await expect(page.locator('[data-keyboard-pane="layers"]')).toBeHidden();
	await expect(page.locator('[data-keyboard-pane="drawer"]')).toBeHidden();
	await expect(
		page.locator('[data-keyboard-pane="diff"].keyboard-focused'),
	).toBeVisible();
	await expectNoViewportOverflow(page);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-mobile-normal-dark.png"),
	});

	await page.keyboard.press("Space");
	await expect(page.locator("#keyboard-guide")).toBeVisible();
	await expectNoViewportOverflow(page);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-mobile-leader-dark.png"),
	});
	await page.keyboard.press("Escape");
	await page.keyboard.press("?");
	await expect(page.locator("#keyboard-help")).toHaveAttribute("open", "");
	await expectNoViewportOverflow(page);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-mobile-help-dark.png"),
	});
	await closeKeyboardHelp(page);
	await pressSequence(page, "Space", "a");
	await expect(page.locator("#keyboard-status")).toContainText("TEXT");
	await expect(page.locator("#question")).toBeFocused();
	await expect(page.locator('[data-keyboard-pane="drawer"]')).toBeVisible();
	await expect(page.locator("#keyboard-context")).toBeHidden();
	await expect(page.locator("#keyboard-return-hint")).toBeVisible();
	await expectNoViewportOverflow(page);
	await page.screenshot({
		path: testInfo.outputPath("keyboard-mobile-typing-dark.png"),
	});
});