Summary
Customers cannot switch templates from the customer-panel template-switching page when their site has no current template (get_template_id() === 0). Clicking "Select" on any template card silently does nothing — the confirmation panel never appears, so there is no way to trigger the switch.
This regressed in v2.10.0 via PR #1113 (commit 05af62d1).
Reproduce
- Have a multisite with at least one site template and one customer site.
- Ensure the customer site's
template_id meta is 0 (i.e. it was created without a template assigned — typical when the checkout form has no Template Selection field, or when the site was created via wpmu_create_blog() outside Ultimate Multisite).
- Log in as the customer and navigate to Customer Panel → Template Switching.
- The current-template card correctly shows the friendly "This site is not currently based on a template. Choose one below to apply it." copy. Good.
- Click Select on any template card in the grid.
Expected: the confirmation panel ("Switch to {template}?") appears and the customer can click Confirm to perform the switch.
Actual: nothing happens. No confirmation panel, no error, no console message. The grid card flips to "Selected" state but the customer has no way to actually trigger the switch.
Root cause
assets/js/template-switching.js line 165 (added in PR #1113):
template_id(new_value, old_value) {
if (old_value === undefined) {
return;
}
if (new_value === 0 || new_value === '0') {
this.confirm_active = false;
return;
}
/*
* Defence in depth against directive-ordering races on
* mount. ... -1 is a value that no real template can
* have, so we treat it as "not yet initialised" and bail
* out — the watcher will fire again on the next legitimate
* template change.
*/
// eslint-disable-next-line eqeqeq
if (this.original_template_id == -1 || this.original_template_id <= 0) { // ← bug
return;
}
// ...
this.confirm_active = true;
}
The guard conflates two distinct states:
original_template_id === -1 — the data() sentinel, meaning "v-init has not run yet" (this is the legitimate race the guard was designed to defend against).
original_template_id === 0 — a perfectly valid runtime state meaning "this site has no current template".
Because the guard fires when original_template_id <= 0, customers whose sites never had a template cannot ever flip confirm_active = true, so the confirm panel never appears and the switch path is unreachable.
The PHP side already handles the template_id === 0 case correctly:
views/ui/template-switching-current.php:22 checks $original_template_id > 0 and renders friendly fallback copy.
inc/ui/class-template-switching-element.php:325-336 (switch_template() AJAX handler) only validates the new template_id, not the existing one.
Site_Duplicator::override_site($from_site_id, $to_site_id) only requires the destination site to exist; it does not require it to have a prior template.
So fixing the JS guard is sufficient — the rest of the stack is already happy.
Proposed fix
Tighten the sentinel check to its original intent: "the data() default of -1 means not-yet-initialised". Treat 0 as a legitimate value:
// Only -1 is the "not yet bound" sentinel. 0 is a real, valid state
// meaning "site has no current template" — the customer must still be
// able to pick a template from the grid in that case.
if (this.original_template_id === -1) {
return;
}
Equivalent variants:
if (Number(this.original_template_id) === -1) { return; }
// or, even more defensive:
if (this.original_template_id < 0) { return; }
The second form (< 0) keeps the door open to any future "uninitialised" sentinel that is also negative without re-introducing the 0 foot-gun.
Suggested files to modify
assets/js/template-switching.js — fix the guard at line 165 (the <= 0 clause).
- The minified twin
assets/js/template-switching.min.js is regenerated at release time — do not hand-edit on the feature branch.
Verification
- Apply the fix.
- Local repro: ensure a customer site exists with
template_id postmeta = 0. Log in as that customer, open the template-switching page, click Select on any template card.
- Expected new behaviour: the confirmation panel appears with the chosen template's thumbnail/name. Clicking Confirm performs the switch via
wu_switch_template AJAX → Site_Duplicator::override_site() and the customer's site is overwritten with the chosen template's content.
- Regression check: ensure customers whose sites do have a current template still see the confirm panel only after they pick a different template (not when v-init initially syncs
template_id to the existing value). The if (old_value === undefined) return; guard at line 137 still handles the v-init case independently of original_template_id, so the original race protection is preserved by the existing old_value === undefined early return.
Add a Cypress / unit test in tests/ covering the "no current template → click Select → confirm panel becomes visible" flow if framework supports asserting on Vue state. At minimum, a manual test note in the PR description is required.
Customer-impact context
This was discovered while investigating a customer report that "sites are being created with default WordPress content instead of being cloned from the chosen template". On their live site (nexaweb.org), the actual signup-form misconfiguration means new customer sites are created with no template_id (so they get fresh WP content). Customers then discovered they cannot use the customer-panel template-switching page to fix their site after the fact — the page shows the templates but clicking Select does nothing. The fix here unblocks those customers' self-service recovery path even when their original signup did not assign a template.
The signup-form misconfiguration itself is a separate, customer-side configuration issue (the network admin needs to add a Template Selection field to each checkout form via Network Admin → Multisite Ultimate → Checkout Forms → edit). That is documented separately in the customer support thread and is not part of this issue.
aidevops.sh v3.14.83 plugin for OpenCode v1.14.33 with claude-opus-4-7 spent 6h 16m and 83,381 tokens on this with the user in an interactive session.
Summary
Customers cannot switch templates from the customer-panel template-switching page when their site has no current template (
get_template_id() === 0). Clicking "Select" on any template card silently does nothing — the confirmation panel never appears, so there is no way to trigger the switch.This regressed in v2.10.0 via PR #1113 (commit
05af62d1).Reproduce
template_idmeta is0(i.e. it was created without a template assigned — typical when the checkout form has no Template Selection field, or when the site was created viawpmu_create_blog()outside Ultimate Multisite).Expected: the confirmation panel ("Switch to {template}?") appears and the customer can click Confirm to perform the switch.
Actual: nothing happens. No confirmation panel, no error, no console message. The grid card flips to "Selected" state but the customer has no way to actually trigger the switch.
Root cause
assets/js/template-switching.jsline 165 (added in PR #1113):The guard conflates two distinct states:
original_template_id === -1— the data() sentinel, meaning "v-init has not run yet" (this is the legitimate race the guard was designed to defend against).original_template_id === 0— a perfectly valid runtime state meaning "this site has no current template".Because the guard fires when
original_template_id <= 0, customers whose sites never had a template cannot ever flipconfirm_active = true, so the confirm panel never appears and the switch path is unreachable.The PHP side already handles the
template_id === 0case correctly:views/ui/template-switching-current.php:22checks$original_template_id > 0and renders friendly fallback copy.inc/ui/class-template-switching-element.php:325-336(switch_template()AJAX handler) only validates the newtemplate_id, not the existing one.Site_Duplicator::override_site($from_site_id, $to_site_id)only requires the destination site to exist; it does not require it to have a prior template.So fixing the JS guard is sufficient — the rest of the stack is already happy.
Proposed fix
Tighten the sentinel check to its original intent: "the data() default of -1 means not-yet-initialised". Treat
0as a legitimate value:Equivalent variants:
The second form (
< 0) keeps the door open to any future "uninitialised" sentinel that is also negative without re-introducing the0foot-gun.Suggested files to modify
assets/js/template-switching.js— fix the guard at line 165 (the<= 0clause).assets/js/template-switching.min.jsis regenerated at release time — do not hand-edit on the feature branch.Verification
template_idpostmeta =0. Log in as that customer, open the template-switching page, click Select on any template card.wu_switch_templateAJAX →Site_Duplicator::override_site()and the customer's site is overwritten with the chosen template's content.template_idto the existing value). Theif (old_value === undefined) return;guard at line 137 still handles the v-init case independently oforiginal_template_id, so the original race protection is preserved by the existingold_value === undefinedearly return.Add a Cypress / unit test in
tests/covering the "no current template → click Select → confirm panel becomes visible" flow if framework supports asserting on Vue state. At minimum, a manual test note in the PR description is required.Customer-impact context
This was discovered while investigating a customer report that "sites are being created with default WordPress content instead of being cloned from the chosen template". On their live site (
nexaweb.org), the actual signup-form misconfiguration means new customer sites are created with notemplate_id(so they get fresh WP content). Customers then discovered they cannot use the customer-panel template-switching page to fix their site after the fact — the page shows the templates but clicking Select does nothing. The fix here unblocks those customers' self-service recovery path even when their original signup did not assign a template.The signup-form misconfiguration itself is a separate, customer-side configuration issue (the network admin needs to add a Template Selection field to each checkout form via Network Admin → Multisite Ultimate → Checkout Forms → edit). That is documented separately in the customer support thread and is not part of this issue.
aidevops.sh v3.14.83 plugin for OpenCode v1.14.33 with claude-opus-4-7 spent 6h 16m and 83,381 tokens on this with the user in an interactive session.