Repository navigation
fix(public): render the signup rejection signal on the homepage #111
Changes from 29 commits
1aa08c5
2b24065
ea2e470
b98de30
6d09442
745e3f8
afbbee3
1eb7ced
b67e0af
e1a3609
41ad5c4
2aa7b9b
a0934f3
a35fc49
239c11d
334d811
4e52cbb
4184cac
8b2bcc7
8605a61
0c6f353
b07b208
aa0b80d
82d0471
95069d8
8dd42d1
92e4dba
214d962
b91d03e
5e4a84b
fbb3a31
0c57442
a6a5b12
562d708
139291e
2d0571e
4d4a764
d639aa3
d3cb29a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -71,6 +71,7 @@ | |
| <div class="orn"><i></i><span class="sc">The appraisal</span><b></b><i></i></div> | ||
| <h1>Most of them leave <em>before they ever get in touch.</em></h1> | ||
| <p class="sub">We read the one page your revenue depends on the way a customer with intent reads it, and show you the exact points at which they go.</p> | ||
| <p class="signal" id="signal-invalid" role="alert" tabindex="-1" hidden>We could not save that request — the email address did not pass. Check it and try again.</p> | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When JavaScript is disabled or Useful? React with 👍 / 👎. |
||
| <form class="lead two" id="start" action="/api/signups" method="post"> | ||
| <input type="text" name="website" required inputmode="url" autocomplete="url" placeholder="yourwebsite.com" aria-label="Your website domain" pattern="(https?://)?([a-zA-Z0-9\-]+\.)+[a-zA-Z]{2,}(:[0-9]+)?(/[^\s]*)?" title="Enter your domain, like example.com"> | ||
| <input type="email" name="email" required placeholder="Your work email" aria-label="Your work email"> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,3 +17,21 @@ | |
| } | ||
| document.readyState==='loading'?document.addEventListener('DOMContentLoaded',run):run(); | ||
| })(); | ||
|
|
||
| // Signup rejection signal: the worker 303-redirects failed signups back to | ||
| // /?signal=invalid (the homepage form and the /audit form both post to | ||
| // /api/signups). No page code used to read that signal, so a visitor whose | ||
| // email the server rejected (the server regex is stricter than the browser's | ||
| // type=email check — e.g. "a@b" passes client-side but not server-side) was | ||
| // silently bounced to the homepage with no explanation. Reveal the banner, | ||
| // move focus to it for assistive tech, then strip the query so a refresh | ||
| // (or a copied link) does not re-show the error. | ||
| (function(){ | ||
| var match=(location.search||'').match(/[?&]signal=([^&]+)/); | ||
| if(!match)return; | ||
|
Comment on lines
+30
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The handler checks only that a nonempty Useful? React with 👍 / 👎. |
||
| var banner=document.getElementById('signal-invalid'); | ||
| if(!banner)return; | ||
| banner.hidden=false; | ||
| banner.focus(); | ||
| if(history.replaceState)history.replaceState(null,'',location.pathname+location.hash); | ||
| })(); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -293,6 +293,46 @@ for (const [pageName, pageHtml] of [["homepage", siteHome], ["audit page", siteA | |
| } | ||
| } | ||
|
|
||
| // Signup rejection-signal guard (dogfood: failed-signup dead end). The worker | ||
| // 303-redirects a rejected signup back to /?signal=invalid (the server email | ||
| // regex is stricter than the browser's type=email check — e.g. "a@b" passes | ||
| // client-side but not server-side), so the homepage must render that state: | ||
| // a role=alert banner, hidden until index.js reads the signal, moves focus | ||
| // into it, and strips the query so a refresh or a copied link does not | ||
| // re-show the error. These are STATIC SOURCE GUARDS, not behavioral tests. | ||
| const signalBanner = siteHome.match(/<p\b[^>]*id="signal-invalid"[^>]*>/i)?.[0] || ""; | ||
| if (!signalBanner) { | ||
| failures.push("Homepage must carry the id=\"signal-invalid\" signup rejection banner."); | ||
| } else { | ||
| if (!/\brole="alert"/i.test(signalBanner)) { | ||
| failures.push("Signup rejection banner must expose role=\"alert\"."); | ||
| } | ||
| if (!/\bhidden(?:\s|>|=)/i.test(signalBanner)) { | ||
| failures.push("Signup rejection banner must start hidden (revealed only by the signal handler)."); | ||
| } | ||
| } | ||
| const homeScript = read("public/index.js"); | ||
| if (!homeScript.includes("signal-invalid")) { | ||
| failures.push("index.js must reference the signal-invalid banner."); | ||
| } | ||
| if (!/signal=([^&]+)/.test(homeScript)) { | ||
| failures.push("index.js must read the ?signal= query parameter."); | ||
|
Comment on lines
+315
to
+319
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These checks never verify that the banner is revealed or focused, so a regression such as changing Useful? React with 👍 / 👎. |
||
| } | ||
| if (!homeScript.includes("replaceState")) { | ||
| failures.push("index.js must strip the signal query with history.replaceState after revealing the banner."); | ||
| } | ||
| const homeCss = read("public/index.css"); | ||
| if (!/\.signal\b/.test(homeCss)) { | ||
| failures.push("index.css must style the signup rejection banner (.signal)."); | ||
| } | ||
| if (!/\.signal\[hidden\]\s*\{[^}]*display:\s*none/i.test(homeCss)) { | ||
| failures.push(".signal[hidden] must force display:none so the banner stays hidden until revealed."); | ||
| } | ||
| if (!worker.includes("htmlRedirect(url, \"invalid\")")) { | ||
| failures.push("Worker must keep 303-redirecting rejected signups to /?signal=invalid (the homepage banner renders it)."); | ||
| } | ||
|
|
||
|
|
||
| if (!retiredDesk.includes("role=\"tabpanel\"") || !retiredDesk.includes("aria-labelledby=\"output-tab-pipelineBrief\"")) { | ||
| failures.push("Agent output must expose a proper tabpanel relationship."); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a sighted keyboard user submits an email rejected by the server,
index.jsmoves focus to this banner, but this rule removes the only focus indicator without providing an alternative. The user therefore cannot visually determine where focus landed after the redirect; keep the outline or replace it with an equally visible:focustreatment.Useful? React with 👍 / 👎.