Used my friendly AI helper to try and work out the problem.
First added a lot of logging. No idea if it gives any hints without knowing where the logs are exactly, but I've added them at the end.
Tried reverting to the Git commit right before this one:
Form works fine then.
---------------
Asked AI if it could work out what the problem could be, comparing the old version before that commit with the current one. It says:
After comparing both versions, here are the critical differences causing your form to break:
Key Breaking Changes:
1. Handler Registration Timing:
- Old version: When registering a handler, it immediately searches for existing forms with that handler name
- New version: Doesn't proactively search for forms, relying on a promise-based system
2. Initial Form Setup:
- Old version: for (const form of qSA<HTMLFormElement>(form[data-wh-form-handler="${CSS.escape(handlername)}"])) void scheduleFormSetup(form, handler);
- New version: This code is completely removed
3. Handler Lookup:
- Old version: Direct object property lookup: handlers?.[form.dataset.whFormHandler!]
- New version: Complex Map lookup with Promise resolution
The Specific Problem
Looking at your code in subscribe.ts, your Vue component mounts and registers the form handler after the form element is in the DOM:
async mounted() {
if (!customElements.get('form-phone-field')) {
customElements.define("form-phone-field", FormPhoneField);
}
registerSubscribeHandler(this); // <-- This is too late in the new system
// ...
}
In the old system, this worked because:
1. When handlers were registered, it actively searched for forms already in the DOM
2. The form's handler attribute was checked after Vue mounted
In the new system:
1. Forms are detected earlier
2. If the handler doesn't exist yet, it creates a Promise to be resolved later
3. But your form might not be correctly connected to that Promise
Fix Options:
1. Register the handler earlier:
Move the handler registration before Vue mounts, perhaps in a script that loads first
2. Revert the file:
Replace the current registration.ts with your old version
3. Force handler reconnection:
Add this code after Vue mounts:
const formEl = document.querySelector('form[data-wh-form-handler="looping_corporate:subscribe"]');
if (formEl && !formEl.whFormHandler) {
const handler = formEl.dataset.whFormHandler;
console.log(`Forcing reconnection for ${handler}`);
formEl.whFormHandler = new SubscribeForm(formEl, this);
}
The simplest solution is to revert to your old registration.ts file, but that might cause other issues with newer code. The third option might be the safest way to fix your specific form without changing the system.
---------------
Tried that 'third option', which eventually fixes the form, but after a timeout of 1s.
---------------
Asked for a summary on why that works:
What the Fix Does and Why It Works
This solution addresses a timing issue caused by recent changes to WebHare's form handler registration system:
The Problem:
1. In April 2025, the form registration system was completely rewritten from using a simple object to a complex Promise-based Map system
2. Your Vue app registers the 'looping_corporate:subscribe' handler after the form element is created in the DOM
3. In the new system, when a form is found before its handler is registered, it creates a Promise waiting for the handler
4. The connection between this Promise and your later handler registration was failing
How Our Fix Works:
1. Adds a Delayed Re-Registration: After Vue mounts, we wait 1 second to ensure everything is loaded
2. Provides Multiple Recovery Methods:
- Re-registers the handler directly using registerHandler
- Attaches a new form handler instance directly to the form element as a backup
3. Avoids Dependency on Vue Internals: Uses the already available app variable instead of trying to access Vue's internal structures
Why This Approach Works:
1. Timing: The timeout ensures Vue has fully mounted and registered its components
2. Direct Connection: By manually attaching the handler to formEl.whFormHandler, we bypass any registration system issues
3. Redundancy: We use two methods to attach the handler, increasing our chances of success
4. Visibility: The detailed logging helps diagnose exactly where any issues occur
This fix effectively provides a safety net that works regardless of the changes made to the internal handler registration system, ensuring your form continues to function properly.
Vue's Role in the Form Registration Problem
Vue's Contribution to the Issue:
1. Component Lifecycle Timing: Vue mounts components asynchronously, meaning your form element exists in the DOM before Vue's mounted() hook runs where you register the handler
2. DOM Manipulation: Vue renders the form into the DOM first, then executes JavaScript to set up functionality - creating a gap between element creation and handler registration
3. Encapsulation Pattern: Vue encourages keeping handler registration within component lifecycle methods rather than registering handlers globally at page load
This Is Not Just a Vue Problem
This issue would affect any Single Page Application (SPA) or dynamic environment where:
1. DOM Elements Load Before Handlers: Any framework (React, Angular, Svelte, etc.) that adds forms to the DOM before registering their handlers would face this issue
2. Dynamic Content Loading: Any system that loads content via AJAX or dynamic imports and then tries to register handlers afterward
3. Lazy-Loaded Components: Any framework using code-splitting or lazy-loading where handler registration code arrives after DOM elements
The Fundamental Timing Problem
The root issue is a mismatch between these two processes:
1. WebHare's new form system immediately scans for forms when the DOM updates and creates promises for future handlers
2. SPAs typically create DOM elements first, then execute JavaScript to set up functionality
The April 2025 changes to WebHare's registration system modified how these two processes connect, breaking the previous pattern where handler registration would actively search for existing forms.
This pattern affects any environment where elements exist before their handlers are registered - Vue just happens to be where you encountered it first.
-------
Also tried very minimal integrations, but can only get it to work if registerHandler is in the main code (eg not in onDomReady and not in Vue.createApp).
Full console logs:
[registerHandlers] =========== REGISTERING HANDLERS ===========
registration.ts:101 [registerHandlers] Registering handlers: ['publisher:form']
registration.ts:102 [registerHandlers] Handlers details: [{…}]
registration.ts:110 [registerHandlers] First time initialization of handlers
registration.ts:125 [registerHandlers] Inserting new handler for "publisher:form"
registration.ts:126 [registerHandlers] Handler function type: function
registration.ts:127 [registerHandlers] Handler function is async: false
registration.ts:128 [registerHandlers] Handler function first line: (form) => new FormBase(form)
registration.ts:125 [registerHandlers] Inserting new handler for "publisher:rpc"
registration.ts:126 [registerHandlers] Handler function type: function
registration.ts:127 [registerHandlers] Handler function is async: false
registration.ts:128 [registerHandlers] Handler function first line: (form) => new RPCFormBase(form)
registration.ts:148 [registerHandlers] Setting up form element registration
registration.ts:252 [registerHandlers] =========== COMPLETED HANDLER REGISTRATION ===========
[registerHandlers] =========== FOUND FORM FOR HANDLER ===========
registration.ts:151 [registerHandlers] Found form with handler: looping_corporate:subscribe
registration.ts:152 [registerHandlers] Form element at registration time: form.wh-form
registration.ts:153 [registerHandlers] Form is in document: true
registration.ts:154 [registerHandlers] Form properties: {id: '', dataset: {…}, className: 'wh-form', handler: 'looping_corporate:subscribe', whFormHandler: 'NO'}
registration.ts:165 [registerHandlers] Creating promise for future handler "looping_corporate:subscribe"
registration.ts:166 [registerHandlers] Form at promise creation time: form.wh-form
registration.ts:167 [registerHandlers] Form is in document at promise creation: true
registration.ts:169 [registerHandlers] Created promise resolvers: {promise: Promise, resolve: ƒ, reject: ƒ}
registration.ts:175 [registerHandlers] Waiting for promise to resolve for handler "looping_corporate:subscribe"
registration.ts:176 [registerHandlers] Promise object: {promise: Promise, resolve: ƒ, reject: ƒ}
registration.ts:256 [registerHandler] Registering single handler "looping_corporate:subscribe"
registration.ts:100 [registerHandlers] =========== REGISTERING HANDLERS ===========
registration.ts:101 [registerHandlers] Registering handlers: ['looping_corporate:subscribe']
registration.ts:102 [registerHandlers] Handlers details: [{…}]
registration.ts:133 [registerHandlers] Resolving promise for handler "looping_corporate:subscribe"
registration.ts:134 [registerHandlers] Handler function type: function
registration.ts:135 [registerHandlers] Handler function is async: false
registration.ts:136 [registerHandlers] Handler function first line: (form) => {
registration.ts:252 [registerHandlers] =========== COMPLETED HANDLER REGISTRATION ===========
registration.ts:256 [registerHandler] Registering single handler "looping_corporate:callmebackform"
registration.ts:100 [registerHandlers] =========== REGISTERING HANDLERS ===========
registration.ts:101 [registerHandlers] Registering handlers: ['looping_corporate:callmebackform']
registration.ts:102 [registerHandlers] Handlers details: [{…}]
registration.ts:125 [registerHandlers] Inserting new handler for "looping_corporate:callmebackform"
registration.ts:126 [registerHandlers] Handler function type: function
registration.ts:127 [registerHandlers] Handler function is async: false
registration.ts:128 [registerHandlers] Handler function first line: (form) => {
registration.ts:252 [registerHandlers] =========== COMPLETED HANDLER REGISTRATION ===========
[registerHandler] Registering single handler "looping_corporate:callmebackform"
registration.ts:100 [registerHandlers] =========== REGISTERING HANDLERS ===========
registration.ts:101 [registerHandlers] Registering handlers: ['looping_corporate:callmebackform']
registration.ts:102 [registerHandlers] Handlers details: [{…}]
registration.ts:125 [registerHandlers] Inserting new handler for "looping_corporate:callmebackform"
registration.ts:126 [registerHandlers] Handler function type: function
registration.ts:127 [registerHandlers] Handler function is async: false
registration.ts:128 [registerHandlers] Handler function first line: (form) => {
registration.ts:252 [registerHandlers] =========== COMPLETED HANDLER REGISTRATION ===========
[registerHandlers] Promise resolved for handler "looping_corporate:subscribe", scheduling form setup
registration.ts:190 [registerHandlers] Factory type: function
registration.ts:191 [registerHandlers] Factory function: (form) => {
const createdForm = handler(form);
if (!isLive && createdForm instanceof FormBas...
registration.ts:192 [registerHandlers] Form at promise resolution time: <form class="wh-form wh-form--allownext" method="post" action="javascript:console.error('No RPC handler installed');" data-objecttype="BOAT" data-wh-form-id="subscribe" data-wh-form-handler="looping_corporate:subscribe" data-wh-form-target="jJOWyZCYtDcb5mPp_JRSKsJzoTWv27TWoTO2vwherNJEpHMxVY94ROq3-Xbd__4WPDpygalfv9yv_RtUGsO7dYNBrhvmlEI5SyeKSQqnU7CUATxiAZzOC4mX7DU.7izhWEdCFnzYbMZ7.KRpMRyy6HqkEuRc3OMoM6g" novalidate>…</form>
registration.ts:193 [registerHandlers] Form is in document at promise resolution: false
registration.ts:12 [scheduleFormSetup] =========== STARTING FORM SETUP ===========
registration.ts:13 [scheduleFormSetup] Starting for form
registration.ts:14 [scheduleFormSetup] Form element: <form class="wh-form wh-form--allownext" method="post" action="javascript:console.error('No RPC handler installed');" data-objecttype="BOAT" data-wh-form-id="subscribe" data-wh-form-handler="looping_corporate:subscribe" data-wh-form-target="jJOWyZCYtDcb5mPp_JRSKsJzoTWv27TWoTO2vwherNJEpHMxVY94ROq3-Xbd__4WPDpygalfv9yv_RtUGsO7dYNBrhvmlEI5SyeKSQqnU7CUATxiAZzOC4mX7DU.7izhWEdCFnzYbMZ7.KRpMRyy6HqkEuRc3OMoM6g" novalidate>…</form>
registration.ts:15 [scheduleFormSetup] Form attributes: {id: '', dataset: {…}, className: 'wh-form', handler: 'looping_corporate:subscribe'}
registration.ts:21 [scheduleFormSetup] Factory type: function Function
registration.ts:22 [scheduleFormSetup] Factory function: (form) => {
const createdForm = handler(form);
if (!isLive && createdForm instanceof FormBas...
registration.ts:28 [scheduleFormSetup] Found 0 custom elements: []
registration.ts:58 [scheduleFormSetup] About to call factory for form
registration.ts:59 [scheduleFormSetup] Current form properties before factory call: {id: '', dataset: {…}, whFormHandler: 'NO'}
[scheduleFormSetup] Calling factory with form element: <form class="wh-form wh-form--allownext" method="post" action="javascript:console.error('No RPC handler installed');" data-objecttype="BOAT" data-wh-form-id="subscribe" data-wh-form-handler="looping_corporate:subscribe" data-wh-form-target="jJOWyZCYtDcb5mPp_JRSKsJzoTWv27TWoTO2vwherNJEpHMxVY94ROq3-Xbd__4WPDpygalfv9yv_RtUGsO7dYNBrhvmlEI5SyeKSQqnU7CUATxiAZzOC4mX7DU.7izhWEdCFnzYbMZ7.KRpMRyy6HqkEuRc3OMoM6g" novalidate>…</form>
[scheduleFormSetup] Factory called successfully, returned: Promise {<pending>}
registration.ts:69 [scheduleFormSetup] Return value type: object
registration.ts:70 [scheduleFormSetup] Return value constructor: Promise
registration.ts:71 [scheduleFormSetup] Return value is Promise: true
registration.ts:72 [scheduleFormSetup] Form handler attached to form element: NO
registration.ts:75 [scheduleFormSetup] WARNING: Factory returned a Promise - this may cause issues
[scheduleFormSetup] Promise resolved to: SubscribeForm {fieldName: '', fieldmap: Map(15), node: form.wh-form.wh-form--allownext, elements: HTMLFormControlsCollection(30), _formsessionid: 'Cj50_1kBQyRhpxNXtMBrWQ', …}
registration.ts:79 [scheduleFormSetup] After Promise resolution - form has whFormHandler: NO
registration.ts:90 [scheduleFormSetup] =========== COMPLETED FORM SETUP ===========
registration.ts:196 [registerHandlers] Handler setup complete, result: SubscribeForm {fieldName: '', fieldmap: Map(15), node: form.wh-form.wh-form--allownext, elements: HTMLFormControlsCollection(30), _formsessionid: 'Cj50_1kBQyRhpxNXtMBrWQ', …}
registration.ts:197 [registerHandlers] Result type: object
registration.ts:198 [registerHandlers] Result constructor: SubscribeForm
registration.ts:199 [registerHandlers] Form has whFormHandler property: NO
registration.ts:205 [registerHandlers] Form has NO whFormHandler property after setup!
registration.ts:206 [registerHandlers] Manually attaching handler to form
registration.ts:210 [registerHandlers] Manually attached handler: SUCCESS
index.ts:10 ❌ No RPC handler registered for this form - setup a handler for 'looping_corporate:subscribe' <form class="wh-form" method="post" action="javascript:console.error('No RPC handler installed');" data-objecttype="BOAT" data-wh-form-id="subscribe" data-wh-form-handler="looping_corporate:subscribe" data-wh-form-target="tRWTZmuBWcPvz_JE7bIcCH53L-LW3UAnpUx_Arhg7PZSVwOJ-UEPK9VZ0U4wMQVt2nhV8ctOv0pH0djpknL-GoIznkKHy6O-nPukFvTTOZveSgGa8IqQMiEBL4s.afAdt12uZZc49ptf.btgsJvO_Nc7spTttXPMjhw">…</form>
subscribe.ts:550 Setting up direct navigation handlers
subscribe.ts:986 Checking for form handler attachment issues...
subscribe.ts:989 Found subscribe form element: <form class="wh-form wh-form--allownext" method="post" action="javascript:console.error('No RPC handler installed');" data-objecttype="BOAT" data-wh-form-id="subscribe" data-wh-form-handler="looping_corporate:subscribe" data-wh-form-target="tRWTZmuBWcPvz_JE7bIcCH53L-LW3UAnpUx_Arhg7PZSVwOJ-UEPK9VZ0U4wMQVt2nhV8ctOv0pH0djpknL-GoIznkKHy6O-nPukFvTTOZveSgGa8IqQMiEBL4s.afAdt12uZZc49ptf.btgsJvO_Nc7spTttXPMjhw" novalidate>…</form>
subscribe.ts:990 Form has handler attached: NO
subscribe.ts:993 Form handler not attached, forcing reconnection
subscribe.ts:996 Creating direct handler
registration.ts:256 [registerHandler] Registering single handler "looping_corporate:subscribe"
registration.ts:100 [registerHandlers] =========== REGISTERING HANDLERS ===========
registration.ts:101 [registerHandlers] Registering handlers: ['looping_corporate:subscribe']
registration.ts:102 [registerHandlers] Handlers details: [{…}]
registration.ts:133 [registerHandlers] Resolving promise for handler "looping_corporate:subscribe"
registration.ts:134 [registerHandlers] Handler function type: function
registration.ts:135 [registerHandlers] Handler function is async: false
registration.ts:136 [registerHandlers] Handler function first line: (form) => {
registration.ts:252 [registerHandlers] =========== COMPLETED HANDLER REGISTRATION ===========
subscribe.ts:1010 Direct attachment as backup
subscribe.ts:1014 Reconnection attempt complete, form has handler: YES
registration.ts:180 [registerHandlers] Checking promise status after 2s for handler "looping_corporate:subscribe"
registration.ts:185 [registerHandlers] Promise state after 2s: Promise {<pending>}