ContactForm relay wiring (recipient, thank-you, honeypot, marker) #2
Reference in New Issue
Block a user
Delete Branch "contact-form-relay"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
ContactForm relay wiring
Wires the site-builder Contact Form to the form-sender relay (companion PR: whp
form-sender-relay).recipientEmail+thankYouUrlsettings;toHtmlemits a<!--WHP-FORM …-->marker (stripped at publish, so the recipient never ships),method=POST action=__WHP_FORM_ACTION__…__placeholder, and a hidden_gotchahoneypot — only when a recipient is set.ContactForm.toHtml.test.ts.🤖 Generated with Claude Code
Review: ContactForm relay wiring (
contact-form-relay→main)Verdict: Approve, with one non-blocking nit.
Verified locally:
npx vitest run src/components/forms/ContactForm.toHtml.test.ts→ 2/2 pass;tsc --noEmitclean.What checks out
ContactForm.tsx:439-446):fidis generated once pertoHtml()call and reused for both the marker'sid="..."and the__WHP_FORM_ACTION__<fid>__placeholder — they can't drift apart within a single render.recipientEmail/thankYouUrlare passed throughesc()(line 396) before being interpolated into the<!--WHP-FORM ...-->marker. Sinceesc()escapes"→"and>→>, a value containing"can't break out of the pseudo-attribute, and one containing-->can't prematurely close the HTML comment (the>is neutralized). Confirmed by hand-tracingesc()at line 396.type="text"(nottype="hidden", which spam bots often skip on purpose) +position:absolute;left:-9999px+tabindex="-1"+aria-hidden="true"+autocomplete="off", named_gotcha(matches the common Formspree-style convention). It's emitted beforefieldsHtml, so it's the first child inside<form>, and being a real<input>it's submitted normally.recipientEmail,marker/honeypotare both'', andactionAttrfalls back to the pre-existingesc(props.formAction || '#'). For the realistic case —fieldsnon-empty (i.e.defaultFields, or any user-populated field list) — I byte-diffed the old (d0925d9) template against the new one and they produce identical output.Math.random().toString(36).slice(2,8)-style id generation matches the existing project convention (same pattern used inCountdown.tsx,Tabs.tsx,Gallery.tsx,NumberCounter.tsx,Menu.tsx,ContentSlider.tsx), so no new pattern introduced.action="__WHP_FORM_ACTION__<id>__",_gotchapresence, id-match between marker and action, and marker/gotcha absence in the non-relay path. All pass.Finding (non-blocking nit)
ContactForm.tsx:450— the "byte-identical" guard doesn't actually hold for emptyfields, and the new regression test only exercises that non-representative case.Both
ContactForm.toHtml.test.tscases usefields: []. With empty fields and norecipientEmail, I confirmed by direct comparison that the new code's output differs from the true pre-PR (d0925d9) baseline: the old template unconditionally emitted${fieldsHtml}producing a whitespace-only line (" ") between<form>and<button>; the new code'sfieldsHtml ? ... : ''ternary drops that line entirely whenfieldsHtmlis falsy.For the realistic case — non-empty
fields(the default, and what every real published form uses) — I confirmed output is byte-identical old vs. new. So this doesn't affect any real site today (nobody publishes a contact form with zero fields), and arguably the new output is better (no stray whitespace-only line). But two things worth tightening:fieldsis non-empty. Worth a one-line caveat, or just special-case empty-fields to match old output exactly if literal byte-identity is the goal.fields: []edge case (which is not byte-identical to pre-PR baseline) and never exercises the commondefaultFields/non-empty-fields path, which is the one that actually needs the regression guard. Recommend adding a case with real fields (ordefaultFields) asserting full-string equality against a fixed expected HTML string, to actually pin the byte-identity guarantee for production forms.Neither point blocks merge — the relay wiring, escaping, id-matching, and honeypot behavior are all correct for the shipping scenarios.