Appearance
Customer account support guide
Diagnostic and troubleshooting reference for the customer-account surface — JSON-template + section migration (Dawn 14+ / Be Yours pattern), live JS form validation via <customer-form>, password show/hide + 4-tier strength meter + caps-lock + confirm-match (<customer-form> again), country/province dropdown rewiring (<country-province-selector>), visual order-status timeline (<order-timeline>), re-order CTA via LOCKED 06.6 window.CartAPI (<order-reorder>), print-receipt button + @media print stylesheet, refund panel + subscription card + discount enrichment, sidebar nav (consumes linklists['customer-account-main-menu'] with theme-default 5-item fallback), Privacy & data link routing to shop.privacy_policy.url, and the header first-name chip swap.
For when something does not work the way it should. The merchant-facing setup guide is at Customer account guide.
FAQ
Q1: Why doesn't my customer-account-main-menu show in the sidebar?
The sidebar reads linklists['customer-account-main-menu'].links first; if .size > 0, it renders one <a class='customers__sidebar-link'> per link. If .size == 0 (the linklist is missing OR provisioned-but-empty), the sidebar falls back to 5 hard-coded links (Profile / Orders / Addresses / Privacy & data / Sign out).
If neither branch shows:
- Run the console snippet. Inspect
sidebar present— should betrueon/accountand/account/orders/{id}. - Inspect the rendered DOM at
<aside class='customers__sidebar'>— should contain a<nav aria-label='{{ "customers.account.sidebar_nav_aria" | t }}'>child with anchor links inside. - If the sidebar is missing entirely, the section file may have been customized away — confirm
sections/main-customer-account.liquid(ormain-customer-order.liquid) renders{% render 'customer-sidebar-nav' %}. - To verify the linklist resolution, view-source on
/accountand grep forcustomers__sidebar-link— every anchor will be present in the source if the snippet is rendering correctly.
To provision the menu programmatically, see scripts/populate-customer-demo-data.mjs (the dev-store provisioner). For production stores, use Shopify admin → Online Store → Navigation → Add menu (handle: customer-account-main-menu).
Q2: Why does the country/province dropdown not populate?
The country/province dropdown is rewired through the <country-province-selector> Custom Element (replaces broken Shopify.CountryProvinceSelector reliance from shopify_common.js — a global the Uisce theme does not load).
If the province <select> stays empty after country change:
- Run the console snippet. Inspect
ce_country_province_selector— should betrue(the Custom Element registered). - Inspect
document.querySelector('country-province-selector'). Should return the host element wrapping<select id='AddressCountry'>and<select id='AddressProvince'>. - View-source on
/account/addressesand confirmdata-provincesJSON is on each country<option>in the country select. If absent, Liquid{{ all_country_option_tags }}may have been replaced by stale markup — restore via<select>{{ all_country_option_tags }}</select>pertemplates/customers/addresses.liquidprecedent. - Check the browser console for JS errors on the page — a SyntaxError in any other script can prevent
customer-account.jsfrom running.
The expected behavior:
- Country change → parses
data-provincesJSON from the active<option>→ populates province<select>with one<option>per province. - Country with
provinces.size == 0(Belgium / Singapore) → province<select>hides viahiddenattribute. - Locale-aware label updates:
country.zip_label(ZIP code / Postcode / PIN code / Postal code / Eircode) +country.province_label(State / Province / County / Region / Prefecture).
Q3: How do I test the cancel-order flow?
You can't, and that's intentional in v1.
The cancel-order CTA was investigated empirically in 06.8-RESEARCH.md §F-ORDER-09 + §Pitfall 2 and disconfirmed for the classic customer-accounts surface in 2026-04:
- Liquid
orderexposes ~40 properties; none indicate cancellable state. There is noorder.cancellable?filter. - There is no Storefront API mutation for customer-side order cancellation in classic accounts.
- New Customer Accounts handles cancellation via the Shopify-hosted portal at
account.{shop}.com(you can test cancellation there inNew onlyorClassic + newmode).
snippets/order-cancel-cta.liquid is a hard-guard hide — it renders nothing in v1. A cancel-order button is not available on classic accounts — Shopify does not expose a customer-side cancel API for the classic surface. This feature is planned for a future update.
If a merchant requests in-classic-account cancellation, route them to a mailto: link via shop.email (the merchant's contact email), or document in shop policies that cancellations require contacting support.
Q4: Why doesn't the first-name chip show in the header?
The chip swap happens in sections/header.liquid, inside the shop.customer_accounts_enabled gate. Two conditions must be true:
- Customer is signed in.
customerLiquid variable is truthy. Sign in via/account/loginfirst. - Viewport is desktop ≥ 768px. On mobile < 768px, the chip CSS reverts via
@media (max-width: 767px)inassets/customer-account.cssto keep visual symmetry with the cart icon.
If you're signed in on desktop but still see the icon:
- Run the console snippet. Inspect
chip present— should betrue. - Inspect
document.querySelector('.header__account-name'). Should return the<span>with the first-letter text. - Check
assets/header.cssis loaded globally (it should be —sections/header.liquid:1includes the stylesheet without template-gating). The chip CSS lives inheader.css(review-fix C1) so the chip styles every page where a logged-in customer browses.
The chip text uses customer.first_name | default: customer.email | first: 1 | upcase — single uppercase letter. If the customer record has first_name set, you see the first letter of that; otherwise the first letter of the email local-part. (Example: tine-customer@sionnach.solutions with no first_name → T.)
Q5: Re-order CTA isn't adding to cart
The <order-reorder> Custom Element calls window.CartAPI.add({ items: [item] }) per line in a sequential Promise.reduce chain (Strategy A; per-line loop, not atomic batch — review-fix C6). Three possible failure modes:
window.CartAPIis undefined. The LOCKED 06.6 cart-api.js failed to load. Run the diagnostic snippet → inspectcartAPI— should betrue. Iffalse, the cart-api.js script is missing or blocked.data-line-itemsattribute is empty or invalid JSON. View-source the order-detail page → grep for<order-reorder→ confirm thedata-line-itemsattr contains a valid JSON array of{id, quantity, properties?, selling_plan?}objects.- All variants are unavailable. The CE skips lines whose variant returns 422 from
/cart/add.js. If all lines skip, the toast shows an error message (role='alert'). Common causes: products archived, variants deleted, inventory at 0 withpolicy=deny. View the order detail page; the line-items table should still render the names. Compare againstProductsadmin → confirm each variant ID still exists with available inventory.
The expected flow on click:
- Button disables + shows spinner via CSS
::afterrotating border. - Per successful line:
cart:addevent dispatched ondocumentper LOCKED 06.6 contract. - After loop:
cart-drawer.open()called once viawindow.UisceCartDrawer.open()ordocument.querySelector('cart-drawer').open()fallback. - Toast composed: all-success / partial-success (with
{added}/{skipped}substitution) / all-skip.
Q6: Why does my order timeline collapse to a single Cancelled card?
Intentional. When order.cancelled_at is truthy, the order-timeline snippet collapses to a single Cancelled state with order.cancel_reason_label. The Placed / Confirmed / Fulfilled steps are suppressed because they no longer apply.
If you see this on a non-cancelled order, the snippet may have misread state — open the diagnostic snippet, inspect ce_order_timeline (should be true), and view-source the order-detail HTML for data-state='cancelled' on the timeline element. If the data-state is wrong, the Liquid state-derivation has a bug — file an issue with the order ID, financial_status, fulfillment_status, and cancelled_at value.
Q7: Privacy & data link 404s
The sidebar Privacy & data link resolves at render time:
- Primary:
{{ shop.privacy_policy.url }}— resolves when you have a published policy. - Fallback:
/policies/privacy-policy— Shopify auto-resolves this route IF you have a privacy policy published.
If the link 404s, you have no published privacy policy. Shopify admin → Settings → Policies → Privacy policy → write or paste your policy → Save. The link will resolve on the next storefront request automatically.
Q8: Recovery email-sent surface doesn't show
The customer-recovery-email-sent snippet renders distinct success messaging on /account/login?customer_posted=true (the URL Shopify redirects to after a successful recover-password POST). If you see a silent reload instead:
- Confirm the URL has the
?customer_posted=truequery param after submitting/account/recover-password. - View-source on the redirect target — confirm
{% if recover_email_sent %}{% render 'customer-recovery-email-sent' %}{% endif %}resolves correctly. - Check the section file
sections/main-customer-login.liquidfor the conditional gate. If a previous customization removed it, restore.
Q9: Why is the form submitting without showing inline errors?
Two possible causes:
- JavaScript is disabled or
<customer-form>failed to upgrade. The progressive-enhancement design ensures the form submits server-side regardless — Shopify's{{ form.errors | default_errors }}summary still renders above the form. But the inline per-field errors require JS. Run the diagnostic snippet → inspectce_customer_form— should betrue. - The form is missing the
<customer-form>tag. Custom merchant Liquid customization may have unwrapped the form. View-source the page and grep for<customer-form— should wrap the<form action='/account/login'>(or/register,/recover, etc.).
The expected behavior:
- On blur of
input[required]orinput[type='email']— validators run; aria-invalid + role=alert sibling visible on failure. - On submit failure — first errored field receives focus within
requestAnimationFrame; summary<div role='alert'>renders above form; per-field errors persist. - No-JS path — server-side errors render via Shopify's default summary.
Q10: Caps-lock warning isn't appearing
The caps-lock warning uses KeyboardEvent.getModifierState('CapsLock') on keydown / keyup events when a password input has focus. Browser-native; no flag setting required.
If the warning doesn't show:
- Confirm Caps Lock is actually on (toggle a
tkeydown — typingTconfirms). - Confirm focus is in a password input. Click into one before pressing Caps Lock.
- Run the diagnostic snippet → inspect
ce_customer_form— should betrue. - Inspect
document.querySelector('.customer-password__caps-warning'). Should return the<p role='status'>element. If absent in the rendered DOM, Plan 02'scustomer-password-fieldsnippet may not have been rendered for this field.
The warning is a role='status' element (polite live-region) — screen readers announce it on appearance.
Q11: Strength meter shows wrong tier
The strength heuristic is class-count + length:
| Tier | Conditions |
|---|---|
| weak | length < 6 OR fewer than 2 character classes (letter / digit / sym) |
| fair | length >= 6 AND 2 character classes |
| good | length >= 8 AND 3 character classes |
| strong | length >= 12 AND 4 character classes (letter + digit + symbol + Caps) |
<div class='customer-password__strength' role='progressbar' aria-valuemin='0' aria-valuemax='4' aria-valuenow='{0|1|2|3|4}'> — aria-valuenow updates as you type (0=empty, 1=weak, 2=fair, 3=good, 4=strong). data-tier='{weak|fair|good|strong}' attribute drives the CSS gradient fill.
If the meter doesn't update as you type, the <customer-form> Custom Element didn't upgrade — see Q9.
Q12: Order-detail page on RTL preset shows the timeline left-to-right instead of right-to-left
The RTL override [dir='rtl'] .order-timeline { flex-direction: row-reverse; } lives in assets/customer-account.css. The <html lang> and <html dir> attributes must be set correctly:
- Confirm the storefront has the RTL locale published (Shopify admin → Settings → Languages → Add language → Arabic / Hebrew / Persian / Urdu).
- Confirm the URL uses the locale path (e.g.,
/ar/account/orders/{id}). - Inspect
<html>in DevTools → Elements panel —dir='rtl'should be set. - Run the diagnostic snippet → inspect the inline DOM observation for the timeline element.
The RTL override only fires when dir='rtl' is set on <html> (or any ancestor of the timeline). Other RTL adjustments in 06.8: sidebar moves to right via inset-inline-start: 0; strength meter fills from right via logical positioning; eye icon, caps-lock warning, and first-name chip use logical inline padding.
Decision tree: "Why is X not working?"
Sidebar nav not showing on /account
- Run console snippet → inspect
sidebar present. Iffalse, the section is missing the<aside class='customers__sidebar'>wrapper — confirmsections/main-customer-account.liquid(or order/addresses) renders{% render 'customer-sidebar-nav' %}inside the wrapper. - If
sidebar presentistrue, but no links inside, the linklist is empty AND the theme-default fallback also failed. View-source for<nav aria-label; should always render either the linklist branch or the 5-item fallback. If neither, the snippet has a bug. - Inspect view-source for
customers__sidebar-link— count anchors. Theme-default = 5; merchant-provisioned = however many menu items.
Country dropdown not populating after country change
- Console snippet →
ce_country_province_selector— must betrue. document.querySelector('country-province-selector')— must return host element.- View-source on
/account/addresses→ grep fordata-provinces— should be on each country<option>. - Browser console → check for JS errors on page (any error halts subsequent script execution).
Re-order CTA not adding to cart
- Console snippet →
cartAPI(must betrue) ANDce_order_reorder(must betrue). - View-source the order-detail page →
<order-reorder data-line-items='[...]'>— confirm valid JSON indata-line-items. - DevTools → Network → click Re-order → expect N successful POST
/cart/add.js(one per line). - If
cartAPIisfalse:assets/cart-api.jsfailed to load (LOCKED 06.6). Re-push theme viascripts/push-presets.sh.
First-name chip shows email initial instead of first name
The chip uses customer.first_name | default: customer.email | first: 1 | upcase. If you see the email initial:
- The customer's first_name field is empty in their Shopify customer record.
- To fix: Shopify admin → Customers → [customer] → Edit → set First name → Save.
- Reload the page; the chip should now show the first-name initial.
This is intentional fallback behavior — a chip with ? (or no chip) is worse than the email initial. The aria-label is aria-label='{{ "customers.account.welcome_aria" | t: name: chip_name }}' which carries the full name (or email) for screen readers.
Form submitting without inline errors
- Console snippet →
ce_customer_form— must betrue. - View-source the form → must be wrapped in
<customer-form>. - If JS disabled, server-side errors still render via Shopify's
{{ form.errors | default_errors }}(progressive enhancement intentional). - Browser console → check for any error from
customer-account.js; the file is template-gated tocustomers.*so should only load on customer routes. If the script is missing on a customer route, checksnippets/scripts.liquidfor the template-gate.
Sidebar Privacy & data link 404s
- Shopify admin → Settings → Policies → Privacy policy. Empty? Publish a policy.
- Saved? Reload
/account. The link should resolve to/policies/privacy-policy(or your custom URL if Shopify exposedshop.privacy_policy.url). - View-source the sidebar nav → grep for
rel='nofollow'. The link should always emitrel='nofollow'per D-12.
Customer-account.js missing on /account/login
The bundle is template-gated:
liquid
{%- if template contains 'customers' -%}
<script src='{{ "customer-account.js" | asset_url }}' defer></script>
{%- endif -%}If missing on a customer route:
- View
snippets/scripts.liquid— confirm the template-gate is present (review the customer-account.js include). - Confirm
templateLiquid variable containscustomersfor the page (e.g.,customers/login,customers/account,customers/order). View<body class='{{ template | replace: ".", "_" | replace: "/", "_" }}'>— should includecustomers_loginor similar. - If the page renders without the customer-account.js, you may be on a custom template that doesn't match the gate — adjust the gate condition or rename the template handle.
Console snippet (paste-into-DevTools)
Paste this into your browser DevTools Console on any customer-account route (/account, /account/login, /account/orders/{id}, /account/addresses, /account/activate/..., /account/reset/...). Produces a single console.table of customer-account state — safe, read-only, and adds zero theme code.
javascript
(function () {
const probe = {
cartAPI: typeof window.CartAPI !== 'undefined',
ce_customer_form: typeof customElements !== 'undefined' && customElements.get('customer-form') !== undefined,
ce_country_province_selector:
typeof customElements !== 'undefined' && customElements.get('country-province-selector') !== undefined,
ce_order_timeline: typeof customElements !== 'undefined' && customElements.get('order-timeline') !== undefined,
ce_order_reorder: typeof customElements !== 'undefined' && customElements.get('order-reorder') !== undefined,
sidebar_present: document.querySelector('.customers__sidebar') !== null,
sidebar_nav_aria: document.querySelector('.customers__sidebar nav[aria-label]') !== null,
sidebar_links_count: document.querySelectorAll('.customers__sidebar-link').length,
sidebar_active_link:
(document.querySelector('.customers__sidebar-link--active') || {}).getAttribute &&
document.querySelector('.customers__sidebar-link--active').getAttribute('href'),
privacy_link_present: document.querySelector('.customers__sidebar-link[rel="nofollow"]') !== null,
privacy_link_href:
(document.querySelector('.customers__sidebar-link[rel="nofollow"]') || {}).getAttribute &&
document.querySelector('.customers__sidebar-link[rel="nofollow"]').getAttribute('href'),
chip_present: document.querySelector('.header__account-name') !== null,
chip_text: (document.querySelector('.header__account-name') || {}).textContent,
customer_form_present: document.querySelector('customer-form') !== null,
country_province_selector_present: document.querySelector('country-province-selector') !== null,
order_timeline_present: document.querySelector('order-timeline') !== null,
order_reorder_present: document.querySelector('order-reorder') !== null,
order_reorder_data_line_items_length: (() => {
const el = document.querySelector('order-reorder');
if (!el) return null;
const raw = el.getAttribute('data-line-items');
try {
return Array.isArray(JSON.parse(raw)) ? JSON.parse(raw).length : 'invalid-shape';
} catch (e) {
return 'invalid-json';
}
})(),
refund_panel_present: document.querySelector('.order-refund-panel') !== null,
subscription_card_present: document.querySelector('.order-subscription-card') !== null,
print_button_present: document.querySelector('.order__print') !== null,
tracking_link_present: document.querySelector('.order__track-link') !== null,
cancel_cta_present: document.querySelector('.order__cancel') !== null,
customer_form_aria_invalid_count: document.querySelectorAll('customer-form [aria-invalid="true"]').length,
customer_form_role_alert_count: document.querySelectorAll('customer-form [role="alert"]').length,
password_strength_meter_present:
document.querySelector('.customer-password__strength[role="progressbar"]') !== null,
password_strength_aria_valuenow:
(document.querySelector('.customer-password__strength[role="progressbar"]') || {}).getAttribute &&
document.querySelector('.customer-password__strength[role="progressbar"]').getAttribute('aria-valuenow'),
password_show_hide_toggle_present: document.querySelector('.customer-password__toggle') !== null,
caps_lock_warning_present: document.querySelector('.customer-password__caps-warning') !== null,
success_toast_present: document.querySelector('.customer-success-toast, [data-customer-toast]') !== null,
success_toast_role:
(document.querySelector('.customer-success-toast') || {}).getAttribute &&
document.querySelector('.customer-success-toast').getAttribute('role'),
delete_form_count: document.querySelectorAll('form[data-confirm-delete]').length,
page_template: document.body.className.match(/(?:^| )(template-customers[a-z-]*)/) || ['', 'unknown'][1],
path: window.location.pathname,
customerAccountsModeHint: window.location.pathname.startsWith('/account')
? 'classic-template-or-redirect'
: 'not-account-route',
uisceStrings_size: typeof window.uisceStrings === 'object' ? Object.keys(window.uisceStrings).length : 0,
html_dir: document.documentElement.dir || 'ltr',
html_lang: document.documentElement.lang || 'en',
};
console.table(probe);
console.log('Full DOM probe:', probe);
return probe;
})();This dumps the customer-account surface state in a single console.table. Send the output to support if filing an issue.
What to look for in the output:
cartAPI === true— LOCKED 06.6 cart API loaded; required for re-order CTA.ce_customer_form / ce_country_province_selector / ce_order_timeline / ce_order_reorder === true— all 4 Custom Elements registered (gated tocustomers.*templates).sidebar_present === trueon/accountand/account/orders/{id}(sidebar wraps both account dashboard + order detail).sidebar_links_count= 5 in theme-default fallback; ≥ 5 in linklist-provisioned branch (your menu's item count).privacy_link_present === trueANDprivacy_link_hrefresolves to your published privacy policy URL (NOT 404).chip_present === truewhen signed in on desktop ≥ 768px;chip_textshould be a single uppercase letter.password_strength_meter_present === trueon/account/register,/account/activate/...,/account/reset/...(NEW password fields only — NOT login).password_strength_aria_valuenowupdates from 0 → 4 as you type (run the snippet after typing in the password field to see the live value).customer_form_aria_invalid_countincrements when validation errors fire.customer_form_role_alert_countincrements when summary or per-field errors fire.cancel_cta_present === falsealways — D-09d hard-guard hide; classic accounts platform constraint.tracking_link_present === trueONLY when the order has a fulfillment with tracking_url; D-08c styled link button (D-08b widget DISCONFIRMED).refund_panel_present === trueONLY whenorder.refunds.size > 0.subscription_card_present === trueONLY whenitem.selling_plan_allocationtruthy.delete_form_count > 0on/account/addresses; each address row has its own<form data-confirm-delete>.html_dir === 'rtl'on Arabic / Hebrew / Persian / Urdu locales; LTR otherwise.uisceStrings_size > 0aftercustomer-account.jsboots — the runtime translation registry has 10+ customer-account-specific keys.
Embedded diagnostic tool: A full embedded diagnostic UI is planned for a future update. The console snippet above provides the core diagnostic value.
curl cheat-sheet
Use these one-liners to verify your live store's customer-account flow. Replace {store} with your storefront domain (e.g., tine.sionnach.solutions) and capture the storefront-password cookie first if your store is a dev store.
For dev stores, capture the auth cookie via:
bash
curl -sI "https://{store}/password" -d "form_type=storefront_password&password={store}" \
-L -c cookies.txt | head -5
# Then add `-b cookies.txt` to all subsequent curl commands belowThe dev-store storefront password matches the preset name (uisce / gaoth / talamh / tine / neart).
For live production stores (no password protection), no cookie is needed.
Platform constraint: Shopify dev stores cannot disable password protection (per memory
reference_dev_store_password_constraint). URL-only scrapers (Meta / LinkedIn) cannot reach dev stores; HTML-paste validators (GRRT) still work via auth curl. Production stores are unaffected.
Verify classic mode vs new-customer-accounts mode dispatch
bash
curl -sILw '%{http_code} %{redirect_url}\n' "https://{store}/account/login"Expected:
- Classic only mode:
200(or302to a Liquid-rendered redirect chain that ends200); your theme renderstemplates/customers/login.json+sections/main-customer-login.liquid. - New only mode:
302toaccount.{shop}.com/...(Shopify-hosted UI); your classic templates are dead code. - Classic + new mixed mode:
200(classic templates serve/account/*);account.{shop}.comis the new-mode handoff destination.
If you see 403, the dev store storefront password gate is intercepting — re-capture cookies (above).
Verify form-action target on classic login
bash
curl -sb cookies.txt "https://{store}/account/login" | grep -oE '<form[^>]+action="[^"]*"'Expected: <form ... action="/account/login" ...> rendering against the classic template. The form has accept-charset='UTF-8' per Shopify's {% form 'customer_login' %} Liquid emit.
Verify privacy policy URL resolution
bash
curl -sILw '%{http_code} %{redirect_url}\n' "https://{store}/policies/privacy-policy"Expected:
- Policy published:
200— Shopify renders the policy page (not theme-owned; Shopify's policy template). - No policy published:
404— Shopify hasn't auto-resolved the route. The sidebar Privacy & data link 404s (the cleanest fix is to publish a privacy policy in Shopify admin → Settings → Policies; see Customer account guide).
Verify order-detail page renders for a known order
bash
ORDER_ID=$(curl -sb cookies.txt "https://{store}/account" | grep -oE '/account/orders/[0-9]+' | head -1)
echo "First order ID found: $ORDER_ID"
curl -sb cookies.txt "https://{store}${ORDER_ID}" | head -200Expected: HTML containing <order-timeline data-state='...'>, <order-reorder data-line-items='[...]'>, <aside class='customers__sidebar'>, optionally <section class='order-refund-panel'> (when order.refunds.size > 0), optionally <aside class='order-subscription-card'> (when item.selling_plan_allocation), and the print-receipt button <button class='order__print'>.
Verify addresses page renders the country-province-selector host
bash
curl -sb cookies.txt "https://{store}/account/addresses" | grep -oE '<country-province-selector[^>]*>'Expected: at least one <country-province-selector ...> host element in the source. Inside, the <select> for country has data-provinces JSON attribute on each <option>.
Verify customer-account-main-menu linklist resolves
bash
curl -sb cookies.txt "https://{store}/account" | grep -oE '<a[^>]+class="customers__sidebar-link[^"]*"' | head -20Expected: 5 links (theme-default fallback) OR however many your customer-account-main-menu linklist has. Each anchor includes customers__sidebar-link class.
Verify chip CSS is loaded on every page
bash
curl -sb cookies.txt "https://{store}/" | grep -oE 'header.css\?[^"]*'
curl -sb cookies.txt "https://{store}/" | grep -oE '\.header__account-name'Expected: header.css?... link tag + the chip class is referenced in the global header CSS (per review-fix C1; chip CSS lives in header.css so it styles every page where a logged-in customer browses, not gated to customers.* templates).
Verify customer-account JS bundle template-gating
bash
# On a customer route — bundle should be present
curl -sb cookies.txt "https://{store}/account/login" | grep -oE 'customer-account\.js\?[^"]*'
# On a non-customer route — bundle should be ABSENT
curl -sb cookies.txt "https://{store}/" | grep -oE 'customer-account\.js\?[^"]*'Expected: present on /account/*, absent on / (template-gated to customers.* per snippets/scripts.liquid). The header chip styling lives in header.css (loaded globally) so the chip works on any page even though customer-account.js is only on customer routes.
Round-trip test the demo customer credentials
bash
# Sign in via classic form-post
curl -sb cookies.txt -c cookies.txt -L \
-d "form_type=customer_login&utf8=%E2%9C%93&customer[email]={preset}-customer@sionnach.solutions&customer[password]={preset}-customer-2026" \
"https://{store}/account/login" -o /dev/null -w '%{http_code} %{url_effective}\n'
# After successful sign-in, dashboard should be reachable
curl -sb cookies.txt "https://{store}/account" | grep -oE '<h1[^>]*>[^<]*</h1>' | head -1Expected:
- Sign-in POST:
200(or302chain to/account). - Dashboard
<h1>:My accountor your custom localized account heading. - If you see
Sign inheading after the POST, credentials failed (verify against demo customer credentials).
In New only mode, /account/login 302-redirects to account.{shop}.com and the classic-template form-post flow is bypassed — the credentials work against the Shopify-hosted UI but not the theme's classic flow.
Common reviewer-flagged gaps and the F-finding closures
Theme Store reviewers often flag classic customer-account surfaces with these comparison gaps:
| Reviewer flag | F-finding | 06.8 closure |
|---|---|---|
| "Form errors are summary only; no per-field state" | F-FORM-04 | Closed by D-04 — <customer-form> ships per-field aria-invalid + role=alert + focus-on-error. |
| "Province dropdown is empty" | F-ADDR-10 | Closed by D-10 — <country-province-selector> Custom Element replaces broken Shopify.CountryProvinceSelector. |
| "No order-status visualization" | F-ORDER-07 | Closed by D-07 — <order-timeline> 3-step horizontal timeline. |
| "No re-order CTA on order detail" | F-ORDER-08a | Closed by D-08a — <order-reorder> consumes LOCKED 06.6 window.CartAPI. |
| "No print receipt" | F-ORDER-08d | Closed by D-08d — window.print() button + @media print stylesheet. |
| "No refund or subscription enrichment on order detail" | F-ORDER-09a + F-ORDER-09c | Closed by D-09a + D-09c — refund panel + subscription card snippets. |
| "No header personalization for logged-in shoppers" | F-HEADER-13 | Closed by D-13 — first-name chip swap. |
| "Privacy/data link is missing or 404s" | F-NAV-12 | Closed by D-12 — sidebar Privacy & data link routes to shop.privacy_policy.url (or /policies/privacy-policy fallback) with rel='nofollow'. |
| "Customer-account-main-menu doesn't render" | F-NAV-18 | Closed by D-18 — sidebar consumes linklist OR theme-default 5-item fallback (always renders). |
"Templates are raw .liquid; no theme-editor visibility" | F-ARCH-01 | Closed by D-01 — JSON+section migration (Dawn 14+ / Be Yours pattern). |
File a GitHub issue with the console-snippet output + browser/OS + preset + customer-accounts mode for further assistance.
Hard rules referenced throughout
CLAUDE.md hard rules apply across the customer-account surface:
- Custom Elements:
disconnectedCallbackmirrorsconnectedCallbackand removes document-level listeners. All 4 Custom Elements (<customer-form>,<country-province-selector>,<order-timeline>,<order-reorder>) install no-op document listeners for spy-able removal symmetry. - Scripts are
defer(neverasync).customer-account.jsis loaded via<script defer>insnippets/scripts.liquid; template-gated tocustomers.*. - Every user-facing string
{{ 'key' | t }}-routed. 50-locale runtime + schema parity enforced bytests/unit/{runtime,schema}-locale-english-leakage.test.js. The console snippet'suisceStrings_sizeprobe verifies the runtime translation registry is populated. - LOCKED 06.6 cart-coordination contracts preserved.
<order-reorder>consumes onlywindow.CartAPI.add()(LOCKED public surface); emitscart:addper successful line; opens cart-drawer once viacart-drawer.open(). ZERO modification toassets/cart-api.js. - No TypeScript, no bundler, no CSS preprocessor. Plain ES2015+ JS, plain CSS, plain Liquid only.
Need more help?
- For deep diagnostic beyond what the console snippet captures, a full embedded diagnostic UI is planned for a future update.
- File a GitHub issue with the console-snippet output + browser/OS + preset + customer-accounts mode (Classic only / Classic + new / New only).
- See the Customer account guide for configuration walkthrough.
Cross-references
- Customer account guide — settings catalog + per-preset niche-quicklink slot + privacy policy publishing requirement + demo-customer credentials + testing flows + platform notes + pre-submission checklist
- Cart support — Phase 06.6 cart support; LOCKED
window.CartAPIcontract referenced by<order-reorder> - Search support — Phase 06.7 predictive-search support (token diagnostics, typed-tab issues)
- Blog and article support — Phase 06.7 article reading experience support (audio playback, structured data, author bio, social share)
- Header support — Phase 06.3 header support (
<shopify-account>Web Component, customer_account_menu wiring) - PDP support — Phase 06.4 product page support
- PLP support — Phase 06.5 collection page support
- Translations (50 locales)
- Support form — submit a ticket if the above does not resolve your issue