diff --git a/apps/web/src/components/AttackDemo.tsx b/apps/web/src/components/AttackDemo.tsx index 6c9d189..719ac5b 100644 --- a/apps/web/src/components/AttackDemo.tsx +++ b/apps/web/src/components/AttackDemo.tsx @@ -5,11 +5,12 @@ import type { AttackStep } from "../lib/demoTypes"; import { useToast } from "../ui/Toast"; export function StepList({ steps, variant }: { steps: AttackStep[]; variant: "bad" | "good" }) { + const listLabel = variant === "bad" ? "Seal-off attack steps" : "Seal-on protection steps"; return ( -
{s.detail}
@@ -81,7 +82,7 @@ export function AttackDemo({ phase = "Reveal" }: { phase?: "Open" | "Reveal" | " const revealOpen = phase !== "Open"; return ( -Main stage
@@ -94,24 +95,30 @@ export function AttackDemo({ phase = "Reveal" }: { phase?: "Open" | "Reveal" | "Bid stored in reversible form. Observer reads early. Losing bidder can abort.
Undecryptable until R. Permissionless mass reveal. Commitment binding on-chain.
diff --git a/apps/web/src/components/RoundStatusBadge.tsx b/apps/web/src/components/RoundStatusBadge.tsx
index aaf3f81..244bc30 100644
--- a/apps/web/src/components/RoundStatusBadge.tsx
+++ b/apps/web/src/components/RoundStatusBadge.tsx
@@ -25,7 +25,12 @@ export function RoundStatusBadge({
onRetry,
}: RoundStatusBadgeProps) {
return (
-
+
{state === "loading" ? (
) : null}
diff --git a/apps/web/src/demo/demo-a11y-smoke.test.tsx b/apps/web/src/demo/demo-a11y-smoke.test.tsx
new file mode 100644
index 0000000..2d39caa
--- /dev/null
+++ b/apps/web/src/demo/demo-a11y-smoke.test.tsx
@@ -0,0 +1,111 @@
+// Copyright (c) 2026 Sub Rosa contributors
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { dirname, resolve } from "node:path";
+import test from "node:test";
+import { fileURLToPath } from "node:url";
+import { renderToStaticMarkup } from "react-dom/server";
+import { createFakeTime } from "@sub-rosa/time";
+
+import { AttackDemo, SealOffColumn, StepList } from "../components/AttackDemo";
+import { RoundStatusBadge } from "../components/RoundStatusBadge";
+import type { AttackStep } from "../lib/demoTypes";
+import { TimeProvider } from "../lib/time";
+import { ToastProvider } from "../ui/Toast";
+
+const DEMO_DIR = dirname(fileURLToPath(import.meta.url));
+
+function readWebSource(relPath: string): string {
+ return readFileSync(resolve(DEMO_DIR, "..", relPath), "utf8");
+}
+
+const SAMPLE_STEPS: AttackStep[] = [
+ { label: "Bidder A commits", detail: "Commitment hash H recorded on-chain", ok: true },
+ { label: "Observer inspects before R", detail: "Plaintext bid leaked early", ok: false },
+];
+
+test("demo default state exposes landmarks, region roles, headings, and focusable controls", () => {
+ const html = renderToStaticMarkup(
+ Seal-off vs seal-on attack<\/h2>/);
+ assert.match(html, /