Skip to content

carshake.online: accessible names for 118 pages of email fields - #3

Open
kindrat86 wants to merge 1 commit into
mainfrom
dom-hardening/carshake
Open

carshake.online: accessible names for 118 pages of email fields#3
kindrat86 wants to merge 1 commit into
mainfrom
dom-hardening/carshake

Conversation

@kindrat86

Copy link
Copy Markdown
Owner

Sixth site in the portfolio DOM and agent-readability pass. Do not merge without review.

What this fixes

118 pages carried an email input whose only description was a placeholder. A placeholder is not a label — it disappears the moment the field has a value, and it is not reliably exposed as an accessible name. Screen readers announced a required unnamed edit box; agents had nothing to key on.

Two form shapes:

  • Site-wide capture form — already had name="email" and a correct action="/api/email-capture" method="post" pair. Needed only aria-label and autocomplete.
  • Lead-magnet form (class="cs-email-input") — additionally had no name at all. Its action is javascript:void(0) so adding a name cannot leak over GET, and its handler calls preventDefault().

The honeypot on the site-wide form is already correctly marked (aria-hidden="true" + tabindex="-1" + offscreen) and was left alone.

Localised pages get their own language

/fr, /es, /pt and /id get the phrase in their own language, matched against each page's <html lang>. An English aria-label on a Portuguese page is noise, not a label.

Measured

check before (production) after
E3 field with no label 6 0
E4 no autocomplete 6 0
E1 no name 2 0
E10 no live region 2 0
E5 no aria-describedby 6 4

⚠️ Verification caveat — read this

This site's JS behaves differently off its production origin, so a local rendered-side audit is not representative here. Locally /blog and /city/new-york render less content than they do in production (ratio >1), and /pricing renders more. I do not have an explanation for that and did not chase it, but it means local rendered numbers for this site should not be trusted.

What is trustworthy: the no-JS side matches production almost exactly (/pricing 1142 vs 1142 chars, / 8206 vs 8235), and that is where these attributes live. The fix is confirmed present in the served documents. Please re-audit production after deploy rather than relying on the local numbers.

That divergence is itself worth someone looking at — it may be a real defect.

Two things this repo taught the tooling

It contains another agent's git worktree under .claude/worktrees/, which is git-excluded — so edits made there never appear in git status. The first pass silently modified 114 files on someone else's branch. Those were reverted, and the fix scripts now refuse to walk into .claude, .vercel, dist, .next and node_modules.

.vercel/output/ is a gitignored prebuilt artifact that had been partly touched. It was made internally consistent rather than left half-updated, but it should be regenerated before any vercel deploy --prebuilt.

Deliberately NOT changed

  • action="javascript:void(0)" on the lead-magnet form — a CSP hazard and meaningless as an action, but changing it risks a real navigation if any handler does not preventDefault. Worth a separate look.
  • <main>/<header> missing on /free/instant-proof (4 findings) — template change.
  • An <a> with no href on /free/instant-proof (C1).
  • axe color-contrast and region (12 findings).

🤖 Generated with Claude Code

118 pages carried an email input whose only description was a placeholder. A
placeholder is not a label: it disappears the moment the field has a value, and
it is not reliably exposed as an accessible name. Screen readers announced a
required unnamed edit box; agents had nothing to key on.

Two form shapes were in play. The site-wide capture form already had a name and
a correct action/method pair and needed only aria-label and autocomplete. The
lead-magnet form (class cs-email-input) additionally had no name at all; its
action is javascript:void(0), so adding a name cannot leak over GET, and its
handler calls preventDefault.

The four localised pages get the phrase in their own language rather than an
English one — an English aria-label on a Portuguese page is noise, not a label.
Matched against each page's own <html lang>.

Two things this repo taught the tooling. It contains another agent's git
worktree under .claude/worktrees/, which is git-excluded, so edits made there
never appear in git status — the first pass silently modified 114 files on
someone else's branch. Those were reverted and the fix scripts now refuse to
walk into .claude, .vercel, dist, .next and node_modules.

.vercel/output/ is a gitignored prebuilt artifact that had been partly touched;
it was made internally consistent, but it should be regenerated before any
`vercel deploy --prebuilt`.

Verification note: this site's JS behaves differently off its production origin,
so a local rendered-side audit is not representative here — /blog and
/city/new-york render LESS content locally than in production. The no-JS side
does match production almost exactly, and that is where these attributes live,
so the fix is confirmed in the served documents rather than by a local rendered
diff. Re-audit production after deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kindrat86

Copy link
Copy Markdown
Owner Author

Correction to this PR's description, and an incident to record.

The "JS behaves differently off the production origin" claim was wrong

The PR says local and production audits disagreed because this site's JS behaves differently off its production origin. That was the wrong explanation.

The ratios varied between runs of the same production URL — 0.476, 1.000 and 2.583 for /city/new-york on three occasions — while being perfectly steady within each run. vercel ls --prod shows this project deploying to production every ~20 minutes. Production content was genuinely changing between runs; the measurement was fine.

The auditor now carries a check (A4) that loads each page twice and reports when the two disagree by more than 5%, taking the larger sample so an under-measured render cannot inflate serverTextRatio and hide a real failure.

An edit of mine was briefly live

While making .vercel/output/static/ internally consistent rather than half-updated, I edited the gitignored prebuilt artifact. git checkout main does not revert a gitignored directory, so the next automated prebuilt deploy shipped it. It was live for roughly 13 minutes before a later deploy regenerated the artifact from main.

Current state is correct — production, .vercel/output/ and main all match, and this PR's changes exist only on this branch. Verified by md5 across six consecutive requests plus a direct check of the artifact.

The general rule stands and is now in the portfolio notes: on any site with a .vercel/output/, returning the git branch is not sufficient. Leave that directory alone, or regenerate it before walking away.

Still true

Everything in the PR body about the actual fix — 118 pages with placeholder-only email fields, the localised aria-labels, the .claude/worktrees/ incident — is unchanged. The measured before/after for the form findings stands, since those are read from the served documents rather than the rendered snapshot.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants