/** * AUTO-GENERATED — do not edit by hand. * Source: src/resources/troubleshooting-skill.md (the single source, distilled * from validated support resolutions). * Regenerate: `npm run gen-troubleshooting-guide` (runs automatically on `prebuild`). * * The troubleshooting guide exposed as an MCP resource + the backing CONTENT * for the `get_troubleshooting_guide` tool. */ export declare const TROUBLESHOOTING_GUIDE_URI = "sealmetrics://troubleshooting-guide"; export declare const TROUBLESHOOTING_GUIDE_NAME = "SealMetrics Troubleshooting Guide"; export declare const TROUBLESHOOTING_GUIDE_DESCRIPTION = "Validated symptom\u2192cause\u2192fix answers for the most common SealMetrics setup and data questions - tags firing before the tracker loads, lost conversions/microconversions, duplicate pageviews in SPAs or Tag Manager setups, channel rules that never match (traffic stuck in Unassigned), payment-gateway and login/SSO domains showing up as traffic sources, numbers that do not match another analytics tool, and live browser checks of whether the tag actually loads on a page."; export declare const TROUBLESHOOTING_GUIDE_CONTENT = "# SealMetrics Troubleshooting Guide\n\nYou are helping a SealMetrics user diagnose a setup or data problem. This guide\ncontains **validated support resolutions** \u2014 each entry is a real, recurring\nquestion answered symptom \u2192 cause \u2192 fix. Do not guess beyond it: follow the\nmatching entry, use the verification tools it lists to confirm with the user's\nreal data, and if nothing matches, say so and suggest contacting SealMetrics\nsupport (see the last section for what to include).\n\nFind the symptom first:\n\n| Symptom | Section |\n|---------|---------|\n| Console error `sealmetrics is not defined`, conversions/micros missing, events fired from GTM | 1 |\n| Pageviews or entrances look inflated (~2x) on a SPA, or content grouping missing on SPA navigations | 2 |\n| A channel rule is active but traffic still shows as Unassigned (or the wrong channel wins) | 3 |\n| Payment gateway (Redsys, etc.) shows up as a traffic source, or \"how do I bypass the gateway referrer?\" | 4 |\n| An excluded login / SSO domain still appears as a top traffic source | 4 |\n| SealMetrics numbers don't match GA4 / the e-commerce platform / another tool | 5 |\n| No data at all, or the pixel \"is not detected\" | 6 |\n| Need to check live whether the tag loads on a page, or why a URL is never measured | 7 |\n\n---\n\n## 1. Tags fire before the tracker loads (\"sealmetrics is not defined\")\n\n**Symptom**: console error `sealmetrics is not defined` (or\n`sealmetrics.micro is not a function`); conversions or microconversions never\narrive. Almost always a Tag Manager setup where an event tag fires on an early\ntrigger (e.g. before the page \u2014 or the tag loading the tracker \u2014 has run), or a\ntag injected via `document.write`.\n\n**Cause**: the SealMetrics tracker exposes one global API (`window.sealmetrics`)\nand event tags call it (`sealmetrics.micro(...)`, `sealmetrics.conv(...)`). If\nan event tag runs **before** the tracker script (`t.js`) has executed, the API\ndoes not exist yet \u2014 the call throws and that event is lost. (In SealMetrics\nv1 each event tag was autonomous and built its own request, so v1 setups never\nhit this; it surfaces when migrating to v2.)\n\n**Fix \u2014 the buffer stub (canonical answer).** Paste this tiny inline snippet so\nit runs **before everything else** \u2014 directly in the `
`, or as a GTM\nCustom HTML tag on the **Initialization \u2013 All Pages** trigger:\n\n```html\n\n```\n\nThe stub creates a queue: any `sealmetrics.micro(...)` / `sealmetrics.conv(...)`\ncall that fires before the tracker loads is buffered and replayed automatically,\nin order, the moment the real library starts. Result: no console errors, no\nlost events, and a single pageview (the tracker still loads exactly once).\n\nKey points when recommending it:\n\n- **No other change is needed.** Event tags keep calling\n `sealmetrics.micro(...)` exactly as they do now; the loader tag keeps its\n current trigger. The only addition is the stub, first of all.\n- **The stub is idempotent** (pasting it twice is a no-op thanks to the `||`\n guards) and weighs ~180 bytes gzipped.\n- **Payloads are built at call time** (URL, referrer, etc. are read live when\n the call happens), so events do not depend on any earlier initialization.\n- **Reject the two tempting alternatives**: (a) moving the *whole tracker* to an\n Initialization trigger is unnecessary \u2014 the stub already solves the ordering;\n (b) **tag sequencing** (firing the loader before every event tag) reloads the\n tracker several times per page and inflates pageviews. The stub avoids both.\n- **If a pageview is queued through the stub** (i.e. the site fires\n `sealmetrics({...})` manually), the tracker must be loaded with `?auto=0`,\n otherwise the drained pageview AND the automatic one both fire \u2192 duplicates.\n Canonical stub pattern: **stub + `t.js?...&auto=0`**. If only `micro`/`conv`\n calls go through the stub, the `auto` setting does not need to change.\n- **Iframe exception**: never inject the stub inside a sandboxed iframe (e.g.\n Shopify Web Pixels) \u2014 there, call `sealmetrics.conv()` / `sealmetrics.micro()`\n directly.\n- **Always use the `sealmetrics` global**, never the `sm` / `_sm` shorthands.\n Those two are best-effort aliases: the tracker only claims them if they are\n free, so during a v1\u2192v2 migration (v1 also registers `window.sm`) a tag\n calling `sm(...)` may silently hit the wrong tracker. Double-tagging v1+v2\n during a migration is safe as long as v2 tags call `sealmetrics(...)`.\n\n**Verify**: reload the page with `?debug=1` appended to the URL (events are\nlogged to the console), then confirm the event reached the backend with\n`verify_event_instrumented`, or check `get_microconversions_raw` /\n`get_conversions_raw` for the expected event.\n\n---\n\n## 2. Duplicate pageviews on SPAs / content grouping missing on SPA navigations\n\n**Symptom**: pageviews (and often entrances) roughly double on a single-page\napp; typically the site fires its own \"virtual pageview\" tags from GTM (History\nChange / virtualPageView triggers). Or: content grouping is set on manual\npageviews but SPA navigations still produce hits without it.\n\n**Cause**: the tracker natively auto-fires a pageview on **every SPA\nnavigation** (History API `pushState` / `replaceState` / `popstate`).\n`?auto=0` does **not** disable that \u2014 it only gates the *initial* pageview;\nthe SPA listeners stay active. So a site that also fires manual pageviews\ncounts every navigation twice: the automatic hit (no/static grouping) plus the\nmanual one (correct grouping).\n\n**Fix**: load the tracker with **`&spa=0`** to suppress the automatic SPA\npageview and keep only the manual ones.\n\n| `?spa=` value | Automatic pageview on SPA navigation |\n|---------------|--------------------------------------|\n| missing / empty / `1` | ON (default) |\n| `0` | OFF (manual SPA mode) |\n\n`auto` and `spa` are independent flags (all four combinations are valid). The\n**canonical Tag Manager pattern for full manual instrumentation with per-page\ncontent grouping** is:\n\n```\nstub (section 1) + t.js?id=...&auto=0&spa=0 + one sealmetrics({ group: '...' }) per page shown\n```\n\nYou fire the initial pageview and every SPA pageview yourself, exactly once\neach, always with the right grouping.\n\nCommon misconceptions to correct:\n\n- *\"The script fires the pageview and my tag fills in the group afterwards.\"*\n No \u2014 **every `sealmetrics({ group })` call IS a complete pageview hit** with\n its own content grouping. Nothing is enriched retroactively. That is exactly\n why auto + manual = double counting.\n- Automatic pageviews can only carry the **static** group from the script URL\n (`t.js?group=...`); they can never pick up a per-page group computed at\n runtime. Dynamic grouping requires manual pageviews (and therefore `spa=0`).\n- Keeping the loader and the pageview/event calls in **separate tags is fully\n supported** \u2014 duplication never comes from splitting tags; it comes from\n automatic and manual pageviews both firing.\n- **Timing**: fire the manual pageview **after** the URL change (the tracker\n updates its internal URL/referrer state on the History event itself). GTM's\n History Change trigger already fires after the change, so it is safe there.\n\n**Before enabling `spa=0`, check coverage**: with it, anything your own\ntriggers don't cover stops being measured \u2014 typical gaps are hash-only routing\n(`#/...`), AJAX checkout steps, and filter/pagination navigations. Extend the\ntriggers first, then flip `spa=0`.\n\n**Verify**: browse the SPA with `?debug=1` and count one pageview per\nnavigation; then compare pageview counts in `get_overview` before/after.\n\n---\n\n## 3. Channel rules that never match (traffic stuck in Unassigned)\n\n**Symptom**: a custom channel rule is active, priority looks right, but the\nmatching traffic still shows as Unassigned \u2014 or a lower-priority rule \"wins\".\nThis is almost never a publication or priority bug; it is nearly always one of\ntwo pattern-writing traps.\n\nHow matching actually works (same engine in the pixel, the dashboard tester and\nthe MCP tools):\n\n- Patterns are **RE2 regexes** (Go dialect \u2014 no lookahead/lookbehind, no\n backreferences), matched **unanchored** (substring search). An empty pattern\n is a wildcard.\n- The incoming `source` / `medium` / `campaign` values are **lowercased and\n trimmed** before matching, but **patterns are applied exactly as written**.\n- All non-empty patterns of a rule must match at once (AND).\n- Evaluation order: your account's custom rules first (by priority, 0\u20131000,\n highest first), then the built-in defaults. **The first rule that matches\n wins**; if none does, the hit is Unassigned.\n\nThe two traps:\n\n1. **Any uppercase letter in a pattern makes the rule dead.** The traffic\n arrives lowercased (`fb-sitelink`, `tradedoubler`, `app_phg`), so a pattern\n like `^FB-SiteLink$` or `(ADS|PaidSocial)` can never match \u2014 silently: the\n rule stays active, no warning is shown, and the traffic falls through to the\n next rule or Unassigned. **Write patterns entirely in lowercase.** (This\n also stays correct if pattern matching becomes case-insensitive in a future\n release.)\n2. **An unescaped `.` is \"any character\", not a dot.** A pattern like\n `(.brand)` requires *some* character before \"brand\", so the bare value\n `brand.com` will not match it. Since matching is already substring-based,\n prefer plain alternatives like `(brand|otherbrand)` over\n `^.*(.brand).*$`; escape real dots as `\\.` (e.g. `facebook\\.com`).\n\nOperational facts to set expectations:\n\n- Changes to rules propagate in **~5 minutes** (pixel cache refresh).\n- Classification happens **at collection time**: rule changes apply to **new\n traffic only** \u2014 historical hits are never reclassified.\n- The dashboard's **\"Test a visit\"** (Site settings \u2192 Channels) and the MCP\n tool `test_channel_rules` use the exact same engine as live classification \u2014\n always test the literal source/medium/campaign values before concluding a\n rule is broken. Pull real values to test with from `get_traffic_sources`.\n- CSV import in the dashboard is an **atomic replace-all** of the account's\n custom rules \u2014 never import an edited export without checking that no rules\n were added after that export. (The MCP's `import_channel_rules` is safer:\n drafts-only and dry-run by default.)\n- Via MCP, `create_channel_rule` / `update_channel_rule` only touch **inactive\n drafts** \u2014 publishing a rule is always a human action in the dashboard.\n\n**Diagnosis workflow**: `list_channel_rules` \u2192 spot uppercase letters or\nunescaped dots in the failing patterns \u2192 `test_channel_rules` with the exact\nreal values \u2192 fix the pattern (all lowercase) \u2192 test again \u2192 publish in the\ndashboard \u2192 confirm on new traffic after ~5 minutes with `get_channels` or\n`get_top_channels`.\n\n---\n\n## 4. Payment gateways (Redsys, etc.) and referrer attribution\n\n**Symptom**: the user wants to \"add the payment gateway as a passthrough\nreferrer\" so the sale is not attributed to the gateway \u2014 or asks why a small\n`redsys / payment` (or similar) row exists in their sources.\n\n**Answer**: common payment gateways (e.g. Redsys: `sis.redsys.es` and\nvariants) are already recognized **globally, out of the box** \u2014 there is\nnothing to configure. When the visitor returns from the gateway within a live\nsession, the visit keeps its **original attribution** (the conversion is\ncredited to the channel that brought the user; the gateway never appears as a\nsource).\n\nA small residual `