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:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+172
View File
@@ -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 containers 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.