Skip to content

Separating Audit from Remediate

Problem

The audit and remediate apps are conflated in a way that breaks down for the most common case — a URL scan of a site we don’t control.

  • We can’t fix someone else’s live site. For a URL job, remediate lets the user edit a captured copy of their HTML (storeJobHtml → our KV). Those edits never reach the user’s site, so “fixing” is theater.
  • The re-audit re-fetches the live URL, never the edited copy (handleReauditfetchUrl(data.url)). So in-tool edits can’t move the grade; the loop only works if the user fixes their real site out-of-band.
  • The wizard mixes two verbs. “Mark as reviewed” (alt text, tab order, headings) is evaluation — the raw material for an accurate ACR. “Fix link text / table headers” tools are copy-editing — meaningful only when we own the artifact. Both live in one wizard, applied to URL jobs where the second verb doesn’t apply.

The user-facing symptom: it’s unclear whether the wizard is helping you judge your site’s conformance or fix it, and for a URL it appears to do the latter while actually doing neither.

The real fault line: do we own the output artifact?

The correct distinction is not “audit vs remediate” but job origin:

OriginWe own the HTML?Right verbDeliverable
PDF → HTML (apps/web converter)Yes — we generate itRemediate (edit)Downloadable fixed HTML
URL scanNo — it’s their live siteEvaluate + guideAccurate ACR + dev punch-list / CMS guidance

The editing tools aren’t wasted — they’re correct for the PDF-origin path. The bug is that the URL path reuses them where they don’t apply.

Target model

1. Audit = evaluate → grade → ACR (standalone, complete)

A URL/PDF scan yields the ~40% machine-testable criteria automatically. The guided “wizards” belong here, reframed as manual conformance evaluation of the criteria a scanner can’t judge (meaningful alt text, logical reading order, heading logic, link purpose in context, meaningful sequence).

  • Each guided step records a per-criterion verdict (Supports / Partially Supports / Does Not Support / Not Applicable), not “looks good.”
  • At the end: grade the VPAT and produce an accurate ACR (VPAT 2.4), reflecting automated results plus the human verdicts — not the automated-only draft we ship today.
  • Then, and only then: “Want help fixing these? → Remediate.”

2. Remediate = the optional next step, branched by origin

  • We own it (PDF→HTML): real editing tools, download the fixed file, re-validate the edited HTML (not a live re-fetch). Fully coherent today — keep as-is.
  • They own it (URL): we do not edit their code. We produce a developer punch-list and CMS-specific fix guidance (both already exist in the wizard: fetchPunchList, fixableInCms, devHandoffNote, CMS detection). The user fixes their live site; a re-audit (back in audit) confirms.

Net effect on the flow: the grade + ACR move earlier (into audit), and remediate becomes the “help me fix this” step — matching “after we grade the VPAT, offer to remediate.”

What already exists (this is mostly relocation, not new build)

  • Grading engine: @accessible-pdf/acr-grading (gradeWebAcr) — shared, already consumed by remediate’s wizard and dashboard.
  • Draft ACR export: apps/audit AcrExportDialog + downloadVpatAcr (VPAT 2.4 renderer in workers/api audit-report-renderer.ts).
  • Audit already grades: apps/audit/src/app/history stores grade / percentage per run.
  • Guided evaluation steps: wizard GUIDED_KEYS (alt text, tab order, headings, links, tables, read-aloud) with server-persisted progress (wizard_progress).
  • Dev-handoff pieces: fetchPunchList, per-group fixableInCms + devHandoffNote, CMS detection.
  • Job-origin signal: type: 'url' | 'file' (dashboard), jobKind: 'url' | 'pdf' (alt decisions).

The wizard already has all the right parts. The work is splitting them by verb and moving the evaluation/grade/ACR half into audit.

Concrete changes

Phase 1 — Reframe the wizard’s guided steps as evaluation (no move yet)

Low-risk, ships value immediately, de-risks the bigger move.

  • Change guided-step controls from “Mark as reviewed” (boolean) to a conformance verdict per WCAG criterion (Supports / Partially / Does Not Support / N/A). Persist the verdict, not just a done-flag, in wizard_progress (add a verdict column; keep row-presence semantics).
  • Feed those verdicts into gradeWebAcr so the grade reflects human judgment, not automated-only results.
  • For URL jobs, hide the copy-editing tools (TableHeaderBrowser, LinkRewriterBrowser, alt writing) and keep only evaluation + punch-list. Gate on job origin, mirroring the existing element-presence gating.

Phase 2 — Move grade + ACR to the end of the audit flow

  • Render the grade card + “Download ACR” in apps/audit at the end of a scan (reuse gradeWebAcr + AcrExportDialog; both already imported there or trivially addable).
  • Audit’s terminal CTA becomes “Prepare ACR” (guided evaluation, in audit) and then “Remediate” (hand off to remediate).
  • The audit→remediate handoff link stays (/audit/wizard/?id={jobId} via the shared url-fetch:{jobId} KV), but now it’s an explicit fix step reached after grading — not the place grading happens.

Phase 3 — Remediate branches hard by origin

  • URL job: remediate shows guidance only — punch-list, CMS-specific instructions, “here’s how to fix each in {detected CMS}.” No copy editor. No live-re-fetch loop framed as “fixing.”
  • PDF/file job: remediate keeps the full editor + download + re-validate the edited HTML.
  • Remove the misleading URL re-audit loop from remediate (re-audit lives in audit, where it belongs).

Phase 4 — Cleanup / naming

  • Wizard route /audit/wizard → split intent: an evaluation view (audit) and a fix view (remediate). Consider renaming to reduce the cross-app /audit/* path inside remediate.
  • Update CLAUDE.md product positioning: audit = evaluate/grade/ACR; TheAccessibleWeb (remediate) = the fix step, artifact-aware.

Open questions

  1. PDF audit → ACR: PDFs grade against PDF/UA. Do we map PDF/UA → WCAG-conformance for a unified ACR, or keep a separate PDF ACR track? (Noted in earlier handoff work; still open.)
  2. wizard_progress schema: add a verdict column vs. a new criterion_evaluations table. Verdict-per-criterion is richer than the current row-presence model; migration required either way.
  3. Where does the re-audit live once grading moves to audit — a button in audit’s result view that re-runs the scan and re-grades?
  4. Auth-gated re-audit: the audit-session token flow (already built) is what lets a URL evaluation reach a login-gated page; wire it into audit’s scan entry, not remediate’s.

Non-goals

  • Rewriting the grading engine (it’s already shared and correct).
  • Changing the PDF→HTML remediation path (it’s the one coherent remediate flow).
  • Building CMS write-back (pushing fixes into a user’s CMS) — out of scope; we generate guidance, the user applies it.