From 899d5bbe1abc61a9ccdbdada9ad818e29bfccd1c Mon Sep 17 00:00:00 2001 From: Maris Popens Date: Sat, 17 Oct 2026 17:52:43 +0300 Subject: [PATCH 1/3] fix(a11y): fix contrast, labels and heading order, and scan both UIs in CI --- .github/dependabot.yml | 10 + .github/workflows/ci.yml | 28 ++- docs/code-patterns.md | 1 + internal/web/static/css/tokens.css | 14 +- internal/web/static/css/utilities.css | 5 +- internal/web/templates/brokers.html | 3 + internal/web/templates/history.html | 2 +- internal/web/templates/layout.html | 12 +- internal/web/templates/tasks.html | 20 +- scripts/a11y/.gitignore | 1 + scripts/a11y/package-lock.json | 286 ++++++++++++++++++++++++++ scripts/a11y/package.json | 10 + scripts/a11y/run.sh | 25 +++ scripts/a11y/scan.mjs | 33 +++ scripts/a11y/seed/main.go | 84 ++++++++ site/layouts/brokers/list.html | 4 +- site/static/css/site.css | 8 +- 17 files changed, 518 insertions(+), 28 deletions(-) create mode 100644 scripts/a11y/.gitignore create mode 100644 scripts/a11y/package-lock.json create mode 100644 scripts/a11y/package.json create mode 100755 scripts/a11y/run.sh create mode 100644 scripts/a11y/scan.mjs create mode 100644 scripts/a11y/seed/main.go diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 30eaa6a..8967a8a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,3 +25,13 @@ updates: groups: actions: patterns: ["*"] + + - package-ecosystem: "npm" + cooldown: + default-days: 7 + directory: "/scripts/a11y" + schedule: + interval: "weekly" + groups: + a11y: + patterns: ["*"] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e734c1a..c9bf2c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,6 +79,32 @@ jobs: - name: Build the site run: ./site/build.sh + # axe-core (WCAG 2.2 A/AA, light and dark) over the website and the web UI + # running against fictional seed data. See scripts/a11y/run.sh. + a11y: + name: Accessibility + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + fetch-depth: 0 # the versioned docs read every release tag + + - uses: actions/setup-go@v7 + with: + go-version-file: go.mod + + - uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1 + with: + hugo-version: "0.165.0" + + - uses: actions/setup-node@v7 + with: + node-version: "24" + + - run: scripts/a11y/run.sh + # The shared go-ci job covers Linux amd64. Release binaries also ship for # macOS, Windows and Linux arm64, so run the tests there too (no -race: # that needs cgo, and the release builds don't use it). @@ -134,7 +160,7 @@ jobs: required-checks-passed: name: Required checks passed runs-on: ubuntu-latest - needs: [ci, zizmor, coverage, site, cross-platform, vulncheck] + needs: [ci, zizmor, coverage, site, a11y, cross-platform, vulncheck] if: always() steps: - if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') diff --git a/docs/code-patterns.md b/docs/code-patterns.md index 1c21ad6..408d1d6 100644 --- a/docs/code-patterns.md +++ b/docs/code-patterns.md @@ -10,6 +10,7 @@ - **History startup**: CLI commands use `openHistoryStore`, which opens the schema and runs the versioned, transactional saved-reply migration via `inbox.MigrateNoDataResponses`. Keep new entry points on this helper so upgrades do not require visiting the web UI. The migration is local-only and retryable after a rollback. - **Pipeline UI**: counts and pending rows share `history.GetActionItems`; use it for Pipeline and Action Needed together. The Pipeline URL carries view, status, response type, search, review, and page filters. Response search filters the active profile in SQLite before pagination, including stored email bodies. - **Multi-profile scoping**: any new `history.Store` method that reads/writes per-user data should take a `profileID string` first parameter and filter by it (see [multi-profile.md](multi-profile.md)) - the two deliberate exceptions are `ClearBrokerResponses`/`GetAllBrokerResponses`, which operate on the one shared inbox across all profiles. +- **Accessibility**: CI runs axe-core (WCAG 2.2 A/AA, light and dark) over the website and the web UI against fictional seed data: `scripts/a11y/run.sh` runs the same thing locally (needs Go, Hugo, Node and Chrome). Add a new web UI page to the URL list in that script. Rules of thumb it enforces: every `Data Brokers - - + +

Loading…

diff --git a/site/static/css/site.css b/site/static/css/site.css index 38da9ea..8140849 100644 --- a/site/static/css/site.css +++ b/site/static/css/site.css @@ -41,8 +41,10 @@ body { color: var(--ink); font: 16px/1.65 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; } -a { color: var(--accent); text-decoration: none; } -a:hover { color: var(--accent-hover); text-decoration: underline; } +a { color: var(--accent); text-decoration: underline; text-underline-offset: .15em; } +a:hover { color: var(--accent-hover); text-decoration-thickness: 2px; } +.brand, .topbar nav a, .btn { text-decoration: none; } +.topbar nav a:hover, .btn:hover { text-decoration: underline; } .topbar { display: flex; align-items: baseline; gap: 1.5rem; @@ -65,7 +67,7 @@ a:hover { color: var(--accent-hover); text-decoration: underline; } display: inline-block; padding: .6rem 1.1rem; border-radius: 8px; background: var(--accent); color: var(--on-accent); font-weight: 600; } -.btn:hover { background: var(--accent-hover); color: var(--on-accent); text-decoration: none; } +.btn:hover { background: var(--accent-hover); color: var(--on-accent); } .btn.ghost { background: transparent; color: var(--accent); border: 1px solid var(--border); } .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 2.5rem; margin-top: 3.5rem; } From f0c5b0bc981ebec32d7287e22fcf223691f6ea4e Mon Sep 17 00:00:00 2001 From: Maris Popens Date: Sat, 17 Oct 2026 17:53:45 +0300 Subject: [PATCH 2/3] style(a11y): gofmt the seed script --- scripts/a11y/seed/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/a11y/seed/main.go b/scripts/a11y/seed/main.go index b25c1fa..e187860 100644 --- a/scripts/a11y/seed/main.go +++ b/scripts/a11y/seed/main.go @@ -73,7 +73,7 @@ func main() { TaskType: t, FormURL: "https://privacy.example-data.test/optout", Status: "pending", ReviewReason: "reply is ambiguous"})) } must(s.CreateBatchApproval(history.BatchApproval{PlanID: "b7c0f3e91d2a4c58", ProfileID: "default", Digest: "d", - Preview: `{"profile_fields":{"first_name":"Jane"},"forms":[{"broker":"Example Data Co","url":"https://privacy.example-data.test/optout"}],"emails":[]}`, + Preview: `{"profile_fields":{"first_name":"Jane"},"forms":[{"broker":"Example Data Co","url":"https://privacy.example-data.test/optout"}],"emails":[]}`, ExpiresAt: now.Add(15 * time.Minute)})) } From 9aea9b4e3e713755edca9ab9f0795a5a58084fdf Mon Sep 17 00:00:00 2001 From: Maris Popens Date: Sat, 17 Oct 2026 18:00:55 +0300 Subject: [PATCH 3/3] fix(a11y): darken error and warning text tokens for the CI-found contrast failures --- internal/web/static/css/tokens.css | 6 +++--- internal/web/templates/layout.html | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/web/static/css/tokens.css b/internal/web/static/css/tokens.css index 5b7fd62..75d14a5 100644 --- a/internal/web/static/css/tokens.css +++ b/internal/web/static/css/tokens.css @@ -23,7 +23,7 @@ --success: #22c55e; --warning: #eab308; - --error: #ef4444; + --error: #f87171; --text-primary: #e4e4e7; --text-secondary: #a1a1aa; @@ -53,8 +53,8 @@ --accent-glow: rgba(79, 70, 229, 0.25); --success: #16a34a; - --warning: #b45309; - --error: #dc2626; + --warning: #92400e; + --error: #b91c1c; --text-primary: #18181b; --text-secondary: #52525b; diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index 028dd27..a7ff69a 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -189,7 +189,7 @@ .btn-danger { background: rgba(239, 68, 68, 0.12); - color: #f87171; + color: var(--error); border: 1px solid rgba(239, 68, 68, 0.3); } @@ -268,9 +268,6 @@ } .light .badge-success { color: #166534; } - .light .badge-warning { color: #92400e; } - .light .badge-error { color: #b91c1c; } - .light .btn-danger { color: #b91c1c; } .text-muted a { text-decoration: underline; } .badge-neutral {