Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# Descrybe web — GTM container import
|
||||
|
||||
Importable Google Tag Manager workspace for `apps/web` analytics (`$lib/analytics`, Consent Mode v2, SPA `page_view`).
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| [`descrybe-web-container.json`](./descrybe-web-container.json) | GTM export format version 2 — tags, triggers, variables |
|
||||
|
||||
No real secrets. Placeholders only: `G-XXXXXXXX`, `GTM-XXXXXXX`.
|
||||
|
||||
## Import fix notes
|
||||
|
||||
### Invalid `Parameter.type` enum (`STRING`)
|
||||
|
||||
GTM import failed with:
|
||||
|
||||
> File format is invalid. Error deserializing enum type [Type]. Unrecognized value [STRING].
|
||||
|
||||
**Root cause:** a previous edit set `consentSettings.consentType` list items to `"type": "STRING"`, matching outdated API prose (“list item is of type STRING”). That value is **not** in the real Parameter type enum.
|
||||
|
||||
Official GTM API `Parameter.type` values (camelCase in the API; **SNAKE_CASE** in container import/export JSON):
|
||||
|
||||
| API | Import/export JSON |
|
||||
|-----|--------------------|
|
||||
| `template` | `TEMPLATE` |
|
||||
| `integer` | `INTEGER` |
|
||||
| `boolean` | `BOOLEAN` |
|
||||
| `list` | `LIST` |
|
||||
| `map` | `MAP` |
|
||||
| `triggerReference` | `TRIGGER_REFERENCE` |
|
||||
| `tagReference` | `TAG_REFERENCE` |
|
||||
|
||||
There is **no** `string` / `STRING`. Text values (including consent type names like `analytics_storage`) use **`TEMPLATE`**.
|
||||
|
||||
This file now uses:
|
||||
|
||||
```json
|
||||
"consentSettings": {
|
||||
"consentStatus": "NEEDED",
|
||||
"consentType": {
|
||||
"type": "LIST",
|
||||
"list": [{ "type": "TEMPLATE", "value": "analytics_storage" }]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Invalid `{` / `}` in entity names
|
||||
|
||||
GTM rejects `{` / `}` in **tag / trigger / variable names**. An earlier draft named a tag `GA4 Event - Custom Event ({{Event}})`, which failed import with *“The name contains invalid character: `{`”*.
|
||||
|
||||
That tag is now **`GA4 Event - Catch-all Custom Events`**. The GA4 **event name parameter** still uses `{{Event}}` (built-in Event variable) — macro syntax is valid in parameter **values**, not in entity names.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Create a **GA4** property and note its Measurement ID (`G-…`).
|
||||
2. Create a **GTM Web** container (or use an existing empty workspace). Note the container public ID (`GTM-…`).
|
||||
3. App loads GTM only when `PUBLIC_GTM_ID` is a valid `GTM-…` id (see root `.env.example` and `apps/web/src/lib/analytics.ts`).
|
||||
|
||||
## Import steps (re-import)
|
||||
|
||||
1. Open [Google Tag Manager](https://tagmanager.google.com/) → your **Web** container.
|
||||
2. **Admin** → **Import Container**.
|
||||
3. Choose `docs/gtm/descrybe-web-container.json`.
|
||||
4. Choose a **workspace** (new or existing).
|
||||
5. Import option:
|
||||
- Prefer **Merge** into an empty/new workspace, or
|
||||
- **Overwrite** only if you intend to replace the workspace contents.
|
||||
- If you previously failed mid-import or have a partial copy, use a **fresh workspace** (or Overwrite) so you do not keep the old invalid-named tag.
|
||||
6. After import, open **Variables** → **Constant - GA4 Measurement ID** and set the value to your real Measurement ID (replace `G-XXXXXXXX`).
|
||||
7. **Consent (verify):** each GA4 tag imports with **Additional Consent Checks** requiring **`analytics_storage`** (`consentStatus` = `NEEDED`, list item type `TEMPLATE`). If Preview shows tags firing without analytics consent, open each GA4 tag → **Advanced settings** → **Consent Settings** → require `analytics_storage`. Add `ad_storage` / `ad_user_data` / `ad_personalization` only if you later add ads tags.
|
||||
8. **Preview** with Tag Assistant against a local/staging site that has analytics consent granted.
|
||||
9. **Submit** → **Publish**.
|
||||
10. Set **`PUBLIC_GTM_ID`** in the web env to the container’s public ID (`GTM-…`) so it matches the published container. The placeholder `GTM-XXXXXXX` in the JSON is **not** a real container id — GTM assigns the public ID when you create the container; import merges into *that* container.
|
||||
|
||||
## What the container includes
|
||||
|
||||
### Variables
|
||||
|
||||
| Name | Type | Notes |
|
||||
|------|------|--------|
|
||||
| `Constant - GA4 Measurement ID` | Constant | **Replace** `G-XXXXXXXX` |
|
||||
| `DL - value` / `currency` / `items` / `transaction_id` | Data Layer | Ecommerce fields (version 2) |
|
||||
| `DL - page_path` / `page_title` / `page_location` | Data Layer | SPA `page_view` |
|
||||
| `DL - billing_term` / `plan` / `pack_id` | Data Layer | Flat extras on checkout/purchase |
|
||||
|
||||
Built-ins enabled: Event, Page URL, Page Path, Page Hostname, Referrer.
|
||||
|
||||
### Triggers (Custom Event only)
|
||||
|
||||
| Trigger | Event name |
|
||||
|---------|------------|
|
||||
| `CE - page_view` | `page_view` |
|
||||
| `CE - begin_checkout` | `begin_checkout` |
|
||||
| `CE - purchase` | `purchase` |
|
||||
| `CE - checkout_canceled` | `checkout_canceled` |
|
||||
| `CE - sign_up` | `sign_up` |
|
||||
| `CE - login` | `login` |
|
||||
| `CE - generate_lead` | `generate_lead` |
|
||||
| `CE - processing_job_started` | `processing_job_started` |
|
||||
| `CE - consent_update` | `consent_update` |
|
||||
| `CE - Other Custom Events (catch-all)` | Regex excluding the above + `gtm.*` |
|
||||
|
||||
**Do not** add History Change or GA4 enhanced-measurement automatic `page_view` — the app already pushes `page_view` from `trackPageview` / `AnalyticsHost`.
|
||||
|
||||
### Tags
|
||||
|
||||
| Tag | Fires on | Behavior |
|
||||
|-----|----------|----------|
|
||||
| `GA4 Configuration` | Initialization (built-in `2147479573`) | `sendPageView=false`; consent: `analytics_storage` |
|
||||
| `GA4 Event - page_view` | `CE - page_view` | Sends `page_path`, `page_title`, `page_location` |
|
||||
| `GA4 Event - begin_checkout` | `CE - begin_checkout` | Maps `currency`, `value`, `items`, plus `billing_term` / `plan` / `pack_id` |
|
||||
| `GA4 Event - purchase` | `CE - purchase` | Same ecommerce fields + `transaction_id` + flat extras |
|
||||
| `GA4 Event - Catch-all Custom Events` | Named core events + catch-all | Event name parameter = `{{Event}}` (dataLayer event name) |
|
||||
|
||||
## Ecommerce mapping (`begin_checkout` / `purchase`)
|
||||
|
||||
Expected dataLayer shape (flat, same object as `event`):
|
||||
|
||||
```js
|
||||
{
|
||||
event: "begin_checkout", // or "purchase"
|
||||
currency: "USD",
|
||||
value: 99,
|
||||
items: [
|
||||
{
|
||||
item_id: "pro",
|
||||
item_name: "Pro",
|
||||
item_category: "subscription", // or "credit_pack"
|
||||
price: 99,
|
||||
quantity: 1
|
||||
}
|
||||
],
|
||||
// purchase only:
|
||||
transaction_id: "cs_test_…",
|
||||
// optional flat extras:
|
||||
billing_term: "monthly",
|
||||
plan: "pro",
|
||||
pack_id: "pack_…"
|
||||
}
|
||||
```
|
||||
|
||||
Dedicated tags map those DL variables into GA4 event parameters. **List / marketing prices may be approximates** (e.g. plan card pricing before Stripe tax/discounts); use Stripe / server data for finance-grade reporting.
|
||||
|
||||
## Consent
|
||||
|
||||
The app sets Consent Mode v2 defaults to denied, then updates via the CMP (`updateConsentMode` + `consent_update` dataLayer event). GTM tags are gated with **Additional Consent Required → `analytics_storage`**. Client-side `trackEvent` / `trackPageview` also no-op until analytics is granted.
|
||||
|
||||
## After publish — env checklist
|
||||
|
||||
```text
|
||||
PUBLIC_GTM_ID=GTM-XXXXXXXX # must match the published container public ID
|
||||
```
|
||||
|
||||
Do not put the GA4 Measurement ID in the web app env for primary loading — measurement ID lives in the GTM variable; the app loads GTM only.
|
||||
|
||||
## Re-import after this fix
|
||||
|
||||
If a previous import failed on `STRING` (or left a partial workspace):
|
||||
|
||||
1. Use a **new workspace** (or **Overwrite** on an empty one) so you do not keep a half-imported state.
|
||||
2. **Admin** → **Import Container** → choose `docs/gtm/descrybe-web-container.json`.
|
||||
3. Confirm all five GA4 tags show Consent → require `analytics_storage`.
|
||||
4. Set **Constant - GA4 Measurement ID**, Preview, then Publish.
|
||||
|
||||
## Limitations / manual fix-ups
|
||||
|
||||
- **Placeholder account/container IDs** (`0000000000` / `GTM-XXXXXXX`) are rewritten by GTM on import into *your* container. If import rejects metadata, create an empty Web container first, then **Merge** this file.
|
||||
- **Fingerprints** are synthetic; GTM regenerates them after import.
|
||||
- **Catch-all** does not forward arbitrary event parameters to GA4 (only the event name). Dedicated ecommerce tags carry `value` / `currency` / `items` / `transaction_id`. Add event-parameter tables for other events if needed.
|
||||
- **No ads tags** in this export — no `ad_*` consent requirements yet.
|
||||
- **`items` as a GA4 event parameter** relies on GTM/GA4 accepting the DL array via the event settings table. If Preview shows empty items, switch the ecommerce tags to “Send ecommerce data” and push an `ecommerce: { … }` object from the app, or verify in Tag Assistant that `{{DL - items}}` resolves.
|
||||
- Re-export from GTM after your first successful import if you want a fingerprint-perfect baseline in git.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user