- FormStylePanel: ContactForm field editor (add/remove/reorder via
ArrayPropEditor + manual move up/down) covering label/name/placeholder/
type (full sanitizeInputType allowlist + textarea/select)/required/
options.
- SubscribeForm.toHtml: was a dead `<form method="POST">` with no action at
all -- wired through the same relayFormWiring contract as ContactForm/
FormContainer so a recipientEmail makes it actually submit (marker +
placeholder action + honeypot), falling back to action="#" otherwise.
- SearchBar: was purely decorative (no action/method/input name) -- now a
real GET form (configurable target, default "/") with input name="q",
safeUrl-guarded against javascript:/vbscript: breakout.
- Box-model (margin/padding per-side, border, shadow, opacity), entrance
animation, and hide-on-device controls added to FormStylePanel and
rolled out (blank/false craft.props defaults) across ContactForm,
FormContainer, InputField, TextareaField, FormButton, SubscribeForm,
SearchBar. No toHtml changes needed for animation/visibility --
html-export.ts's buildDataAttrs() already emits data-animation/
data-hide-* generically from these prop names.
- Extended toHtml tests for all 7 components: field-type rendering
(incl. textarea/select), type-attribute XSS sanitization, relay/GET
functional wiring, box-model style passthrough, craft.props defaults.
npx vitest run: 689/689 passed. npm run build: green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An adversarial pass found 5 Critical XSS sinks where props declared number/enum
in TypeScript were interpolated raw into exported HTML attribute values,
trusting the type — but nothing enforces it at runtime (AI update_props only
validates node_id; deserialized saved state is untyped JSON). Fixed all 5
(NumberCounter data-target, StarRating aria-label, FormContainer method,
ContactForm/InputField input type) plus 6 sibling sinks found by an exhaustive
audit of every attribute-value interpolation across src/components: a
JS-source injection into ContentSlider's inline setInterval script, a
prototype-pollution-adjacent allowlist gap in Section's divider-shape lookup,
TextareaField rows, Testimonials rating aria-label, HeroSimple textAlign, and
MapEmbed zoom. Adds shared sanitizeFormMethod/sanitizeInputType allowlist
helpers to utils/escape.ts alongside the existing escapeAttr/safeUrl/cssValue
primitives. Every fix is TDD'd: a malicious-value test reproduces the raw
injection against the pre-fix code, then passes after the fix.
502 tests green (npx vitest run), tsc + vite build green (npm run build).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two components of the det-id bug class were missed in the earlier migration:
NumberCounter's wrapper/counter ids and count-up script, and the shared
form-relay-wiring fid (used by ContactForm/FormContainer). Both used
Math.random() for exported HTML ids, breaking caching/diffing across exports.
Migrate both to scopeId(nodeId, fallbackSeed, prefix), threading nodeId
through NumberCounter.toHtml and relayFormWiring (via ContactForm.toHtml and
FormContainer.toHtml, both now passing nodeId as their 3rd arg).
Each component defined a .craft.related.settings panel that was never
rendered -- the right panel renders only GuidedStyles (per-type
*StylePanel components), never .related.settings. Removed all dead
settings components across every component, their settings-only helpers
(including the dead uploadToWhp/showBrowser/handleBrowse asset-browse
blocks in Logo/Navbar/VideoBlock/FeaturesGrid, and CtasEditor in
_cta-helpers), and dropped the now-empty related keys. Render output,
.craft props/rules, and toHtml statics are unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The recipient field was only on the ContactForm block; templates build forms
from FormContainer + InputField, so template-based contact forms had no way to
set a target address. Add 'Send submissions to' + thank-you fields to
FormContainer, and extract the marker/placeholder/honeypot into a shared
form-relay-wiring helper so ContactForm and FormContainer can't drift.
Rebuilt the visual site builder from scratch using Craft.js, React 18,
and TypeScript. The new editor renders directly in the DOM (no iframe),
supports 40+ components, multi-page with shared header/footer, 16
templates, full-spectrum color/gradient controls, custom head code
injection, save/publish workflow, and auto-save.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>