Known patterns¶
What we've learned about Wix → static replatform that must be folded into the pipeline so we don't relearn it on every site.
Each pattern: how it manifests, where it bites, and where the fix lives (or
needs to live) in the codebase. When lib/structure-gap.js flags a
mystery section, check this list before debugging from scratch.
When you spot a new pattern: add an entry here in the same session you fix it. A shipped fix without a catalogued pattern is half the value.
Hero / above-the-fold¶
Wix slideshow with no visible "next" button (auto-rotates)¶
Bites: capture-layout.js's slide enumerator clicked a next-button selector and returned only the active slide. Hero rendered as a single static frame.
Fix: lib/capture-layout.js#captureSlides — three-pass:
1. Query every <img> / [style*="background-image"] / <video> inside the
slideshow root. Wix often pre-renders all slides as siblings, just toggling
visibility — picks them up in one shot.
2. Try the next-button click flow (works on older Wix templates).
3. Auto-rotation watcher: poll the active slide for ~20s, collect new URLs
as the slideshow advances itself.
Status: shipped 2026-05-09.
Hero heading wrapped in <h3> / <h4> instead of <h1> / <h2>¶
Bites: Wix editors choose heading element by visual style, not semantics.
Hero matcher checked only h1/h2 → empty <h1></h1> slot on 4/5 garvanbay
service pages. Silent regression — the build emitted broken pages until
Gemini caught it.
Fix: lib/matchers/Hero.js#extract — heading lookup is `h1 > h2 > h3 > h4
large-font-p`. Subtext also looks at h4.
Status: shipped 2026-05-08 after Gemini verify-design pass surfaced it.
Page-aware hero overlay (single image vs slideshow / video)¶
Bites: subpage heroes use a single static image — needs a strong dark overlay so white heading text reads. Homepage heroes use slideshow / video which have their own visual dimming — strong overlay over those reads as a murky black band.
Fix: lib/assembler-fulldev/translate.js#translateHero infers
scrimIntensity: 'light' when video or slideshow >1, 'strong' for single
image, 'medium' as fallback. lib/components-v3/blocks/hero-4.astro
swaps three opacity tiers.
Status: shipped 2026-05-09.
Background video extraction without component render¶
Bites: Hero matcher captured backgroundVideo correctly — hero-4
component only ever rendered <Image>. Video silently dropped.
Fix: lib/components-v3/blocks/hero-4.astro branches on slides /
backgroundVideo / image priority. Video uses autoplay muted loop
playsinline with image.src as poster.
Status: shipped 2026-05-09.
Sections / matchers¶
TeamGrid false-positives on services¶
Bites: TeamGrid matcher fires at priority 75 on any section with 2+ content images and matching paragraph count. wfpainters services (farm painting, commercial cleaning, roof painting, tarmac restoration) hit this signal and rendered as 4 team-member cards instead of a service grid.
Fix needed: TeamGrid match() should require team-name patterns
(Firstname Lastname[, CREDS]) in card-text segments, not just
"image-with-text repetition". Falls back to ServiceGrid when no name pattern.
Status: open. Affects every FCR service page that uses image+text repetition for service offerings.
Checklist sections (heading + ✔-prefixed bullets)¶
Bites: USPBar/InfoCards both partially-matched and rendered nearly empty content (heading-only, or paired bullets as title+description cards).
Fix: lib/matchers/Checklist.js (priority 75 — beats both). Detects 50%+
of paragraphs starting with [✔✓☑], strips prefix, emits one item per
bullet. Renders via dedicated lib/components-v3/blocks/checklist.astro
(borderless inline list).
Status: shipped 2026-05-08.
TeamGrid heading regex eats credentials¶
Bites: First version matched Tom Holmes but stopped before
BBS, FCCA because [A-Z][a-z]+ doesn't allow an internal capital
(McNamara) and the credentials clause wanted \s+ not ,.
Fix: lib/matchers/TeamGrid.js#FIRST_NAME_RE accepts \p{L}'’- chars
within a name word and trailing [\s,]+[A-Z]{2,} groups for credential
strings (2+ caps rules out "S" from "Senior").
Status: shipped 2026-05-08.
TeamGrid DOM-order ≠ visual order (seniority)¶
Bites: Wix uses absolute positioning so DOM-order doesn't track visual order. Garvanbay's about page rendered Tom → Cathy → John → Orla, live shows Tom → John → Cathy → Orla.
Fix: Sort items by numeric prefix in image alt (001.jpg, 002.jpg
…) — FCR upload-order convention tracks seniority. Items without a numeric
prefix go last.
Better fix queued: sort by image y-position from per-page
capture-layout.js output. General solution; needs assembler-fulldev to
load <slug>.layout.json (currently only homepage is captured).
Status: alt-numeric shipped 2026-05-08; layout-y queued.
Multi-line address in Contact section¶
Bites: Live FCR contact pages render the address as a single <p> with
<br>-separated lines. Default cleanText flattens to one string, losing
the line breaks; the contact slot showed "Garvanbay Accounting 26A Parnell
Street Dungarvan Waterford Ireland" run-on.
Fix: lib/matchers/Contact.js#extract walks the address <p>'s child
nodes preserving <br> separators, yields addressLines: string[].
Translator emits structured <div class="contact-info-name"> + lines.
Status: shipped 2026-05-09.
Contact form recaptcha + Wix-internal hidden fields¶
Bites: Contact matcher surfaced every <input> including
g-recaptcha-response, hidden honeypots, and Wix-mangled names like
textarea_comp-l6.... The form rendered with junk fields.
Fix: lib/assembler-fulldev/translate.js#translateContact filters by
name regex (drop recaptcha/honeypot/empty-name/hidden/checkbox), maps
common field names → human labels (First name / Last name / Email /
Message). Falls back to a sane 4-field shape if extraction is too sparse.
Status: shipped 2026-05-09.
Phone/email as plain text instead of tel: / mailto:¶
Bites: Some FCR contact pages render contact info as styled rich-text
("Phone: 058 89555") not anchors. Contact matcher only pulled
<a href="tel:"> so phone went missing.
Fix: lib/matchers/Contact.js#extract — fallback regex scan for
IE-style phone patterns and email addresses in paragraph text when the
anchor pass yields zero.
Status: shipped 2026-05-09.
Map address fallback from footer¶
Bites: LocationMap matcher pulls address from the section's first
short <p> — flaky because the address often isn't in the section's prose
at all. Wix mapSrc is a parastorage URL that won't load outside Wix.
Fix: lib/assembler-fulldev/emit.js#applyCrossEntryBackfills looks up
the footer's "Address" column and joins the non-link entries to backfill
the LocationMap's address prop. Map-wcp embeds Google Maps via
maps.google.com/maps?q=...&output=embed (no API key).
Status: shipped 2026-05-09.
Components / pipeline¶
Floating CTA + Floating Social — components missing¶
Bites: Both translators emit a deferred passthrough referencing
component files that don't exist (floating-cta-wcp.astro,
floating-social-wcp.astro). Build fails with rollup import errors as
soon as the matchers fire.
Fix: Translators currently return component: null to skip. Real
fix: build minimal pinned-corner CTA component (FAB-style with phone
icon) and a multi-icon social rail.
Status: dropped at translate-time as a stopgap (2026-05-09). Real components TODO.
Map mapSrc points at parastorage¶
Bites: Wix's mapSrc extracted from iframes is a Wix-internal URL that
won't resolve outside their CDN.
Fix: Ignore extracted mapSrc; embed Google Maps via the no-key
maps.google.com/maps?q=ADDRESS&output=embed URL using the address text.
Status: shipped 2026-05-09.
Phase 1 captures desktop viewport only — mobile-only widgets miss¶
Bites: Wix mobile-only QuickActionBar (sticky bottom phone+email
strip) renders only at mobile widths. Phase 1 captures at 1440x900 →
QuickActionBar never enters our DOM extract.
Fix: Synthesise the bar from the TopBar entry's contact data.
lib/assembler-fulldev/emit.js injects <StickyMobileCta phone email />
at </body> whenever phone or email exists. lib/components-v3/blocks/sticky-mobile-cta.astro
hidden above 640px, reserves 3.25rem body padding-bottom on mobile.
Status: shipped 2026-05-08.
Multi-tone heading colour spans¶
Bites: Wix lets editors recolour individual words inside a heading via
inline <span style="color: #...">. Plain-text cleanText loses that;
"GARVANBAY ACCOUNTING" rendered as a single colour instead of the live
yellow-green + cyan two-tone.
Fix: lib/matchers/About.js#extractHeadingHtml walks heading children
preserving color / font-weight / font-style inline styles, sanitises
URL/parens/angle-brackets to block XSS, drops unstyled wrappers. Translator
prefers headingHtml over escaped plain text.
Status: shipped 2026-05-08.
Service-section accent leaking onto Hero / welcome / CTA-strip¶
Bites: [data-slot="section-prose"] :is(h1,h2,h3)::before/::after rule
applied to every section-prose, so once garvanbay's
--service-accent-above-display flipped to block, a yellow line
appeared above Hero, content-1, and CTA-strip headings too.
Fix: lib/assembler-fulldev/theme.js scopes accent rules to
.content-2 only. Service-heading-color rule too.
Status: shipped 2026-05-08.
Subpage routing — emit per-slug page + content files¶
Bites: First subpage build clobbered the homepage's index.astro and
content.ts.
Fix: lib/assembler-fulldev.js threads slug through meta.
lib/assembler-fulldev/emit.js writes src/pages/<slug>.astro and
src/data/content-<slug>.ts (homepage + slug='index'/'homepage' stay at
index.astro + content.ts).
Status: shipped 2026-05-08.
Cross-entry backfills must run before content.ts is serialized¶
Bites: Map address backfill mutated layoutMap inside
emitIndexAstro, which runs after emitContentFile. The mutation
never reached the runtime data.
Fix: lib/assembler-fulldev/emit.js#applyCrossEntryBackfills lifted
out and called at the top of emitProject, before any file writes. Any
future cross-entry prop derivation should go in there.
Status: shipped 2026-05-09.
Out-of-scope / open questions¶
Why Choose Us section absent from captured DOM¶
Bites: wfpainters live shows "Why Choose Us" content; nothing in our
captured DOM matches the wording. Either Wix lazy-renders it client-side
after Phase 1's load event, or it's ascii-encoded inside a sub-tree we're
not visiting.
Status: open. Need to: 1. Confirm presence on live (manual eyeball) 2. If present in source, find what tag wraps it and why our enumeration misses it 3. If JS-rendered, decide whether to wait longer in Phase 1 or add a second enumeration pass
Booking — Cal.com Teams require paid plan¶
Bites: chosen direction was Cal.com self-host for portfolio. Cal.com free tier doesn't support Teams (the multi-tenant primitive).
Status: deferred. Open alternatives — self-host Cal.com (AGPLv3 free
on our infra), schedule-x + custom backend, lightweight request form. See
memory/project_booking_solution.md.
Bottom CTA bar styling — bg/text per-site¶
Bites: Live garvanbay's mobile QuickActionBar is black/white. Other FCR sites may use brand colours. We hardcode black/white as portfolio default.
Status: open. Per-site override via theme.json field would generalise.
verify-design --selector body only sees above-the-fold¶
Bites: Gemini run only checks the hero region; below-the-fold issues
slip through. placement-check.js does full-page but is heavier.
Status: open. Either run multi-region per page (header / hero / each content section / FAQ / footer) or extend verify-design to scroll-capture.