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
(
handleReaudit→fetchUrl(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:
| Origin | We own the HTML? | Right verb | Deliverable |
|---|---|---|---|
PDF → HTML (apps/web converter) | Yes — we generate it | Remediate (edit) | Downloadable fixed HTML |
| URL scan | No — it’s their live site | Evaluate + guide | Accurate 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/auditAcrExportDialog+downloadVpatAcr(VPAT 2.4 renderer inworkers/apiaudit-report-renderer.ts). - Audit already grades:
apps/audit/src/app/historystoresgrade/percentageper 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-groupfixableInCms+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 averdictcolumn; keep row-presence semantics). - Feed those verdicts into
gradeWebAcrso 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/auditat the end of a scan (reusegradeWebAcr+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 sharedurl-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.mdproduct positioning: audit = evaluate/grade/ACR; TheAccessibleWeb (remediate) = the fix step, artifact-aware.
Open questions
- 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.)
wizard_progressschema: add averdictcolumn vs. a newcriterion_evaluationstable. Verdict-per-criterion is richer than the current row-presence model; migration required either way.- 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?
- 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.