Kévin Sauvage.
Back to portfolio

Case study

ComplyLoop — Accessibility Compliance

A compliance tool built around one loop: find the problem, explain it, fix it, verify the fix and keep the proof.

Design & development · 2026

Next.js 16React 19TypeScriptPostgresDrizzle ORMAuth.jsGitHub App (Octokit)GitHub ActionsPlaywrightaxe-coreVercel AI SDKZodTailwind CSS 4VitestSentry
ComplyLoop landing page: "From RGAA requirement to verified code and audit evidence" above the six steps of the compliance loop
132

Accessibility checks

244

Unit-test files

10

E2E specs

RGAA 4 · WCAG 2.2

Standards

Overview

The problem & the approach

Most accessibility scanners stop at a list of findings. ComplyLoop is built around what happens next: each finding says what failed, where and why it matters, a fix is proposed as a draft pull request, and the finding is only closed once a new run confirms it.

It targets React, Next.js and TypeScript projects and the French RGAA 4 standard alongside WCAG 2.2. You connect a GitHub repository, it scans the source, and with a preview URL it also audits the rendered pages for what a source scan cannot see, such as contrast and reflow.

The product is in private preview: the landing page is public, while sign-in and the workspace stay behind a password because the workspace runs scans. The source is public to read.

What I owned

  • Defined the product scope: the loop from requirement to evidence, and who it is for

  • Designed the domain model, the module boundaries and the durable job architecture

  • Built the analysis engine, the GitHub App integration, the workspace UI and the evidence exports

  • Set up Postgres with Drizzle migrations, deployment on Vercel, CI and production checks

Product

What it does

Source scan

01

Custom AST checks together with eslint-plugin-jsx-a11y, over a shallow clone of the repository that is deleted when the job ends. Re-runs skip files that have not changed.

Runtime audit

02

With a preview URL, Playwright and axe audit the running pages for rules a source scan cannot decide, like colour contrast, landmarks and reflow.

Findings you can act on

03

Each finding gives what failed, why, where, the impact, a confidence level and which engine found it, so a developer can act without opening the standard.

Fixes as draft pull requests

04

Where a patch can be generated and verified, it is opened as a draft PR. Runtime findings get guidance for the call site instead of a generic attribute.

Verification and evidence

05

Only a re-run can mark a finding verified. Every assessment, fix and decision is appended to an evidence log that exports as JSON, Markdown or HTML.

Continuous monitoring

06

Pushes to the default branch trigger a new assessment through webhooks, using short-lived GitHub App tokens, so checks keep running with nobody signed in.

Organisations and roles

07

Owner, admin, member and viewer roles, invites by GitHub login, a personal organisation on first sign-in and an organisation switcher.

Engineering

How it is built

Deterministic analysis, advisory AI

Statuses come from checks, not from a model. AI can explain a finding or suggest a patch, but a patch has to pass the same checks before a PR is offered, and it never sets a requirement status.

One registry for every check

Each of the 132 checks is registered once, with its authority, the engines that can report it and the catalog control it backs. Coverage tests cross-check that registry against the AST checks, the rule maps and the catalog, so a check cannot ship without an engine or guidance.

Assessments as durable jobs

A run is queued in Postgres and picked up by a GitHub Actions worker, claimed with FOR UPDATE SKIP LOCKED. A 15-minute schedule picks up jobs whose dispatch failed or whose lease expired, so nothing needs a long-running server.

A single write path

Mutations go through write helpers that take a per-project lock and guard against stale writes. Server actions never touch the database directly, and ESLint enforces it.

Evidence that cannot be edited

The evidence log is append-only: a record can be superseded, never changed. GitHub tokens are encrypted at rest with AES-256-GCM.

Boundaries enforced by lint

The shared contract sits at the bottom, the app core imports only from it, and the AI module cannot import server code. ESLint fails the build when a layer reaches across.

Delivery

A per-request Content-Security-Policy with a nonce, a Basic-auth preview gate that fails closed, Sentry reporting through a tunnel route, and three GitHub workflows: CI, the assessment worker and a production configuration check.

Quality

Tested, typed and accessible

Quality & delivery
  • 244 Vitest files, with a coverage gate and a Postgres persistence integration suite

  • Playwright end-to-end specs behind a gated harness that is never enabled on customer deployments

  • Strict TypeScript, ESLint layer rules and a single verify command that runs lint, typecheck, tests, build and a bundle check

  • Coverage tests that fail when a check is missing an engine, a catalog row or guidance

Want to see it live?

The store is live: browse the catalog, switch language and add to cart. The source code is private, so I am happy to walk through it on a call.