feat(analytics): instrument the add-network conversion and document how the site is measured - #2300
Conversation
687f028 to
f7505f6
Compare
|
The GA4 destination is now wired into
Page-view parity is safe to merge. Two things are still outstanding and neither blocks it:
Also in this PR since the last review: |
Swap the bare ga4 integration in docs.json for GTM (tagId placeholder until the container is created), fire dataLayer events from AddNetworkButton for the add-network conversion, and document the measurement setup in ANALYTICS.md: GTM/GA4 runbooks for humans and a Cloudflare AI Crawl Control runbook for bot/agent traffic, which no client-side tag can see. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ANALYTICS.md is internal maintainer documentation, deliberately not in docs.json navigation. The orphan check began enforcing when #2293 removed its continue-on-error, so it now fails on the same category of file it already excludes for README.md, AGENTS.md and CLAUDE.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ook steps Owners are teams rather than individuals: this repository is public, and .github/CODEOWNERS already assigns /docs.json to the same team. Also corrects three steps the doc described as pending that are done: the GTM container exists and docs.json carries its ID, the Google tag now resolves G-0CXEKQ81V2 with a history-change trigger, and docs.celo.org is already proxied through Cloudflare. Records the tag-ID failure mode, since it produces no error anywhere, and notes that cf-cache-status: HIT widens the Cloudflare-vs-GA4 delta for reasons unrelated to bots. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MCP requests are not invisible to Cloudflare — they traverse the proxy and return cf-ray, so volume is measurable by path today. The real blind spot is query content, which no layer here can show. Also sources the Starter-plan claim to #2250 rather than asserting it, and records that Pro was evaluated and declined there. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…exists
The assistant is instrumented on both sides, in celo-org/docs-ai-assistant:
widget.js emits seven GA4 events through the page's existing window.gtag,
and the chat route pushes {question, citedUrls, answered, ...} onto the
Upstash Redis list docs-assistant:questions, trimmed to the last 10,000.
This file previously said that telemetry did not exist.
Records the two real gaps: the assistant's event parameters are not in the
custom-dimension list, so they arrive but cannot be reported on; and
track() is a no-op unless window.gtag is defined, which needs re-checking
after the GA4-to-GTM swap.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
e80cd79 to
9a476f7
Compare
palango
left a comment
There was a problem hiding this comment.
The diff itself is fine, and page-view collection keeps working after the swap. What stops me approving is that ANALYTICS.md ticks off four things as confirmed that aren't true, and the swap turns off the assistant's telemetry without the PR mentioning it.
I checked the claims by loading three pages in headless Chrome and capturing the /g/collect hits GA4 actually sends: the live site as it is today, a page with only a plain gtag.js snippet for G-0CXEKQ81V2, and a page with only the GTM-NP9GP2BT snippet. Each hit carries a session sequence number (_s), so duplicates below are distinct hits, not retries.
| Setup | page_view per load |
page_view per pushState |
|---|---|---|
Live site today (integrations.ga4) |
2 | 2 |
| Plain gtag.js, no GTM | 1 | 1 |
GTM-NP9GP2BT as published |
1 | 2 |
1. The History Change trigger double-counts every in-site navigation
GA4 enhanced measurement already sends a page_view on pushState for this stream. The plain gtag.js page, with no GTM and no history trigger, sends exactly one per navigation, with dr set to the previous page. Under the container the Google tag fires a second time on gtm.historyChange, so each navigation produces two hits: one from the re-run config about 2 s after the push (no dr), one from enhanced measurement about 5 s later (with dr).
ANALYTICS.md:47 says the opposite ("without the history trigger only the first page view is counted") and marks it done. The History Change trigger needs to come off the Google tag, and that line needs to change. The live site already double-counts today through Mintlify's own integration, so this isn't a regression, but as written the runbook tells the next maintainer to keep the wrong setup.
2. The swap removes window.gtag, and the assistant goes quiet
On the live site typeof window.gtag is function. On a page carrying only the GTM container it is undefined. public/widget.js in docs-ai-assistant only tracks if (typeof window.gtag === 'function'), so all seven assistant_* events stop the moment this merges. I also tried the obvious workaround, a gtag(){ dataLayer.push(arguments) } shim on the GTM page followed by gtag('event', 'assistant_opened'). No hit went out. A shim in the widget won't rescue it.
ANALYTICS.md:71 says the global "should still be provided by GTM's Google tag". It isn't. The PR should say the swap breaks assistant events until #2307 lands, and that item needs rewording. Landing the widget change first would avoid the gap altogether.
3. docs.celo.org is not behind Celo's Cloudflare zone
From both 1.1.1.1 and 8.8.8.8, docs.celo.org is a plain CNAME to cname.vercel-dns.com, and the A records behind it (76.76.21.x, 66.33.60.x) belong to Vercel. A proxied record returns Cloudflare IPs and hides the CNAME target; a visible CNAME to Vercel means the record is DNS-only. The cf-ray and cf-cache-status headers come from somewhere upstream of Vercel, not from the celo.org zone, so AI Crawl Control on that zone would see nothing.
ANALYTICS.md:82 and :84 should be unticked and the "already in place" sentence dropped. The claim at ANALYTICS.md:97 that MCP volume "is visible in Cloudflare's HTTP analytics" goes with it.
4. ANALYTICS.md will be a public page
ANALYTICS.md:3 says the file isn't public because it isn't in navigation. Mintlify serves every .md under the content root regardless: https://docs.celo.org/AGENTS and /CLAUDE are live today with full content, and /ANALYTICS will be too. Nothing in the file is secret, both IDs are already in page source, but the sentence is wrong. Either accept that it's public and say so, or move the content somewhere Mintlify won't serve.
Smaller things
- The published container still holds only the Google tag and the history listener. Pushing
add_network_clickon the GTM page produced no hit, so theAddNetworkButton.jsxevents ship dormant. That matches your comment; just confirming it's still true at the current head. - The PR checklist still shows the ownership table as open. It's filled in as of e7fe61a.
Everything else I checked holds up. The container carries G-0CXEKQ81V2 and sends a page view on load. integrations.gtm.tagId matches Mintlify's schema. The widget event names, the Redis list and its trim size match docs-ai-assistant. The plan and price claims match #2250. Google's channel-group doc lists exactly ChatGPT, Gemini, DeepSeek, Copilot and Grok for the built-in AI Assistant channel. The snippet change is clean.
- The History Change trigger double-counts. GA4 enhanced measurement already sends a page_view on pushState, so the trigger makes every in-site navigation count twice. The runbook said the opposite and ticked it off; it is now unticked with the trigger to be removed from the container. - The GTM swap removes window.gtag, which silences all seven assistant_* events until the widget pushes to dataLayer directly. A hand-rolled gtag shim does not rescue it. Stated plainly instead of as a re-verify step. - docs.celo.org is not behind the celo.org Cloudflare zone. It is a DNS-only CNAME to cname.vercel-dns.com with Vercel addresses behind it; a proxied record would return Cloudflare addresses and hide the target. The cf-ray headers come from upstream of Vercel. Runbook 3 is now conditional on a DNS change, and the claim that MCP volume is visible goes with it. - Mintlify serves every Markdown file under the content root, so this file will be public at /ANALYTICS, as /AGENTS and /CLAUDE are today. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
All four are corrected. I re-verified the two I could check from here rather than taking them on trust, and both hold. 1. The History Change trigger double-counts — unticked and reversedThe runbook said the opposite of what is true and had it ticked off, which is the worst combination. It now says: All Pages only, do not add a History Change trigger, with the reason (GA4 enhanced measurement already sends a Flagging clearly: the published container still carries that trigger. I cannot change the GTM container from here — that is a UI action someone has to take before this item can be ticked. The file now says so rather than implying it is done. 2. The swap silences the assistant — stated plainlyRewritten from a "re-verify after the swap" step into a statement of fact: 3. Cloudflare — confirmed, and untickedA visible CNAME to Vercel with Vercel addresses behind it is proof the record is DNS-only — a proxied record returns Cloudflare addresses and hides the target. The 4. ANALYTICS.md will be public — confirmed and ownedBoth serve full content. The header now says the file is reachable at The one thing I did not changeThe Which raises the honest question for you as reviewer: given the container still has the wrong trigger and none of the event tags, is there a reason to merge this now rather than after the GTM work? The swap's only immediate effect on the live site would be to silence the assistant events. I have left it in scope because that was the call on this PR, but I would take the argument that the VerificationCI here stays red until #2335 lands — Re-requesting review. |
palango
left a comment
There was a problem hiding this comment.
Thanks for working through the last round. The history trigger, the window.gtag gap and the public-page warning are all fixed where I pointed at them.
To answer your question in the PR: yes, I'd hold the docs.json swap. I fetched the published container (gtm.js?id=GTM-NP9GP2BT) today and it still has the Google tag firing on gtm.historyChange, and none of the event tags (add_network_*, scroll_depth, outbound_click, copy_code, ai_menu_click, is_automated, assistant_* all appear 0 times). Merging now keeps the double-counted navigations and switches off all assistant telemetry, with nothing new measured in return. Two ways forward:
- (a) Remove the history trigger, add the event tags including the assistant ones, land the widget change, then merge this as is.
- (b) Split it: merge ANALYTICS.md and the
AddNetworkButtonpushes now (they're harmless no-ops until the tags exist) and do theintegrationsswap as the last step.
Either way, please say in the PR body that the swap stops the assistant_* events, and update #2307: its Gap 2 still says GTM's Google tag "should still define the global", and its acceptance criterion expects typeof window.gtag === 'function' after the swap. Both turned out false.
Also still open from last time: the "Ownership table in ANALYTICS.md filled in" checkbox in the PR body.
| | Audience | Tool | Where | | ||
| |---|---|---| | ||
| | Humans (browsers) | GA4 `G-0CXEKQ81V2`, tags managed in Google Tag Manager | `docs.json` → `integrations.gtm` | | ||
| | Bots and AI agents | Cloudflare AI Crawl Control (free tier), proxied in front of the domain | Cloudflare zone for `celo.org` | |
There was a problem hiding this comment.
Runbook 3 now says docs.celo.org is DNS-only, but this row still lists Cloudflare AI Crawl Control "proxied in front of the domain" as the tool for bots. Please mark it as not in place (e.g. "none today, see Runbook 3").
| |---|---|---| | ||
| | GA4 property | `G-0CXEKQ81V2` | `@celo-org/devrel` | | ||
| | GTM container | `GTM-NP9GP2BT` | `@celo-org/devrel` | | ||
| | Cloudflare zone / DNS for `docs.celo.org` | `celo.org` zone, `docs.celo.org` proxied | `@celo-org/devrel` | |
There was a problem hiding this comment.
Same here: the record isn't proxied, so "docs.celo.org proxied" is wrong.
| - `snippets/` holds reusable JSX/MDX (`/snippets/ColoredText.jsx`, `/snippets/YouTube.jsx`, `/snippets/AddNetworkButton.jsx`). Import with an absolute path after the frontmatter: `import {YouTube} from '/snippets/YouTube.jsx'`. | ||
| - Static assets: `img/`, `images/`, `assets/`, `logo/`. | ||
| - **Any `.js` file under the content root runs on every published page.** Mintlify injects them automatically — there is no allowlist and no way to scope one to a single page — and the same applies to `.css`. Treat a `.js` file here as production code shipped to every reader, not as content: it has full same-origin DOM access on pages that print contract addresses and RPC endpoints. Mintlify does not support a raw `<script src>` in MDX, so third-party scripts are injected programmatically from such a file (`assistant.js` is the example). Note `submodules/developer-tooling` sits under this root too. | ||
| - Site analytics (GA4 via Google Tag Manager for humans, Cloudflare AI Crawl Control for bots/agents) are documented in [ANALYTICS.md](./ANALYTICS.md). |
There was a problem hiding this comment.
This still says bots/agents are measured by Cloudflare AI Crawl Control. Nothing measures them today, so please drop that part or say it's planned.
| `chatgpt\.com|chat\.openai\.com|claude\.ai|perplexity\.ai|gemini\.google\.com|copilot\.microsoft\.com|grok\.com|x\.ai|deepseek\.com|you\.com|phind\.com|meta\.ai` | ||
| GA4's built-in "AI Assistant" channel recognizes only ChatGPT, Gemini, DeepSeek, Copilot and Grok — not Claude or Perplexity. Known limit: a large share of AI-referred sessions arrive with no referrer and land in Direct; this channel measures the floor, not the total. | ||
| - [ ] **Custom dimensions** (event-scoped): `percent_scrolled`, `link_domain`, `ai_target`, `network`, `result`, `is_automated`, plus the assistant's `answered`, `escalated`, `truncated`, `from_api`, `status` and `href`. Without these registered the assistant events still arrive, but their parameters cannot be used in any report. | ||
| - [ ] **The GTM swap silences every assistant event, and there is no shim for it.** `widget.js` calls `track()` only `if (typeof window.gtag === 'function')`. On the live site that global is a function, provided by `integrations.ga4`; on a page carrying only the GTM container it is `undefined`. Defining `gtag(){ dataLayer.push(arguments) }` by hand does *not* rescue it — no hit goes out. So all seven `assistant_*` events stop the moment the swap merges and stay stopped until the widget is changed to push to `dataLayer` directly (#2307). Land the widget change first if the gap is not acceptable. |
There was a problem hiding this comment.
A dataLayer push only reaches GA4 if a GTM tag listens for it, which is the same reason add_network_* does nothing today. Runbook 1 has no item for the seven assistant_* events, so landing #2307 alone won't bring them back. Please add a Custom Event trigger (e.g. regex ^assistant_) plus a GA4 event tag forwarding answered, escalated, truncated, from_api, status and href, and say here that both the widget change and the tag are needed.
| - [ ] **Custom channel group "AI Assistants"**: condition Session source matches regex | ||
| `chatgpt\.com|chat\.openai\.com|claude\.ai|perplexity\.ai|gemini\.google\.com|copilot\.microsoft\.com|grok\.com|x\.ai|deepseek\.com|you\.com|phind\.com|meta\.ai` | ||
| GA4's built-in "AI Assistant" channel recognizes only ChatGPT, Gemini, DeepSeek, Copilot and Grok — not Claude or Perplexity. Known limit: a large share of AI-referred sessions arrive with no referrer and land in Direct; this channel measures the floor, not the total. | ||
| - [ ] **Custom dimensions** (event-scoped): `percent_scrolled`, `link_domain`, `ai_target`, `network`, `result`, `is_automated`, plus the assistant's `answered`, `escalated`, `truncated`, `from_api`, `status` and `href`. Without these registered the assistant events still arrive, but their parameters cannot be used in any report. |
There was a problem hiding this comment.
Nit: "Without these registered the assistant events still arrive" stops being true after the swap, because under GTM they don't arrive at all (next item). Suggest: "Once the assistant events flow again, their parameters can't be used in reports until registered."
| - `docs.json` → `integrations.gtm.tagId` loads the GTM container on every page. GA4 itself is configured **inside** GTM (Google Tag), not in `docs.json` — having both would double-count page views. | ||
| - `snippets/AddNetworkButton.jsx` pushes `dataLayer` events: `add_network_click` on click, and `add_network_result` with `result` = `success` | `rejected` | `error` | `no_wallet` and `network` = chain name. This is the highest-intent action on the site. | ||
| - The docs assistant is instrumented on both sides, in `celo-org/docs-ai-assistant` rather than in this repo: | ||
| - **Client (GA4).** `widget.js` reuses the page's existing `window.gtag` rather than loading a second tracker, and emits `assistant_opened`, `assistant_question` (`answered`, `escalated`, `truncated`), `assistant_escalate`, `assistant_new_chat`, `assistant_copy`, `assistant_citation_click` (`href`) and `assistant_error` (`from_api`, `status`). Question text is deliberately never sent to GA4. |
There was a problem hiding this comment.
Nit: "reuses the page's existing window.gtag" describes behaviour this PR breaks. A pointer to the Runbook 2 item about the swap would stop readers assuming it still works.
| - The docs assistant is instrumented on both sides, in `celo-org/docs-ai-assistant` rather than in this repo: | ||
| - **Client (GA4).** `widget.js` reuses the page's existing `window.gtag` rather than loading a second tracker, and emits `assistant_opened`, `assistant_question` (`answered`, `escalated`, `truncated`), `assistant_escalate`, `assistant_new_chat`, `assistant_copy`, `assistant_citation_click` (`href`) and `assistant_error` (`from_api`, `status`). Question text is deliberately never sent to GA4. | ||
| - **Server (Redis).** `app/api/chat/route.ts` calls `logQuestion()`, which pushes `{question, model, citedUrls, answered, timestamp, refused}` onto the Upstash Redis list `docs-assistant:questions`, trimmed to the most recent 10,000. Without Redis configured it falls back to `console.log`, which on Vercel is short-retention only. **This list is the docs-gap signal** — the uncited questions in it are the pages that need writing. | ||
| - A handful of outbound partner links carry manual UTM parameters (`tooling/libraries-sdks/reown/index.mdx`, `tooling/indexers/goldrush.mdx`). The GTM outbound-click tag below covers outbound attribution generally, so new UTMs are not required. |
There was a problem hiding this comment.
Nit: the outbound-click tag doesn't exist yet, and it answers a different question from partner UTMs (UTMs let the partner attribute traffic to us; the tag tells us about clicks). I'd drop "so new UTMs are not required".
|
|
||
| ## Runbook 3: Cloudflare in front of docs.celo.org (bot visibility) | ||
|
|
||
| This is the only layer that could see non-JS bot traffic. **None of it is in place today, and the earlier claim that it was is wrong.** |
There was a problem hiding this comment.
Nit: "and the earlier claim that it was is wrong" is about the doc's history, not guidance. "None of it is in place today." is enough.
| - **JS-capable agentic browsers** (Comet, Atlas, computer-use agents) execute the GA4 tag and count as humans; `is_automated` catches only naive automation. | ||
| - **MCP traffic, both volume and content.** Requests to `https://docs.celo.org/mcp` are invisible to GA4, which needs JavaScript. They are not visible in our Cloudflare analytics either, because the hostname is not behind our zone (see Runbook 3) — that would only become true after the DNS change. And even then, what no layer here can show is **what was asked**: the question text, which tool was called, whether the answer cited anything. | ||
|
|
||
| Mintlify's own dashboard would cover part of that, but it needs the Pro plan ($450/mo), which #2250 evaluated and declined — the site was previously on Pro and deliberately moved to Starter. Query-level signal comes from the in-page assistant instead, and it is already implemented on both sides (see below). |
There was a problem hiding this comment.
Nit: "(see below)" points past the end of the file. The assistant is described in "What is instrumented in this repo" above.
The container still fires the Google tag on gtm.historyChange and carries none of the event tags, so swapping docs.json now would double-count navigations and switch off all seven assistant_* events while measuring nothing new in return. What stays is inert on its own: ANALYTICS.md, the AddNetworkButton dataLayer pushes (no-ops until tags exist) and the AGENTS.md pointer. docs.json is back to integrations.ga4, and the swap becomes the last step once the container is fixed and the widget no longer depends on window.gtag. ANALYTICS.md now describes the state as it actually is rather than the intended end state: GA4 loaded directly, the container not yet wired up, and docs.celo.org not proxied. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Taken option (b): the Your reasoning decided it — the container check you ran today is the whole argument. Merging the swap while the history trigger is still attached and the event tags are absent trades working assistant telemetry and correct page-view counts for nothing. Beyond removing the swap, I went back through Also ticked the ownership checkbox you flagged — the table has been filled in since #2307 still needs updating — its Gap 2 says GTM's Google tag "should still define the global" and its acceptance criterion expects |
palango
left a comment
There was a problem hiding this comment.
Dropping the swap was the right call, and the doc now separates what is live from what is planned. L17 and L28 are fixed, and L39 and L70 are true again now that the site keeps integrations.ga4.
Two things before this merges, both small:
AGENTS.md:15still describes the end state as live (inline).- Runbook 1 has no GTM tag for the
assistant_*events, so whoever does the swap by following it will lose them (inline at L61).
The rest are nits.
| - `snippets/` holds reusable JSX/MDX (`/snippets/ColoredText.jsx`, `/snippets/YouTube.jsx`, `/snippets/AddNetworkButton.jsx`). Import with an absolute path after the frontmatter: `import {YouTube} from '/snippets/YouTube.jsx'`. | ||
| - Static assets: `img/`, `images/`, `assets/`, `logo/`. | ||
| - **Any `.js` file under the content root runs on every published page.** Mintlify injects them automatically — there is no allowlist and no way to scope one to a single page — and the same applies to `.css`. Treat a `.js` file here as production code shipped to every reader, not as content: it has full same-origin DOM access on pages that print contract addresses and RPC endpoints. Mintlify does not support a raw `<script src>` in MDX, so third-party scripts are injected programmatically from such a file (`assistant.js` is the example). Note `submodules/developer-tooling` sits under this root too. | ||
| - Site analytics (GA4 via Google Tag Manager for humans, Cloudflare AI Crawl Control for bots/agents) are documented in [ANALYTICS.md](./ANALYTICS.md). |
There was a problem hiding this comment.
This still says GA4 runs "via Google Tag Manager" and bots are measured by "Cloudflare AI Crawl Control". Neither is true today, and this file is what agents read first. Something like: "Site analytics (GA4 today; the planned GTM and Cloudflare setup) are documented in ANALYTICS.md."
| - [ ] **AI-menu clicks**: Click trigger on the page-level contextual menu (Copy page / ChatGPT / Claude / Cursor / VS Code / MCP — the `contextual.options` in `docs.json`) → GA4 event `ai_menu_click` with `ai_target` set from the clicked item's text. Same selector caveat as above. | ||
| - [ ] **AddNetworkButton events**: Custom Event triggers for `add_network_click` and `add_network_result` → GA4 event tags forwarding `network` and `result` as parameters (Data Layer variables). | ||
| - [ ] **Automation heuristic**: Custom HTML tag (fires before the Google Tag, e.g. on Consent/Initialization) that pushes `{ is_automated: "true" }` to the dataLayer when `navigator.webdriver === true` or the user agent contains `HeadlessChrome`; attach `is_automated` as a parameter on the Google Tag. This is a weak signal, not a count — agentic browsers such as Comet and Atlas use stock Chrome user agents and are indistinguishable client-side. | ||
| - [ ] Verify everything in GTM **Preview mode** against the live site, then **Publish**. |
There was a problem hiding this comment.
Runbook 1 still has no item for the seven assistant_* events. Once the widget pushes to dataLayer (#2307), those pushes only reach GA4 if a GTM tag listens for them, just like add_network_*. Please add a Custom Event trigger (e.g. regex ^assistant_) plus a GA4 event tag forwarding answered, escalated, truncated, from_api, status and href, and make L71 say that both the widget change and this tag have to be in place before the swap. The PR body's to-do already lists "the six event tags, including assistant_*", but the six items here don't include them.
| const [pending, setPending] = useState(false); | ||
|
|
||
| // GTM picks these up and forwards them to GA4 (see /ANALYTICS.md). | ||
| // dataLayer is absent when GTM is blocked or not yet configured. |
There was a problem hiding this comment.
Nit: under today's integrations.ga4, the standard gtag snippet creates window.dataLayer, so these pushes probably land in gtag's dataLayer rather than going nowhere. I expect gtag.js to ignore plain {event: ...} objects, so "ships dormant" should still hold, but I haven't captured it. Could you check that clicking the button sends no /g/collect hit today, and reword the comment to match (e.g. "GA4 only sees these once a GTM tag forwards them")?
| - The docs assistant is instrumented on both sides, in `celo-org/docs-ai-assistant` rather than in this repo: | ||
| - **Client (GA4).** `widget.js` reuses the page's existing `window.gtag` rather than loading a second tracker, and emits `assistant_opened`, `assistant_question` (`answered`, `escalated`, `truncated`), `assistant_escalate`, `assistant_new_chat`, `assistant_copy`, `assistant_citation_click` (`href`) and `assistant_error` (`from_api`, `status`). Question text is deliberately never sent to GA4. | ||
| - **Server (Redis).** `app/api/chat/route.ts` calls `logQuestion()`, which pushes `{question, model, citedUrls, answered, timestamp, refused}` onto the Upstash Redis list `docs-assistant:questions`, trimmed to the most recent 10,000. Without Redis configured it falls back to `console.log`, which on Vercel is short-retention only. **This list is the docs-gap signal** — the uncited questions in it are the pages that need writing. | ||
| - A handful of outbound partner links carry manual UTM parameters (`tooling/libraries-sdks/reown/index.mdx`, `tooling/indexers/goldrush.mdx`). The GTM outbound-click tag below covers outbound attribution generally, so new UTMs are not required. |
There was a problem hiding this comment.
Nit: the outbound-click tag doesn't exist yet, and it answers a different question from partner UTMs (UTMs let the partner attribute traffic to us; the tag tells us about clicks). I'd drop "so new UTMs are not required".
|
|
||
| ## Runbook 3: Cloudflare in front of docs.celo.org (bot visibility) | ||
|
|
||
| This is the only layer that could see non-JS bot traffic. **None of it is in place today, and the earlier claim that it was is wrong.** |
There was a problem hiding this comment.
Nit: "and the earlier claim that it was is wrong" is about the doc's history, not guidance. "None of it is in place today." is enough.
| - **JS-capable agentic browsers** (Comet, Atlas, computer-use agents) execute the GA4 tag and count as humans; `is_automated` catches only naive automation. | ||
| - **MCP traffic, both volume and content.** Requests to `https://docs.celo.org/mcp` are invisible to GA4, which needs JavaScript. They are not visible in our Cloudflare analytics either, because the hostname is not behind our zone (see Runbook 3) — that would only become true after the DNS change. And even then, what no layer here can show is **what was asked**: the question text, which tool was called, whether the answer cited anything. | ||
|
|
||
| Mintlify's own dashboard would cover part of that, but it needs the Pro plan ($450/mo), which #2250 evaluated and declined — the site was previously on Pro and deliberately moved to Starter. Query-level signal comes from the in-page assistant instead, and it is already implemented on both sides (see below). |
There was a problem hiding this comment.
Nit: "(see below)" points past the end of the file. The assistant is described under "What is instrumented in this repo" above.
|
On #2307: yes please, go ahead and update it. Gap 2 should say GTM's Google tag does not define |
…end state as live - AGENTS.md said GA4 runs via GTM and bots are measured by Cloudflare. It is the first thing an agent reads, and neither is true today. Now names GA4 as current and the GTM/Cloudflare work as planned. - Runbook 1 had no tag for the seven assistant_* events, so following it through the swap would have lost them even after the widget change. Adds a Custom Event trigger on ^assistant_ with a GA4 event tag forwarding the six parameters, and the pre-swap gate now names both halves: the widget push and the tag that listens for it. - The AddNetworkButton comment claimed GTM forwards these today. Checked on the live site: the pushes land in gtag's dataLayer, which gtag.js creates, and gtag even stamps them with gtm.uniqueEventId, but no /g/collect hit goes out. Comment now says GA4 only sees them once a tag forwards them. - Drops the claim that the outbound-click tag removes the need for partner UTMs; they answer different questions. Drops a line about the doc's own history, and fixes a "see below" that pointed past the end of the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Both blocking items and all four nits are in. 1. AGENTS.md:15Fixed. You are right that this one matters more than its size — it is the first file an agent reads, and it was asserting the end state as current. Now: "Site analytics — GA4 today, plus the planned Google Tag Manager and Cloudflare setup — are documented in ANALYTICS.md." 2. The missing
|
palango
left a comment
There was a problem hiding this comment.
All items from the last round are in: AGENTS.md no longer describes the planned setup as live, Runbook 1 has the ^assistant_ trigger and tag, and the pre-swap item says both it and #2307 are needed. Thanks for capturing the /g/collect check on the button. Small follow-up if you touch the PR body again before merging: the "stops the assistant_* events until the widget change lands" line should also mention the GTM tag, since the squash message comes from it.
What this is now
The
docs.jsonGTM swap has been removed from this PR, per review. What is left cannot change what the live site measures:snippets/AddNetworkButton.jsx—dataLayerpushes for the add-network conversion:add_network_clickandadd_network_result(success/rejected/error/no_wallet). The highest-intent action on the site, previously untracked.ANALYTICS.md— architecture, ownership, and three runbooks (GTM container, GA4 property config, Cloudflare AI Crawl Control).AGENTS.md— one-line pointer to it.scripts/check-orphans.sh— exclude the new top-level file.docs.jsonis untouched: the site still loadsintegrations.ga4.Why the swap is held
The published container (
gtm.js?id=GTM-NP9GP2BT) still fires the Google tag ongtm.historyChange, and none of the event tags exist. So merging the swap today would:page_viewonpushState, so the history trigger produces a second hit.assistant_*events.widget.jscallstrack()onlyif (typeof window.gtag === 'function'). That global exists viaintegrations.ga4and isundefinedunder the container alone. A hand-rolledgtag(){ dataLayer.push(arguments) }shim does not rescue it — no hit goes out.…and measure nothing new in return, because the event tags aren't there. So the swap goes last, after the container is fixed and the widget pushes to
dataLayerdirectly.Stated plainly, as asked: the
integrationsswap stops theassistant_*events until the widget change lands. That sentence is now inANALYTICS.mdtoo, not just here.What ANALYTICS.md now says vs. what it said
The earlier version described the intended end state as though it were live. Corrected throughout:
docs.json→integrations.gtmintegrations.ga4today;integrations.gtmonce the swap landsdocs.celo.orgbehind Cloudflare/ANALYTICS, as/AGENTSand/CLAUDEare todaywindow.gtagafter the swapRemaining ops steps (not code, and not blocking this PR)
assistant_*dataLayerchange (task: make the assistant's existing telemetry reportable (GA4 custom dimensions, gtag after GTM swap, unanswered-question review) #2307)docs.jsontointegrations.gtm— separate PRdocs.celo.orgbehind thecelo.orgCloudflare zone at allChecklist
@celo-org/devrel), matching how.github/CODEOWNERSalready assigns/docs.jsonAddNetworkButtonpushes ship dormant. That is now harmless rather than a regression, since nothing is being switched off alongside them.Verification
snippets/AddNetworkButton.jsxsyntax-checked with esbuild. No pages moved; no redirects needed.🤖 Generated with Claude Code