From 839713c8f79d216062b20167a25268176211c944 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 5 Oct 2026 20:03:20 +0000 Subject: [PATCH 1/3] fix: show Marketo required asterisks only when IsRequired The Talk to us form CSS forced every .mktoAsterix to display, so optional fields such as Phone looked required. Hide the native asterisk unless Marketo marks the field with .mktoRequiredField, and suppress the ThemeStyleOverride ::after so required labels do not get two marks. Co-authored-by: Marc Klingen --- style.css | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/style.css b/style.css index 9e3553a71b..e0e7c2ad22 100644 --- a/style.css +++ b/style.css @@ -978,8 +978,13 @@ line-height: 1.25rem !important; } -.lf-marketo-form .mktoAsterix { - display: inline !important; +/* Marketo always emits .mktoAsterix in every label, then hides it unless + the field wrap has .mktoRequiredField (from the form descriptor's + IsRequired: true). Do not force-show every asterisk — that marks + optional fields such as Phone as required. Keep this selector more + specific than the Marketo ThemeStyleOverride so page CSS wins. */ +.lf-marketo-form .mktoForm .mktoAsterix { + display: none !important; float: none !important; line-height: inherit !important; padding-left: 0 !important; @@ -987,6 +992,16 @@ color: hsl(var(--destructive)) !important; } +.lf-marketo-form .mktoForm .mktoRequiredField .mktoAsterix { + display: inline !important; +} + +/* The Marketo form ThemeStyleOverride also injects " *" via ::after on + required labels. Suppress it so required fields do not show two marks. */ +.lf-marketo-form .mktoForm .mktoRequiredField .mktoLabel::after { + content: none !important; +} + .lf-marketo-form input[type="text"], .lf-marketo-form input[type="email"], .lf-marketo-form input[type="tel"], From 017c2bd8c6a9907bd74452f79fd4a578e410f0e0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 5 Oct 2026 20:21:07 +0000 Subject: [PATCH 2/3] fix: beat Marketo ThemeStyleOverride on optional asterisks The page CSS required .mktoForm, which is not always on the loaded
, so Marketo's later display:contents rule still showed every asterisk. Target form[id^=mktoForm_] and hide optional marks from .mktoRequiredField after render so IsRequired wins. Co-authored-by: Marc Klingen --- components/MarketoContactForm.tsx | 18 ++++++++++++++++++ style.css | 18 +++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/components/MarketoContactForm.tsx b/components/MarketoContactForm.tsx index 8c3705f7df..124736962e 100644 --- a/components/MarketoContactForm.tsx +++ b/components/MarketoContactForm.tsx @@ -31,6 +31,19 @@ declare global { } } +function applyRequiredAsterisks(formElement: HTMLElement) { + formElement + .querySelectorAll(".mktoAsterix") + .forEach((asterisk) => { + const isRequired = Boolean(asterisk.closest(".mktoRequiredField")); + asterisk.style.setProperty( + "display", + isRequired ? "inline" : "none", + "important", + ); + }); +} + function MarketoSuccessPanel() { return (
@@ -71,6 +84,11 @@ export function MarketoContactForm() { MARKETO_MUNCHKIN_ID, MARKETO_FORM_ID, (form) => { + const renderedForm = document.getElementById(MARKETO_FORM_ELEMENT_ID); + if (renderedForm) { + applyRequiredAsterisks(renderedForm); + } + setIsFormLoaded(true); form.onSuccess(() => { diff --git a/style.css b/style.css index e0e7c2ad22..0de5eae135 100644 --- a/style.css +++ b/style.css @@ -981,9 +981,13 @@ /* Marketo always emits .mktoAsterix in every label, then hides it unless the field wrap has .mktoRequiredField (from the form descriptor's IsRequired: true). Do not force-show every asterisk — that marks - optional fields such as Phone as required. Keep this selector more - specific than the Marketo ThemeStyleOverride so page CSS wins. */ -.lf-marketo-form .mktoForm .mktoAsterix { + optional fields such as Phone as required. + + Selectors must beat the Marketo ThemeStyleOverride + `.lf-marketo-form .mktoAsterix { display: contents !important }` which + is injected after page CSS. The form id is always present; `.mktoForm` + is not reliably on the when Forms2 finishes loading. */ +.lf-marketo-form form[id^="mktoForm_"] .mktoAsterix { display: none !important; float: none !important; line-height: inherit !important; @@ -992,13 +996,13 @@ color: hsl(var(--destructive)) !important; } -.lf-marketo-form .mktoForm .mktoRequiredField .mktoAsterix { +.lf-marketo-form form[id^="mktoForm_"] .mktoRequiredField .mktoAsterix { display: inline !important; } -/* The Marketo form ThemeStyleOverride also injects " *" via ::after on - required labels. Suppress it so required fields do not show two marks. */ -.lf-marketo-form .mktoForm .mktoRequiredField .mktoLabel::after { +/* ThemeStyleOverride also injects " *" via ::after on required labels. + Suppress it so required fields do not show two marks. */ +.lf-marketo-form form[id^="mktoForm_"] .mktoRequiredField .mktoLabel::after { content: none !important; } From 3586523fbc3992c3150011607a4b93e5a272ef72 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 6 Oct 2026 08:24:54 +0000 Subject: [PATCH 3/3] fix: keep Marketo analytics test passing with asterisk helper applyRequiredAsterisks now no-ops when querySelectorAll is missing so the sales-completion fixture cannot throw before onSuccess is registered. The test mock covers required vs optional asterisks and a stub without querySelectorAll. Co-authored-by: Marc Klingen --- components/MarketoContactForm.tsx | 4 ++ scripts/test-use-case-analytics.mjs | 91 ++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/components/MarketoContactForm.tsx b/components/MarketoContactForm.tsx index 124736962e..8dbcb7dedf 100644 --- a/components/MarketoContactForm.tsx +++ b/components/MarketoContactForm.tsx @@ -32,6 +32,10 @@ declare global { } function applyRequiredAsterisks(formElement: HTMLElement) { + if (typeof formElement.querySelectorAll !== "function") { + return; + } + formElement .querySelectorAll(".mktoAsterix") .forEach((asterisk) => { diff --git a/scripts/test-use-case-analytics.mjs b/scripts/test-use-case-analytics.mjs index 7a6a724459..e5f56f9199 100644 --- a/scripts/test-use-case-analytics.mjs +++ b/scripts/test-use-case-analytics.mjs @@ -469,7 +469,42 @@ test("sales completion is emitted only after Marketo success and is deduplicated }, }, }; - globalThis.document = { getElementById: () => ({ replaceChildren() {} }) }; + const requiredAsterisk = { + display: null, + priority: null, + closest: (selector) => + selector === ".mktoRequiredField" ? { required: true } : null, + style: { + setProperty(name, value, priority) { + if (name === "display") { + requiredAsterisk.display = value; + requiredAsterisk.priority = priority; + } + }, + }, + }; + const optionalAsterisk = { + display: null, + priority: null, + closest: () => null, + style: { + setProperty(name, value, priority) { + if (name === "display") { + optionalAsterisk.display = value; + optionalAsterisk.priority = priority; + } + }, + }, + }; + globalThis.document = { + getElementById: () => ({ + replaceChildren() {}, + querySelectorAll(selector) { + assert.equal(selector, ".mktoAsterix"); + return [requiredAsterisk, optionalAsterisk]; + }, + }), + }; const form = load(compile("components/MarketoContactForm.tsx"), (name) => { if (name === "react") return { @@ -496,6 +531,10 @@ test("sales completion is emitted only after Marketo success and is deduplicated }); try { form.MarketoContactForm(); + assert.equal(requiredAsterisk.display, "inline"); + assert.equal(requiredAsterisk.priority, "important"); + assert.equal(optionalAsterisk.display, "none"); + assert.equal(optionalAsterisk.priority, "important"); assert.equal(captured.length, 0, "Loading a form is not a conversion"); assert.equal(onSuccess({ Email: "test@example.invalid" }, ""), false); assert.equal(onSuccess({}, ""), false); @@ -511,3 +550,53 @@ test("sales completion is emitted only after Marketo success and is deduplicated delete globalThis.document; } }); + +test("sales completion still registers when the form stub has no querySelectorAll", () => { + const captured = []; + let onSuccess; + globalThis.window = { + MktoForms2: { + loadForm(_base, _id, _form, callback) { + callback({ + onSuccess: (fn) => { + onSuccess = fn; + }, + }); + }, + }, + }; + globalThis.document = { getElementById: () => ({ replaceChildren() {} }) }; + const form = load(compile("components/MarketoContactForm.tsx"), (name) => { + if (name === "react") + return { + useState: (value) => [value, () => {}], + useRef: (value) => ({ current: value }), + useCallback: (fn) => fn, + useEffect: (fn) => fn(), + }; + if (name === "react/jsx-runtime") + return { + jsx: (type, props) => ({ type, props }), + jsxs: (type, props) => ({ type, props }), + }; + if (name === "lucide-react") return { Check: () => null }; + if (name === "posthog-js") + return { + capture: (event, props) => captured.push({ event, props }), + }; + if (name === "@/lib/ad-conversions") + return { reportTalkToUsConversion: () => {} }; + if (name === "@/lib/use-case-analytics") + return { readUseCaseAttribution: () => attribution }; + throw Error(name); + }); + try { + assert.doesNotThrow(() => form.MarketoContactForm()); + assert.equal(typeof onSuccess, "function"); + assert.equal(onSuccess({}, ""), false); + assert.equal(captured[0]?.event, "sales:inquiry_completed"); + } finally { + delete globalThis.window; + delete globalThis.document; + } +});