diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5043b36..7d8cd10 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,5 +30,5 @@ jobs:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- - run: pnpm exec playwright install --with-deps chromium
+ - run: pnpm exec playwright install --with-deps chromium webkit
- run: pnpm test:a11y
diff --git a/README.md b/README.md
index 975cad4..e3cc5a0 100644
--- a/README.md
+++ b/README.md
@@ -7,6 +7,10 @@ See participation, completion, scores, and where learners stopped, on your own C

+The same page in dark mode.
+
+
+
## How it works
1. Deploy your instance (one click below, or CLI).
@@ -75,6 +79,11 @@ Anonymous pseudonymous identity is the default. Operators can configure notice o
Read keys are separate from ingest keys, so pages can write results without being able to read them back. The read API provides JSON summaries and paste-ready markdown reports; `llms.txt` documents those endpoints for AI builders, and [docs/api.md](docs/api.md) is the reference.
+The dashboard follows your operating system's light or dark appearance by
+default. A Theme control in the header pins it to Light or Dark instead, or back
+to Auto. The choice is stored per device and per browser, not per account, so it
+never travels with your data and never leaves the browser.
+
## Deploy
**Recommended:** use the Deploy to Cloudflare button. See [docs/deploy.md](docs/deploy.md) for one-click, CLI, EU jurisdiction, and the current limits of regional placement. Production instances must set the `ADMIN_PASSWORD` secret:
diff --git a/docs/assets/dashboard-dark.png b/docs/assets/dashboard-dark.png
new file mode 100644
index 0000000..394bdfd
Binary files /dev/null and b/docs/assets/dashboard-dark.png differ
diff --git a/docs/assets/dashboard.png b/docs/assets/dashboard.png
index 0ba50af..d3f1d6a 100644
Binary files a/docs/assets/dashboard.png and b/docs/assets/dashboard.png differ
diff --git a/src/about.tsx b/src/about.tsx
index 97a4895..40dbe16 100644
--- a/src/about.tsx
+++ b/src/about.tsx
@@ -6,6 +6,7 @@ export function About() {
+
About Proof
diff --git a/src/dashboard/routes.tsx b/src/dashboard/routes.tsx
index bae5e2f..e96a36c 100644
--- a/src/dashboard/routes.tsx
+++ b/src/dashboard/routes.tsx
@@ -66,6 +66,14 @@ const VERB_LABELS: Record = {
};
const verbLabel = (iri: string) => VERB_LABELS[iri] ?? iri.split("/").pop() ?? iri;
+/** A table cell holding only an em dash reads as an empty cell to a screen
+ reader. Keep the glyph for sighted scanning, say the word to assistive tech. */
+function orNone(value: string) {
+ return value === "\u2014"
+ ? <>{"\u2014"}None>
+ : value;
+}
+
const Q_IRI_RE = /\/q\/([^/]+)$/;
function timelineDetail(row: TimelineRow): string {
if (row.step) return row.step;
@@ -134,17 +142,17 @@ function FunnelSection(props: {
: "—";
return (
+ {operator
+ ? `${operator} uses Praxity Proof to record participation and results from some learning activities it provides.`
+ : "This Praxity Proof instance records participation and results from some learning activities."}
+
+
+
+
+
What may be recorded
+
Your learner identifier, activity progress, answers, completion, scores, and duration. The exact information depends on the activity.
+
+
+
How long it is kept
+
Learning statements on this instance are retained for {String(props.settings.retentionDays)} days, then deleted.
+
+
+
+
+
Questions about your data?
+
{props.settings.privacyContact
+ ? <>Contact {operator || "the activity operator"} at {props.settings.privacyContact}.>
+ : "Ask the activity operator how to make a privacy request."}