{"id":9319,"date":"2026-08-09T15:37:30","date_gmt":"2026-08-09T13:37:30","guid":{"rendered":"https:\/\/www.lukaswojcik.com\/blog\/werkzeuge\/gtm-container-history-deep-search-engine\/"},"modified":"2026-10-23T15:00:33","modified_gmt":"2026-10-23T13:00:33","slug":"gtm-container-history-deep-search-engine","status":"publish","type":"page","link":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/","title":{"rendered":"GTM-Containerverlauf und Tiefensuche"},"content":{"rendered":"<div class=\"gtm-analyser-container lw-tool-surface\" style=\"background: var(--bg-panel, #1e1e24); padding: 25px; border-radius: 8px; border: 1px solid var(--border, #2a2a35);\">\n<div class=\"lw-aussenhinweis\" style=\"background: var(--bg-body, #14141a); border: 1px solid var(--border, #2a2a35); border-left: 3px solid var(--lw-tool-warn); border-radius: 6px; padding: 14px 16px; margin-bottom: 20px;\">\n<div style=\"color: var(--lw-tool-warn); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; margin-bottom: 6px;\">Ben\u00f6tigt eine Google-Anmeldung<\/div>\n<p style=\"color: var(--text-secondary, #a0a0b0); margin: 0 0 8px; font-size: 0.9rem; line-height: 1.55;\">Anders als die \u00fcbrigen Werkzeuge hier arbeitet dieses nicht f\u00fcr sich allein. Es liest aus der Google Tag Manager REST API v2 und verlangt daf\u00fcr eine Anmeldung bei Google. Das angeforderte Recht lautet tagmanager.readonly und ist ausschlie\u00dflich lesend \u2014 \u00e4ndern l\u00e4sst sich dar\u00fcber nichts.<\/p>\n<p style=\"color: var(--text-secondary, #a0a0b0); margin: 0; font-size: 0.9rem; line-height: 1.55;\">Was geholt wird, bleibt in dieser Registerkarte: Die Auswertung l\u00e4uft im Browser, an lukaswojcik.com geht nichts. Die unten vorbelegte OAuth-Client-ID l\u00e4sst sich durch eine eigene ersetzen.<\/p>\n<\/div>\n<p style=\"color: var(--text-secondary); margin-bottom: 20px; line-height: 1.6;\">\n        Verbindet sich per OAuth 2.0 direkt mit der <strong>Google Tag Manager REST API v2<\/strong>.<br \/>\n        Ruft die Versions-Header ab und durchsucht Hunderte von Containerversionen nach Tags, Skripten, Triggern oder Variablen &#8211; ganz ohne manuellen Dateiexport.\n    <\/p>\n<p id=\"gtm-element-history-intro\" style=\"color: var(--text-secondary); margin-bottom: 20px; line-height: 1.6;\"><strong>\u00c4nderungsverlauf eines Elements:<\/strong> Nach der Anmeldung l\u00e4sst sich ein einzelnes Tag, ein Trigger, eine Variable, eine Vorlage, ein Ordner, ein Client, eine Transformation oder eine Zone per Name, ID oder GTM-Adresse eingeben. Das Werkzeug listet jede Version, in der das Element angelegt, ge\u00e4ndert, umbenannt, entfernt oder wiederhergestellt wurde, mit Versionsnummer, Versionsname und Notizen, Datum, Unterschieden Feld f\u00fcr Feld, \u00c4nderungen an verwendeten Triggern und Variablen sowie unver\u00f6ffentlichten \u00c4nderungen in Arbeitsbereichen.<\/p>\n<p id=\"gtm-unification-background\" style=\"color: var(--text-secondary); margin-bottom: 20px; line-height: 1.6;\"><strong>Fr\u00fchere Einordnung der Zusammenlegung:<\/strong> <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/digital-analytics-de\/zusammenlegung-von-google-tag-und-tag-manager-destinations-gtm-init-trigger-und-was-die-lade-id-entscheidet\/\">Zusammenlegung von Google Tag und Tag Manager: Destinations, gtm-init-Trigger und was die Lade-ID entscheidet<\/a>.<\/p>\n<p id=\"gtm-unification-article\" style=\"color: var(--text-secondary); margin-bottom: 20px; line-height: 1.6;\"><strong>Hintergrund zur Umstellung:<\/strong> <a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/digital-analytics-de\/google-tag-manager-und-gtag-js-verschmelzen-was-sich-2026-andert\/\">Google Tag Manager und gtag.js verschmelzen: Was sich 2026 \u00e4ndert<\/a>. Die Oktober-Einordnung erkl\u00e4rt Containeroptimierung, gtag.config-Ereignisse und eine kontrollierte Migration.<\/p>\n<div id=\"gtm-tool-update-20261019\" style=\"border-left:3px solid var(--accent);padding:12px 16px;margin:0 0 24px;background:var(--bg-body);border-radius:6px;color:var(--text-secondary);line-height:1.6;\">\n<p style=\"margin:0 0 8px;\"><strong>Update vom 19.10.2026:<\/strong> Durchsucht werden die gew\u00e4hlten Tag-, Trigger-, Variablen-, Template- und Ordnerlisten der abgerufenen Versionen. Das ist keine Suche \u00fcber s\u00e4mtliche zentralen Einstellungen oder Rechte. Googles ausgelieferter Laufzeitcode kann sich ohne neue Version \u00e4ndern; ein fehlender Treffer f\u00fcr <code>gtag.config<\/code> schlie\u00dft dieses Laufzeitereignis nicht aus. Die optionale Optimierung erfordert einen eigenen Abgleich zentraler Einstellungen und Ziel-Tags. Rechte werden nicht versioniert und durch eine Versionsr\u00fcckkehr nicht wiederhergestellt.<\/p>\n<p style=\"margin:0;font-size:0.9em;\"><a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/digital-analytics-de\/google-tag-manager-und-gtag-js-verschmelzen-was-sich-2026-andert\/\">Einordnung der Oktober\u00e4nderungen<\/a> \u00b7 <a href=\"https:\/\/support.google.com\/tagmanager\/answer\/4620708?hl=en\">Google-Versionshinweise<\/a> \u00b7 <a href=\"https:\/\/support.google.com\/analytics\/answer\/16659369\">Optimierung und Rechte<\/a><\/p>\n<\/div>\n<div id=\"gtm-related-links-20261020\" class=\"lw-gtm-related\" style=\"margin:20px 0;color:var(--text-secondary);line-height:1.6;\">\n<p><strong>Passende Anleitungen und Werkzeuge:<\/strong><\/p>\n<ul>\n<li><a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/digital-analytics-de\/tutorials-de\/tutorial-die-containerversion-finden-in-der-sich-ein-tag-geandert-hat\/\">Die Version einer Tag-\u00c4nderung finden<\/a> \u2013 Versionsarchiv, Fingerprints und Grenzen historischer Nachweise.<\/li>\n<li><a href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/web-entwicklung\/zwei-gtm-container-exporte-vergleichen-normalisieren-vor-dem-diff-und-was-ein-zeilenvergleich-nicht-zeigt\/\">GTM-Exporte normalisieren und vergleichen<\/a> \u2013 Konfigurationsunterschiede und Grenzen des Textvergleichs.<\/li>\n<\/ul>\n<\/div>\n<p>    <!-- STEP 1: OAUTH & API AUTHENTICATION --><\/p>\n<div style=\"background: var(--bg-body, #101416); padding: 20px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 20px;\">\n<h3 style=\"color: var(--accent); font-size: 1rem; margin-top: 0; margin-bottom: 12px;\">1. Authentifizierung an der Google API<\/h3>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; align-items: flex-end;\">\n<div style=\"flex: 1; min-width: 280px;\">\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\" for=\"api-client-id\">Google Cloud OAuth Client ID:<\/label><br \/>\n                <input type=\"text\" id=\"api-client-id\" value=\"423029313598-2ko0jilc28eevp253rqncvodg2181e1l.apps.googleusercontent.com\" class=\"form-control\" style=\"width: 100%; padding: 10px; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-primary); border-radius: 6px; font-family: monospace;\">\n            <\/div>\n<div>\n                <button id=\"auth-btn\" class=\"button\" style=\"background: var(--accent); color:var(--on-accent); border: none; padding: 11px 22px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Mit Google autorisieren<\/button>\n            <\/div>\n<\/p><\/div>\n<div data-mp-mask id=\"auth-status\" style=\"margin-top: 10px; font-size: 0.85rem; color: var(--text-secondary);\">Status: <strong style=\"color: var(--fail);\">Nicht verbunden<\/strong><\/div>\n<\/p><\/div>\n<p>    <!-- STEP 2: CONTAINER SELECTOR --><\/p>\n<div id=\"container-selector-panel\" style=\"background: var(--bg-body); padding: 20px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 20px; display: none;\">\n<h3 style=\"color: var(--accent); font-size: 1rem; margin-top: 0; margin-bottom: 12px;\">2. GTM-Container und Versionsbereich w\u00e4hlen<\/h3>\n<div style=\"display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-bottom: 15px;\">\n<div>\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\">GTM-Konto:<\/label><br \/>\n                <select data-mp-mask id=\"account-select\" style=\"width: 100%; padding: 10px; background: var(--bg-panel); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px;\"><option value=\"\">&#8212; Konto w\u00e4hlen &#8212;<\/option><\/select>\n            <\/div>\n<div>\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\">Container:<\/label><br \/>\n                <select data-mp-mask id=\"container-select\" style=\"width: 100%; padding: 10px; background: var(--bg-panel); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px;\" disabled><option value=\"\">&#8212; Container w\u00e4hlen &#8212;<\/option><\/select>\n            <\/div>\n<div>\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\">Zu pr\u00fcfender Versionsbereich:<\/label><br \/>\n                <select id=\"range-select\" style=\"width: 100%; padding: 10px; background: var(--bg-panel); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px;\" disabled><option value=\"30\">Letzte 30 Versionen (schnell)<\/option><option value=\"100\">Letzte 100 Versionen<\/option><option value=\"300\">Letzte 300 Versionen<\/option><option value=\"all\">ALLE Versionen (vollst\u00e4ndige Pr\u00fcfung)<\/option><\/select>\n            <\/div>\n<\/p><\/div>\n<div data-mp-mask id=\"header-status\" style=\"font-size: 0.85rem; color: var(--text-secondary);\"><\/div>\n<\/p><\/div>\n<p>    <!-- MODE TABS: DEEP SEARCH \/ ELEMENT HISTORY (23.10.2026) --><\/p>\n<div id=\"gtm-mode-tabs\" role=\"tablist\" style=\"display: none; gap: 8px; flex-wrap: wrap; margin-bottom: 15px;\">\n        <button type=\"button\" role=\"tab\" class=\"gtm-mode-tab button\" data-mode=\"search\" aria-selected=\"true\" style=\"border: 1px solid var(--accent); padding: 10px 18px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Tiefensuche<\/button><br \/>\n        <button type=\"button\" role=\"tab\" class=\"gtm-mode-tab button\" data-mode=\"history\" aria-selected=\"false\" style=\"border: 1px solid var(--accent); padding: 10px 18px; border-radius: 6px; font-weight: 700; cursor: pointer;\">\u00c4nderungsverlauf eines Elements<\/button>\n    <\/div>\n<p>    <!-- STEP 3: DEEP SEARCH CONTROLS --><\/p>\n<div id=\"search-controls-panel\" style=\"background: var(--bg-body); padding: 20px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 25px; display: none;\">\n<h3 style=\"color: var(--accent); font-size: 1rem; margin-top: 0; margin-bottom: 12px;\">3. Tiefensuche im Versionsverlauf<\/h3>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; margin-bottom: 15px;\">\n<div style=\"flex: 1; min-width: 260px;\">\n                <input type=\"text\" id=\"search-query\" placeholder=\"z. B. G-XXXXXXX, fbq(, analytics_storage oder ^gtm\\..*\" style=\"width: 100%; padding: 12px; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-primary); border-radius: 6px; font-family: monospace;\">\n            <\/div>\n<div>\n                <button id=\"run-api-search-btn\" class=\"button\" style=\"background: var(--accent); color:var(--on-accent); border: none; padding: 12px 28px; border-radius: 6px; font-weight: 700; cursor: pointer;\">Tiefensuche \u00fcber die API starten<\/button>\n            <\/div>\n<\/p><\/div>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; align-items: center; border-top: 1px solid var(--border); padding-top: 15px; font-size: 0.85rem;\">\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" id=\"opt-regex\"> Regul\u00e4ren Ausdruck verwenden<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" id=\"opt-case\"> Gro\u00df-\/Kleinschreibung beachten<\/label><br \/>\n            <span style=\"color: var(--border);\">|<\/span><br \/>\n            <span style=\"color: var(--text-primary); font-weight: bold;\">Suchbereiche:<\/span><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" class=\"scope-cb\" value=\"tag\" checked> Tags<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" class=\"scope-cb\" value=\"trigger\" checked> Trigger<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" class=\"scope-cb\" value=\"variable\" checked> Variablen<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" class=\"scope-cb\" value=\"customTemplate\" checked> Vorlagen<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" class=\"scope-cb\" value=\"folder\" checked> Ordner<\/label>\n        <\/div>\n<p>        <!-- PROGRESS BAR --><\/p>\n<div id=\"progress-container\" style=\"margin-top: 15px; display: none;\">\n<div style=\"display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 5px;\">\n                <span data-mp-mask id=\"progress-text\">GTM-API-Versionen werden gepr\u00fcft &#8230;<\/span><br \/>\n                <span data-mp-mask id=\"progress-percent\">0%<\/span>\n            <\/div>\n<div style=\"width: 100%; height: 8px; background: var(--bg-raised); border-radius: 4px; overflow: hidden;\">\n<div id=\"progress-bar\" style=\"width: 0%; height: 100%; background: var(--ok); transition: width 0.2s;\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>    <!-- STEP 3 (ALTERNATIVE): CHANGE HISTORY OF ONE ELEMENT (23.10.2026) --><\/p>\n<div id=\"element-history-panel\" style=\"background: var(--bg-body); padding: 20px; border-radius: 8px; border: 1px solid var(--border); margin-bottom: 25px; display: none;\">\n<h3 style=\"color: var(--accent); font-size: 1rem; margin-top: 0; margin-bottom: 12px;\">3. \u00c4nderungsverlauf eines Elements<\/h3>\n<p style=\"color: var(--text-secondary); font-size: 0.9rem; line-height: 1.55; margin: 0 0 15px;\">Eingabe: Name, ID oder eine aus dem Tag Manager kopierte Adresse (\u2026\/tags\/12). Das Element wird \u00fcber seine ID verfolgt, Umbenennungen bleiben so in einem Verlauf. Es gilt der Versionsbereich aus Schritt 2; bereits gelesene Versionen bleiben f\u00fcr das n\u00e4chste Element erhalten.<\/p>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 15px;\">\n<div style=\"min-width: 170px;\">\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\" for=\"eh-type\">Elementart:<\/label><br \/>\n                <select id=\"eh-type\" style=\"width: 100%; padding: 11px 10px; background: var(--bg-panel); color: var(--text-primary); border: 1px solid var(--border); border-radius: 6px;\"><option value=\"any\">Alle Arten<\/option><option value=\"tag\">Tag<\/option><option value=\"trigger\">Trigger<\/option><option value=\"variable\">Variable<\/option><option value=\"builtInVariable\">Integrierte Variable<\/option><option value=\"customTemplate\">Vorlage<\/option><option value=\"folder\">Ordner<\/option><option value=\"client\">Client (Server)<\/option><option value=\"transformation\">Transformation (Server)<\/option><option value=\"zone\">Zone<\/option><\/select>\n            <\/div>\n<div style=\"flex: 1; min-width: 240px;\">\n                <label style=\"color: var(--text-secondary); display: block; font-size: 0.85rem; margin-bottom: 6px;\" for=\"eh-query\">Name, ID oder GTM-Adresse:<\/label><br \/>\n                <input type=\"text\" id=\"eh-query\" list=\"eh-suggestions\" autocomplete=\"off\" data-mp-mask data-rc-private placeholder=\"z. B. GA4 - purchase, 42 oder https:\/\/tagmanager.google.com\/#\/container\/\u2026\/tags\/42\" style=\"width: 100%; padding: 11px 12px; background: var(--bg-panel); border: 1px solid var(--border); color: var(--text-primary); border-radius: 6px; font-family: monospace;\"><br \/>\n                <datalist id=\"eh-suggestions\"><\/datalist>\n            <\/div>\n<div>\n                <button id=\"eh-run-btn\" type=\"button\" class=\"button\" style=\"background: var(--accent); color: var(--on-accent); border: none; padding: 12px 28px; border-radius: 6px; font-weight: 700; cursor: pointer;\">\u00c4nderungsverlauf anzeigen<\/button>\n            <\/div>\n<\/p><\/div>\n<div style=\"display: flex; gap: 15px; flex-wrap: wrap; align-items: center; border-top: 1px solid var(--border); padding-top: 15px; font-size: 0.85rem;\">\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" id=\"eh-deps\" checked> Verwendete Trigger und Variablen einbeziehen<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" id=\"eh-drafts\" checked> Unver\u00f6ffentlichte \u00c4nderungen in Arbeitsbereichen einbeziehen<\/label><br \/>\n            <label style=\"color: var(--text-secondary); cursor: pointer;\"><input type=\"checkbox\" id=\"eh-touched\"> Speicherungen ohne Unterschied in der Konfiguration zeigen<\/label>\n        <\/div>\n<div data-mp-mask id=\"eh-status\" style=\"margin-top: 12px; font-size: 0.85rem; color: var(--text-secondary);\"><\/div>\n<div id=\"eh-progress\" style=\"margin-top: 15px; display: none;\">\n<div style=\"display: flex; justify-content: space-between; font-size: 0.8rem; color: var(--text-secondary); margin-bottom: 5px;\">\n                <span data-mp-mask id=\"eh-progress-text\"><\/span><br \/>\n                <span data-mp-mask id=\"eh-progress-percent\">0%<\/span>\n            <\/div>\n<div style=\"width: 100%; height: 8px; background: var(--bg-raised); border-radius: 4px; overflow: hidden;\">\n<div id=\"eh-progress-bar\" style=\"width: 0%; height: 100%; background: var(--ok); transition: width 0.2s;\"><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<\/p><\/div>\n<p>    <!-- RESULTS --><\/p>\n<div data-mp-mask id=\"api-search-results\"><\/div>\n<div data-mp-mask data-rc-private id=\"element-history-results\" style=\"display: none;\"><\/div>\n<\/div>\n<p><script>\n(() => {\n    let accessToken = null;\n    let tokenClient = null;\n    let versionHeaders = [];\n    const accountsMap = {};\n    const containersMap = {};<\/p>\n<p>    const authBtn = document.getElementById(\"auth-btn\");\n    const authStatus = document.getElementById(\"auth-status\");\n    const accSelect = document.getElementById(\"account-select\");\n    const contSelect = document.getElementById(\"container-select\");\n    const rangeSelect = document.getElementById(\"range-select\");\n    const headerStatus = document.getElementById(\"header-status\");\n    const resultsEl = document.getElementById(\"api-search-results\");\n    const progressCont = document.getElementById(\"progress-container\");\n    const progressBar = document.getElementById(\"progress-bar\");\n    const progressText = document.getElementById(\"progress-text\");\n    const progressPercent = document.getElementById(\"progress-percent\");<\/p>\n<p>    const esc = (str) => String(str).replace(\/&\/g, \"&amp;\").replace(\/<\/g, \"&lt;\").replace(\/>\/g, \"&gt;\");<\/p>\n<p>    \/\/ 1. OAUTH 2.0 INITIALIZATION\n    authBtn.addEventListener(\"click\", () => {\n        const clientId = document.getElementById(\"api-client-id\").value.trim();\n        if (!clientId) {\n            alert(\"Bitte eine g\u00fcltige Google Cloud Client ID eingeben.\");\n            return;\n        }<\/p>\n<p>        if (!window.google || !google.accounts || !google.accounts.oauth2) {\n            \/\/ Google Identity Services nur nach Einwilligung (consented.eu), siehe js\/lw-google-login.js\n            if (window.lwGoogleSdk) { window.lwGoogleSdk().then(() => authBtn.click(), () => {}); }\n            return;\n        }<\/p>\n<p>        tokenClient = google.accounts.oauth2.initTokenClient({\n            client_id: clientId,\n            scope: \"https:\/\/www.googleapis.com\/auth\/tagmanager.readonly\",\n            callback: (resp) => {\n                if (resp.error) {\n                    authStatus.innerHTML = \"Status: <strong style='color:#ff6b6b;'>Authentifizierungsfehler: \" + esc(resp.error) + \"<\/strong>\";\n                    return;\n                }\n                accessToken = resp.access_token;\n                authStatus.innerHTML = \"Status: <strong style='color:#51cf66;'>Verbunden (nur Lesezugriff)<\/strong>\";\n                document.getElementById(\"container-selector-panel\").style.display = \"block\";\n                loadAccounts();\n            }\n        });\n        tokenClient.requestAccessToken();\n    });<\/p>\n<p>    \/\/ 2. API FETCHER WITH RATE-LIMIT THROTTLING & ADVANCED ERROR DECODING\n    const gtmApi = async (path) => {\n        const res = await fetch(\"https:\/\/tagmanager.googleapis.com\/tagmanager\/v2\/\" + path, {\n            headers: { \"Authorization\": \"Bearer \" + accessToken }\n        });\n        if (res.status === 429) {\n            await new Promise(r => setTimeout(r, 1500));\n            return gtmApi(path);\n        }\n        if (!res.ok) {\n            let errDetails = res.statusText || \"Unbekannter API-Fehler\";\n            try {\n                const errJson = await res.json();\n                if (errJson && errJson.error && errJson.error.message) {\n                    errDetails = errJson.error.message;\n                }\n            } catch (e) {}\n            throw new Error(\"API-Fehler \" + res.status + \": \" + errDetails);\n        }\n        return res.json();\n    };<\/p>\n<p>    const loadAccounts = async () => {\n        try {\n            accSelect.innerHTML = \"<option>Konten werden geladen ...<\/option>\";\n            const data = await gtmApi(\"accounts\");\n            accSelect.innerHTML = \"<option value=''>-- Konto w\u00e4hlen --<\/option>\";\n            (data.account || []).forEach(a => {\n                accountsMap[a.accountId] = a.name;\n                const opt = document.createElement(\"option\");\n                opt.value = a.accountId;\n                opt.textContent = a.name + \" (\" + a.accountId + \")\";\n                accSelect.appendChild(opt);\n            });\n        } catch (e) {\n            accSelect.innerHTML = \"<option>Fehler beim Laden der Konten<\/option>\";\n            alert(\"Konten konnten nicht abgerufen werden: \" + e.message);\n        }\n    };<\/p>\n<p>    accSelect.addEventListener(\"change\", async () => {\n        const accId = accSelect.value;\n        if (!accId) return;\n        try {\n            contSelect.innerHTML = \"<option>Container werden geladen ...<\/option>\";\n            contSelect.disabled = true;\n            const data = await gtmApi(\"accounts\/\" + accId + \"\/containers\");\n            contSelect.innerHTML = \"<option value=''>-- Container w\u00e4hlen --<\/option>\";\n            (data.container || []).forEach(c => {\n                containersMap[c.containerId] = c;\n                const opt = document.createElement(\"option\");\n                opt.value = c.containerId;\n                opt.textContent = c.name + \" (\" + c.publicId + \")\";\n                contSelect.appendChild(opt);\n            });\n            contSelect.disabled = false;\n        } catch (e) {\n            alert(\"Container konnten nicht geladen werden: \" + e.message);\n        }\n    });<\/p>\n<p>    contSelect.addEventListener(\"change\", async () => {\n        const accId = accSelect.value;\n        const contId = contSelect.value;\n        if (!contId) return;\n        try {\n            headerStatus.innerHTML = \"Metadaten aller Containerversionen werden abgerufen ...\";\n            const data = await gtmApi(\"accounts\/\" + accId + \"\/containers\/\" + contId + \"\/version_headers\");\n            versionHeaders = (data.containerVersionHeader || []).sort((a, b) => Number(b.containerVersionId) - Number(a.containerVersionId));\n            headerStatus.innerHTML = \"<strong style='color:#51cf66;'>Erfolg:<\/strong> In diesem Container wurden <strong>\" + versionHeaders.length + \" Versionen<\/strong> gefunden.\";\n            rangeSelect.disabled = false;\n            ehContainerReady();\n        } catch (e) {\n            headerStatus.innerHTML = \"<span style='color:#ff6b6b;'>Fehler beim Laden der Versions-Header: \" + esc(e.message) + \"<\/span>\";\n        }\n    });<\/p>\n<p>    \/\/ 3. DEEP SEARCH TRAVERSAL LOGIC\n    const searchInObject = (obj, regex, path = \"\") => {\n        const matches = [];\n        if (typeof obj === \"string\") {\n            if (regex.test(obj)) matches.push({ path: path || \"value\", snippet: obj });\n        } else if (Array.isArray(obj)) {\n            obj.forEach((item, idx) => matches.push(...searchInObject(item, regex, path ? path + \"[\" + idx + \"]\" : \"[\" + idx + \"]\")));\n        } else if (obj && typeof obj === \"object\") {\n            for (const key in obj) {\n                if (Object.prototype.hasOwnProperty.call(obj, key)) {\n                    const val = obj[key];\n                    const nextPath = path ? path + \".\" + key : key;\n                    if (regex.test(key)) matches.push({ path: nextPath, snippet: typeof val === \"object\" ? JSON.stringify(val) : String(val) });\n                    matches.push(...searchInObject(val, regex, nextPath));\n                }\n            }\n        }\n        return matches;\n    };<\/p>\n<p>    const getBadge = (type) => {\n        const colors = {\n            tag: { bg: \"#1a3a4b\", color: \"#51cf66\", label: \"TAG\" },\n            trigger: { bg: \"#4b3a1a\", color: \"#ff922b\", label: \"TRIGGER\" },\n            variable: { bg: \"#2a1a4b\", color: \"#339af0\", label: \"VARIABLE\" },\n            customTemplate: { bg: \"#4b1a47\", color: \"#cc5de8\", label: \"VORLAGE\" },\n            folder: { bg: \"#242b30\", color: \"#a0a6b0\", label: \"ORDNER\" }\n        };\n        const c = colors[type] || { bg: \"#242b30\", color: \"#ffffff\", label: type.toUpperCase() };\n        return \"<span style='background:\" + c.bg + \"; color:\" + c.color + \"; padding:3px 8px; border-radius:4px; font-size:0.75rem; font-weight:700;'>\" + c.label + \"<\/span>\";\n    };<\/p>\n<p>    \/\/ 4. QUEUED SCANNER ENGINE\n    document.getElementById(\"run-api-search-btn\").addEventListener(\"click\", async () => {\n        const query = document.getElementById(\"search-query\").value.trim();\n        if (!query) { alert(\"Bitte einen Suchbegriff eingeben.\"); return; }<\/p>\n<p>        const isRegex = document.getElementById(\"opt-regex\").checked;\n        const isCase = document.getElementById(\"opt-case\").checked;\n        const activeScopes = Array.from(document.querySelectorAll(\".scope-cb:checked\")).map(cb => cb.value);<\/p>\n<p>        let regex;\n        try {\n            const flags = isCase ? \"g\" : \"gi\";\n            regex = isRegex ? new RegExp(query, flags) : new RegExp(query.replace(\/[.*+?^${}()|[\\]\\\\]\/g, \"\\\\$&\"), flags);\n        } catch (e) {\n            alert(\"Ung\u00fcltiger regul\u00e4rer Ausdruck: \" + e.message); return;\n        }<\/p>\n<p>        const rangeVal = rangeSelect.value;\n        const toScan = rangeVal === \"all\" ? versionHeaders : versionHeaders.slice(0, Number(rangeVal));<\/p>\n<p>        resultsEl.innerHTML = \"\";\n        progressCont.style.display = \"block\";\n        const allResults = [];\n        let completed = 0;<\/p>\n<p>        \/\/ Run batch pool of 3 concurrent API calls to avoid 429 limit\n        const concurrency = 3;\n        for (let i = 0; i < toScan.length; i += concurrency) {\n            const batch = toScan.slice(i, i + concurrency);\n            await Promise.all(batch.map(async (vh) => {\n                try {\n                    const fullVer = await gtmApi(vh.path);\n                    activeScopes.forEach((scope) => {\n                        const items = fullVer[scope] || [];\n                        items.forEach((item) => {\n                            const itemMatches = searchInObject(item, regex);\n                            if (itemMatches.length > 0) {\n                                allResults.push({\n                                    versionId: vh.containerVersionId,\n                                    versionName: vh.name || \"(ohne Namen)\",\n                                    type: scope,\n                                    entityName: item.name || \"(unbenanntes Element)\",\n                                    matches: itemMatches\n                                });\n                            }\n                        });\n                    });\n                } catch (err) {\n                    console.error(\"Error scanning version \" + vh.containerVersionId, err);\n                } finally {\n                    completed++;\n                    const pct = Math.round((completed \/ toScan.length) * 100);\n                    progressBar.style.width = pct + \"%\";\n                    progressPercent.textContent = pct + \"%\";\n                    progressText.textContent = \"Version \" + completed + \" von \" + toScan.length + \" gepr\u00fcft ...\";\n                }\n            }));\n        }<\/p>\n<p>        progressText.textContent = \"Pr\u00fcfung abgeschlossen!\";<\/p>\n<p>        if (!allResults.length) {\n            resultsEl.innerHTML = \"<\/p>\n<div style='background:var(--bg-body); padding:25px; border-radius:6px; border:1px solid var(--border); text-align:center; color:var(--text-secondary);'>Keine Treffer f\u00fcr <strong>\" + esc(query) + \"<\/strong> in den gepr\u00fcften Versionen.<\/div>\n<p>\";\n            return;\n        }<\/p>\n<p>        allResults.sort((a, b) => Number(b.versionId) - Number(a.versionId));<\/p>\n<p>        const cardsHtml = allResults.map((res) => {\n            const matchRows = res.matches.slice(0, 6).map((m) => [\n                \"<\/p>\n<div style='background:#0b1114; border:1px solid var(--border); padding:10px; border-radius:4px; margin-top:8px; font-family:monospace; font-size:0.85rem; overflow-x:auto;'>\",\n                \"  <span style='color:#ff922b; display:block; margin-bottom:4px;'>\" + esc(m.path) + \"<\/span>\",\n                \"  <span style='color:#e0e6ed; white-space:pre-wrap;'>\" + esc(m.snippet.slice(0, 400)) + (m.snippet.length > 400 ? \"...\" : \"\") + \"<\/span>\",\n                \"<\/div>\n<p>\"\n            ].join(\"\")).join(\"\");<\/p>\n<p>            return [\n                \"<\/p>\n<div style='background:var(--bg-body); border:1px solid var(--border); border-radius:8px; padding:18px; margin-bottom:15px;'>\",\n                \"  <\/p>\n<div style='display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:10px; border-bottom:1px solid var(--border); padding-bottom:12px; margin-bottom:12px;'>\",\n                \"    <\/p>\n<div style='display:flex; align-items:center; gap:10px;'>\",\n                \"      \" + getBadge(res.type),\n                \"      <strong style='color:var(--text-primary); font-size:1.05rem;'>\" + esc(res.entityName) + \"<\/strong>\",\n                \"    <\/div>\n<p>\",\n                \"    <\/p>\n<div style='color:var(--text-secondary); font-size:0.85rem;'>\",\n                \"      Gefunden in Version: <strong style='color:var(--accent);'>v\" + esc(res.versionId) + \"<\/strong> (\" + esc(res.versionName) + \")\",\n                \"    <\/div>\n<p>\",\n                \"  <\/p><\/div>\n<p>\",\n                \"  <\/p>\n<div style='color:var(--text-secondary); font-size:0.85rem;'>\u00dcbereinstimmende Eigenschaften (\" + res.matches.length + \"):<\/div>\n<p>\",\n                \"  \" + matchRows,\n                \"<\/p><\/div>\n<p>\"\n            ].join(\"\");\n        }).join(\"\");<\/p>\n<p>        resultsEl.innerHTML = [\n            \"<\/p>\n<h3 style='color:var(--text-primary); margin-bottom:15px; font-size:1.15rem;'>Treffer im Versionsverlauf: \" + allResults.length + \"<\/h3>\n<p>\",\n            cardsHtml\n        ].join(\"\");\n    });<\/p>\n<p>    \/\/ 5. CHANGE HISTORY OF ONE ELEMENT (23.10.2026)\n    \/\/ Tracks a tag, trigger, variable etc. by its ID across the versions in the chosen range.\n    \/\/ Each version is reduced to normalized element strings; identical strings are shared, so a\n    \/\/ second element of the same container is answered from memory without new API calls.\n    const T = {\"types\": {\"tag\": \"Tag\", \"trigger\": \"Trigger\", \"variable\": \"Variable\", \"builtInVariable\": \"Integrierte Variable\", \"customTemplate\": \"Vorlage\", \"folder\": \"Ordner\", \"client\": \"Client\", \"transformation\": \"Transformation\", \"zone\": \"Zone\", \"gtagConfig\": \"Google-Tag-Konfiguration\"}, \"loadingList\": \"Elementliste der neuesten Version wird geladen ...\", \"listReady\": \"Elementliste aus Version v{v} geladen \u2013 Vorschl\u00e4ge erscheinen beim Tippen.\", \"urlOtherContainer\": \"Hinweis: Die eingef\u00fcgte Adresse geh\u00f6rt zu einem anderen Konto oder Container als dem gew\u00e4hlten.\", \"moreLines\": \"\u2026 {n} weitere Zeilen\", \"fromLine\": \"ab Zeile {n}\", \"absent\": \"nicht gesetzt\", \"before\": \"Vorher\", \"after\": \"Nachher\", \"moreFields\": \"\u2026 {n} weitere Felder (vollst\u00e4ndig im JSON-Export)\", \"depsTitle\": \"Verwendete Elemente\", \"depAdded\": \"neu verwendet\", \"depRemoved\": \"nicht mehr verwendet\", \"depChanged\": \"ge\u00e4ndert\", \"depMissing\": \"fehlt\", \"depNotInContainer\": \"in dieser Version nicht vorhanden\", \"fieldsChanged\": \"{n} Feld(er) unterscheiden sich\", \"openInGtm\": \"Version im GTM \u00f6ffnen\", \"live\": \"Live\", \"ev_baseline\": \"Vorhanden in der \u00e4ltesten gepr\u00fcften Version\", \"ev_created\": \"Angelegt\", \"ev_restored\": \"Wieder vorhanden\", \"ev_removed\": \"Entfernt\", \"ev_changed\": \"Konfiguration ge\u00e4ndert\", \"ev_renamed\": \"Umbenannt von \u201e{from}\u201c in \u201e{to}\u201c\", \"ev_depChanged\": \"Selbst unver\u00e4ndert \u2013 ein verwendetes Element hat sich ge\u00e4ndert\", \"ev_touched\": \"Erneut gespeichert ohne Unterschied in der Konfiguration\", \"ev_draft\": \"Arbeitsbereich \u201e{ws}\u201c: {status}, noch nicht ver\u00f6ffentlicht\", \"k_baseline\": \"Bereichsbeginn\", \"k_created\": \"Angelegt\", \"k_restored\": \"Wiederhergestellt\", \"k_removed\": \"Entfernt\", \"k_changed\": \"Ge\u00e4ndert\", \"k_renamed\": \"Umbenannt\", \"k_depChanged\": \"Abh\u00e4ngigkeit\", \"k_touched\": \"Gespeichert\", \"k_draft\": \"Entwurf\", \"ws_added\": \"hinzugef\u00fcgt\", \"ws_deleted\": \"gel\u00f6scht\", \"ws_updated\": \"ge\u00e4ndert\", \"ws_none\": \"unver\u00e4ndert\", \"draftCompared\": \"{n} Feld(er) weichen von Version v{v} ab\", \"version\": \"Version\", \"versionDate\": \"Version gespeichert\", \"elementDate\": \"Element zuletzt gespeichert\", \"versionNotes\": \"Versionsnotizen\", \"absentSince\": \"fehlte seit der Version nach v{v}\", \"gapNote\": \"{n} Version(en) davor lie\u00dfen sich nicht lesen\", \"showConfig\": \"Konfiguration anzeigen ({n} Felder)\", \"account\": \"GTM-Konto\", \"container\": \"Container\", \"scanned\": \"Gepr\u00fcft\", \"scannedText\": \"{n} Versionen ({from} bis {to})\", \"unreadable\": \"{n} Version(en) lie\u00dfen sich nicht lesen\", \"rangeHint\": \"\u00c4ltere Versionen liegen au\u00dferhalb des gew\u00e4hlten Bereichs; \u201eALLE Versionen\u201c in Schritt 2 umfasst den ganzen Verlauf.\", \"liveVersion\": \"Live-Version\", \"noLive\": \"keine ver\u00f6ffentlicht\", \"draftError\": \"\u00c4nderungen in Arbeitsbereichen lie\u00dfen sich nicht lesen:\", \"exportJson\": \"JSON exportieren\", \"exportCsv\": \"CSV exportieren\", \"elementId\": \"ID\", \"firstSeen\": \"Zuerst gesehen\", \"beforeRange\": \"in dieser Version oder fr\u00fcher angelegt\", \"lastChange\": \"Letzte \u00c4nderung\", \"noChange\": \"keine \u00c4nderung im Bereich\", \"changeCount\": \"\u00c4nderungen im Bereich\", \"inLive\": \"In der Live-Version enthalten\", \"yes\": \"ja\", \"no\": \"nein\", \"liveOutOfRange\": \"Live-Version au\u00dferhalb des Bereichs\", \"names\": \"Namen im Verlauf\", \"limits\": \"Grenzen: Die GTM API liefert weder f\u00fcr ein Element noch f\u00fcr eine Version eine Person \u2013 wer eine Version ver\u00f6ffentlicht hat, steht in der Versionsliste der Tag-Manager-Oberfl\u00e4che. Die Datumsangaben stammen aus den Fingerprints, die die API f\u00fcr Versionen und Elemente liefert; Google dokumentiert sie als \u00c4nderungsmarke, derzeit enthalten sie den Speicherzeitpunkt in Millisekunden. Eine Version h\u00e4lt die Konfiguration zum Zeitpunkt ihres Entstehens fest, \u00c4nderungen, die zwischen zwei Versionen gemacht und wieder zur\u00fcckgenommen wurden, hinterlassen also keine Spur. Von Google ausgelieferter Laufzeitcode, containerweite Einstellungen und Berechtigungen geh\u00f6ren nicht zu diesem Vergleich. Alles l\u00e4uft in diesem Browser-Tab, nichts geht an lukaswojcik.com.\", \"multiMatch\": \"Die Eingabe passt zu {n} Elementen; jedes hat unten einen eigenen Verlauf.\", \"needQuery\": \"Bitte einen Namen, eine ID oder eine GTM-Adresse eingeben.\", \"noVersions\": \"Dieser Container hat noch keine Versionen.\", \"progress\": \"Version {n} von {total} wird gelesen ...\", \"authExpired\": \"Die Google-Anmeldung ist abgelaufen (HTTP 401). Bitte in Schritt 1 erneut autorisieren.\", \"nothingReadable\": \"Keine Version im Bereich lie\u00df sich lesen.\", \"notFound\": \"In den {n} gepr\u00fcften Versionen gibt es kein Element mit dem Namen oder der Nummer \u201e{q}\u201c.\", \"similar\": \"\u00c4hnliche Namen in der neuesten Version:\", \"loadingDrafts\": \"Arbeitsbereiche werden auf unver\u00f6ffentlichte \u00c4nderungen gepr\u00fcft ...\", \"tooMany\": \"{n} Elemente passen; die ersten 10 werden gezeigt. Ein genauerer Name, eine ID oder eine Elementart grenzt das Ergebnis ein.\"};\n    const EH_LOCALE = \"de-DE\";\n    const EH_TYPES = {\n        tag: \"tagId\", trigger: \"triggerId\", variable: \"variableId\", builtInVariable: \"type\",\n        customTemplate: \"templateId\", folder: \"folderId\", client: \"clientId\",\n        transformation: \"transformationId\", zone: \"zoneId\", gtagConfig: \"gtagConfigId\"\n    };\n    const EH_URL_TYPES = { tags: \"tag\", triggers: \"trigger\", variables: \"variable\", templates: \"customTemplate\", folders: \"folder\", clients: \"client\", transformations: \"transformation\", zones: \"zone\" };\n    const EH_IGNORE = { fingerprint: 1, path: 1, tagManagerUrl: 1, accountId: 1, containerId: 1, workspaceId: 1 };\n    const EH_EVENT_COLORS = { created: \"var(--ok)\", restored: \"var(--ok)\", baseline: \"var(--text-secondary)\", changed: \"var(--accent)\", renamed: \"var(--accent)\", removed: \"var(--fail)\", depChanged: \"var(--lw-tool-warn)\", touched: \"var(--text-secondary)\", draft: \"var(--lw-tool-warn)\" };<\/p>\n<p>    const ehPanel = document.getElementById(\"element-history-panel\");\n    const ehResults = document.getElementById(\"element-history-results\");\n    const ehTabs = document.getElementById(\"gtm-mode-tabs\");\n    const ehType = document.getElementById(\"eh-type\");\n    const ehQuery = document.getElementById(\"eh-query\");\n    const ehList = document.getElementById(\"eh-suggestions\");\n    const ehRunBtn = document.getElementById(\"eh-run-btn\");\n    const ehStatus = document.getElementById(\"eh-status\");\n    const ehProg = document.getElementById(\"eh-progress\");\n    const ehProgBar = document.getElementById(\"eh-progress-bar\");\n    const ehProgText = document.getElementById(\"eh-progress-text\");\n    const ehProgPct = document.getElementById(\"eh-progress-percent\");<\/p>\n<p>    let ehMode = \"search\";\n    let ehCache = new Map();\n    let ehIntern = new Map();\n    let ehLiveId = null;\n    let ehNewest = null;\n    let ehGen = 0;\n    let ehLast = null;<\/p>\n<p>    const ehAttr = (s) => esc(s).replace(\/\"\/g, \"&quot;\").replace(\/'\/g, \"&#39;\");\n    const ehFmt = (s, o) => String(s).replace(\/\\{(\\w+)\\}\/g, (m, k) => (o && k in o ? o[k] : m));\n    const ehTypeName = (t) => T.types[t] || t;\n    const ehNumSort = (a, b) => Number(a.id) - Number(b.id);<\/p>\n<p>    const ehDate = (fp) => {\n        if (!\/^\\d{13}$\/.test(String(fp || \"\"))) return \"\";\n        const n = Number(fp);\n        if (n < 1325376000000 || n > Date.now() + 86400000) return \"\";\n        try { return new Date(n).toLocaleString(EH_LOCALE, { dateStyle: \"medium\", timeStyle: \"short\" }); }\n        catch (e) { return new Date(n).toISOString(); }\n    };<\/p>\n<p>    \/\/ Parameter lists [{type,key,value}] become objects keyed by \"key\", so a diff names\n    \/\/ \"parameter.html.value\" instead of a position that shifts when a parameter is added.\n    const ehNorm = (v, top) => {\n        if (Array.isArray(v)) {\n            const keyed = v.length > 0 && v.every(x => x && typeof x === \"object\" && !Array.isArray(x) && typeof x.key === \"string\");\n            if (keyed && new Set(v.map(x => x.key)).size === v.length) {\n                const o = {};\n                v.slice().sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0)).forEach(x => {\n                    const item = Object.assign({}, x);\n                    delete item.key;\n                    o[x.key] = ehNorm(item, false);\n                });\n                return o;\n            }\n            return v.map(x => ehNorm(x, false));\n        }\n        if (v && typeof v === \"object\") {\n            const o = {};\n            Object.keys(v).sort().forEach(k => { if (!(top && EH_IGNORE[k])) o[k] = ehNorm(v[k], false); });\n            return o;\n        }\n        return v;\n    };<\/p>\n<p>    const ehFlat = (v, p, out) => {\n        if (v && typeof v === \"object\") {\n            const isArr = Array.isArray(v);\n            const ks = isArr ? v.map((x, i) => i) : Object.keys(v);\n            if (!ks.length) { out.set(p || \"value\", isArr ? \"[]\" : \"{}\"); return out; }\n            ks.forEach(k => ehFlat(v[k], isArr ? p + \"[\" + k + \"]\" : (p ? p + \".\" + k : k), out));\n        } else {\n            out.set(p || \"value\", v === undefined || v === null ? \"\" : String(v));\n        }\n        return out;\n    };<\/p>\n<p>    const ehRecord = (vh, ver) => {\n        const rec = {\n            id: String(vh.containerVersionId), name: ver.name || vh.name || \"\", desc: ver.description || \"\",\n            fp: ver.fingerprint || \"\", url: ver.tagManagerUrl || \"\", ok: true, items: {}\n        };\n        Object.keys(EH_TYPES).forEach(type => {\n            const list = ver[type] || [];\n            const m = new Map();\n            list.forEach(it => {\n                const id = String(it[EH_TYPES[type]] == null ? \"\" : it[EH_TYPES[type]]);\n                if (!id) return;\n                const j = JSON.stringify(ehNorm(it, true));\n                const f = String(it.fingerprint || \"\");\n                const key = f + \"\\u0000\" + j;\n                let entry = ehIntern.get(key);\n                if (!entry) { entry = { n: String(it.name || \"\"), f: f, j: j }; ehIntern.set(key, entry); }\n                m.set(id, entry);\n            });\n            rec.items[type] = m;\n        });\n        return rec;\n    };<\/p>\n<p>    const ehFetch = async (vh, gen) => {\n        if (ehCache.has(vh.path)) return ehCache.get(vh.path);\n        let rec;\n        try {\n            const ver = await gtmApi(vh.path);\n            rec = ehRecord(vh, ver);\n            if (gen === ehGen) ehCache.set(vh.path, rec);\n        } catch (e) {\n            rec = { id: String(vh.containerVersionId), name: vh.name || \"\", ok: false, err: e.message, items: {} };\n        }\n        return rec;\n    };<\/p>\n<p>    const ehShowMode = (mode) => {\n        ehMode = mode;\n        const history = mode === \"history\";\n        document.getElementById(\"search-controls-panel\").style.display = history ? \"none\" : \"block\";\n        resultsEl.style.display = history ? \"none\" : \"block\";\n        ehPanel.style.display = history ? \"block\" : \"none\";\n        ehResults.style.display = history ? \"block\" : \"none\";\n        ehTabs.querySelectorAll(\".gtm-mode-tab\").forEach(btn => {\n            const active = btn.getAttribute(\"data-mode\") === mode;\n            btn.setAttribute(\"aria-selected\", active ? \"true\" : \"false\");\n            btn.style.background = active ? \"var(--accent)\" : \"transparent\";\n            btn.style.color = active ? \"var(--on-accent)\" : \"var(--accent)\";\n        });\n    };\n    ehTabs.querySelectorAll(\".gtm-mode-tab\").forEach(btn => btn.addEventListener(\"click\", () => ehShowMode(btn.getAttribute(\"data-mode\"))));<\/p>\n<p>    const ehFillList = () => {\n        ehList.innerHTML = \"\";\n        if (!ehNewest) return;\n        const sel = ehType.value;\n        const types = sel === \"any\" ? Object.keys(EH_TYPES) : [sel];\n        const seen = new Set();\n        let count = 0;\n        types.forEach(t => {\n            const m = ehNewest.items[t];\n            if (!m) return;\n            m.forEach((it, id) => {\n                if (count >= 3000 || !it.n || seen.has(t + it.n)) return;\n                seen.add(t + it.n);\n                const opt = document.createElement(\"option\");\n                opt.value = it.n;\n                opt.label = ehTypeName(t) + (t === \"builtInVariable\" ? \"\" : \" \u00b7 ID \" + id);\n                ehList.appendChild(opt);\n                count++;\n            });\n        });\n    };\n    ehType.addEventListener(\"change\", ehFillList);<\/p>\n<p>    \/\/ Called by the container selector once the version headers are loaded.\n    const ehContainerReady = async () => {\n        const gen = ++ehGen;\n        ehCache = new Map();\n        ehIntern = new Map();\n        ehLiveId = null;\n        ehNewest = null;\n        ehLast = null;\n        ehResults.innerHTML = \"\";\n        ehList.innerHTML = \"\";\n        ehTabs.style.display = \"flex\";\n        ehShowMode(ehMode);\n        const base = \"accounts\/\" + accSelect.value + \"\/containers\/\" + contSelect.value;\n        try {\n            const live = await gtmApi(base + \"\/versions:live\");\n            if (gen === ehGen && live && live.containerVersionId != null) ehLiveId = String(live.containerVersionId);\n        } catch (e) { \/* container was never published *\/ }\n        if (gen !== ehGen || !versionHeaders.length) return;\n        ehStatus.textContent = T.loadingList;\n        let rec = null;\n        for (let i = 0; i < Math.min(3, versionHeaders.length) &#038;&#038; !(rec &#038;&#038; rec.ok); i++) {\n            rec = await ehFetch(versionHeaders[i], gen);\n            if (gen !== ehGen) return;\n        }\n        if (rec.ok) {\n            ehNewest = rec;\n            ehFillList();\n            ehStatus.textContent = ehFmt(T.listReady, { v: rec.id });\n        } else {\n            ehStatus.textContent = \"\";\n        }\n    };\n\n    const ehParse = (raw) => {\n        const q = raw.trim();\n        const r = { q: q, type: ehType.value, id: null, warn: \"\" };\n        const m = q.match(\/\\\/(tags|triggers|variables|templates|folders|clients|transformations|zones)\\\/(\\d+)\/);\n        if (m) {\n            r.type = EH_URL_TYPES[m[1]];\n            r.id = m[2];\n            const c = q.match(\/accounts\\\/(\\d+)\\\/containers\\\/(\\d+)\/);\n            if (c && (c[1] !== accSelect.value || c[2] !== contSelect.value)) r.warn = T.urlOtherContainer;\n        }\n        return r;\n    };<\/p>\n<p>    const ehByName = (rec, type) => {\n        rec._byName = rec._byName || {};\n        if (!rec._byName[type]) {\n            const m = new Map();\n            (rec.items[type] || new Map()).forEach((it, id) => { if (!m.has(it.n)) m.set(it.n, id); });\n            rec._byName[type] = m;\n        }\n        return rec._byName[type];\n    };<\/p>\n<p>    \/\/ Triggers of a tag, the template behind a cvt_ type and every {{variable}}, followed\n    \/\/ through variables and triggers until no new reference appears.\n    const ehDeps = (rec, type, id, entry) => {\n        const out = new Map();\n        const queue = [{ t: type, e: entry }];\n        const add = (dt, did, label) => {\n            const key = dt + \":\" + did;\n            if (out.has(key) || (dt === type && did === id)) return;\n            const d = rec.items[dt] && rec.items[dt].get(did);\n            out.set(key, d ? { t: dt, id: did, n: d.n, j: d.j } : { t: dt, id: did, n: label || did, j: null });\n            if (d && dt !== \"builtInVariable\") queue.push({ t: dt, e: d });\n        };\n        while (queue.length) {\n            const cur = queue.shift();\n            let obj;\n            try { obj = JSON.parse(cur.e.j); } catch (e) { continue; }\n            if (cur.t === \"tag\") {\n                [].concat(obj.firingTriggerId || [], obj.blockingTriggerId || []).forEach(x => add(\"trigger\", String(x)));\n            }\n            if (\/^cvt_\/.test(String(obj.type || \"\"))) {\n                (rec.items.customTemplate || new Map()).forEach((tpl, tid) => { if (String(obj.type).endsWith(\"_\" + tid)) add(\"customTemplate\", tid); });\n            }\n            const re = \/\\{\\{([^{}]+)\\}\\}\/g;\n            let m;\n            while ((m = re.exec(cur.e.j))) {\n                const name = m[1].trim();\n                const vid = ehByName(rec, \"variable\").get(name);\n                if (vid) { add(\"variable\", vid); continue; }\n                const bid = ehByName(rec, \"builtInVariable\").get(name);\n                if (bid) { add(\"builtInVariable\", bid); continue; }\n                add(\"variable\", \"?\" + name, name);\n            }\n        }\n        return out;\n    };<\/p>\n<p>    const ehDiff = (aj, bj) => {\n        const a = ehFlat(aj ? JSON.parse(aj) : {}, \"\", new Map());\n        const b = ehFlat(bj ? JSON.parse(bj) : {}, \"\", new Map());\n        const rows = [];\n        new Set([...a.keys(), ...b.keys()]).forEach(p => {\n            const x = a.has(p) ? a.get(p) : null;\n            const y = b.has(p) ? b.get(p) : null;\n            if (x !== y) rows.push({ path: p, before: x, after: y });\n        });\n        return rows.sort((r1, r2) => (r1.path < r2.path ? -1 : r1.path > r2.path ? 1 : 0));\n    };<\/p>\n<p>    const ehDepDiff = (a, b) => {\n        const out = [];\n        new Set([...a.keys(), ...b.keys()]).forEach(k => {\n            const x = a.get(k), y = b.get(k);\n            if (x && !y) out.push({ kind: \"depRemoved\", t: x.t, id: x.id, n: x.n, missing: !x.j });\n            else if (!x && y) out.push({ kind: \"depAdded\", t: y.t, id: y.id, n: y.n, missing: !y.j });\n            else if (x.j !== y.j) {\n                if (!y.j) out.push({ kind: \"depMissing\", t: y.t, id: y.id, n: y.n, missing: true });\n                else out.push({ kind: \"depChanged\", t: y.t, id: y.id, n: y.n, nBefore: x.n, rows: ehDiff(x.j, y.j) });\n            }\n        });\n        return out;\n    };<\/p>\n<p>    const ehTimeline = (recs, type, id, opts) => {\n        const events = [];\n        let prev = null;\n        let lastPresent = null;\n        let gap = 0;\n        recs.forEach((rec, idx) => {\n            if (!rec.ok) { gap++; return; }\n            const cur = (rec.items[type] && rec.items[type].get(id)) || null;\n            const deps = cur && opts.deps ? ehDeps(rec, type, id, cur) : null;\n            const base = { version: rec, entry: cur, gap: gap, rows: [], deps: [] };\n            if (!prev) {\n                if (cur) events.push(Object.assign(base, { kind: \"baseline\", rows: ehDiff(null, cur.j) }));\n            } else if (!prev.cur && cur) {\n                if (lastPresent) events.push(Object.assign(base, { kind: \"restored\", rows: ehDiff(lastPresent.cur.j, cur.j), since: lastPresent.rec, deps: deps && lastPresent.deps ? ehDepDiff(lastPresent.deps, deps) : [] }));\n                else events.push(Object.assign(base, { kind: \"created\", rows: ehDiff(null, cur.j) }));\n            } else if (prev.cur && !cur) {\n                events.push(Object.assign(base, { kind: \"removed\", entry: prev.cur }));\n            } else if (prev.cur && cur) {\n                const rows = prev.cur.j !== cur.j ? ehDiff(prev.cur.j, cur.j) : [];\n                const depRows = deps && prev.deps ? ehDepDiff(prev.deps, deps) : [];\n                const renamed = prev.cur.n !== cur.n;\n                if (rows.length || depRows.length) {\n                    events.push(Object.assign(base, {\n                        kind: renamed ? \"renamed\" : rows.length ? \"changed\" : \"depChanged\",\n                        rows: rows, deps: depRows, oldName: renamed ? prev.cur.n : \"\"\n                    }));\n                } else if (opts.touched && prev.cur.f !== cur.f) {\n                    events.push(Object.assign(base, { kind: \"touched\" }));\n                }\n            }\n            if (cur) lastPresent = { cur: cur, rec: rec, deps: deps };\n            prev = { cur: cur, deps: deps, rec: rec };\n            gap = 0;\n        });\n        return events;\n    };<\/p>\n<p>    const ehDrafts = async (keys, newestRec) => {\n        const out = [];\n        const base = \"accounts\/\" + accSelect.value + \"\/containers\/\" + contSelect.value;\n        const ws = await gtmApi(base + \"\/workspaces\");\n        for (const w of (ws.workspace || [])) {\n            const st = await gtmApi(w.path + \"\/status\");\n            (st.workspaceChange || []).forEach(ch => {\n                Object.keys(EH_TYPES).forEach(t => {\n                    if (!ch[t]) return;\n                    const id = String(ch[t][EH_TYPES[t]] == null ? \"\" : ch[t][EH_TYPES[t]]);\n                    if (!keys.has(t + \":\" + id)) return;\n                    const before = newestRec && newestRec.items[t] && newestRec.items[t].get(id);\n                    const j = JSON.stringify(ehNorm(ch[t], true));\n                    out.push({\n                        kind: \"draft\", t: t, id: id, workspace: w.name || w.workspaceId, status: ch.changeStatus || \"\",\n                        fpEntity: ch[t].fingerprint || \"\", name: ch[t].name || \"\",\n                        rows: ch.changeStatus === \"deleted\" ? [] : ehDiff(before ? before.j : null, j)\n                    });\n                });\n            });\n        }\n        return out;\n    };<\/p>\n<p>    \/\/ Line diff for code fields (Custom HTML, template code): common start and end are cut off,\n    \/\/ the middle is compared line by line.\n    const ehLineDiff = (a, b) => {\n        const A = a.split(\"\\n\"), B = b.split(\"\\n\");\n        let s = 0;\n        while (s < A.length &#038;&#038; s < B.length &#038;&#038; A[s] === B[s]) s++;\n        let e = 0;\n        while (e < A.length - s &#038;&#038; e < B.length - s &#038;&#038; A[A.length - 1 - e] === B[B.length - 1 - e]) e++;\n        const x = A.slice(s, A.length - e), y = B.slice(s, B.length - e);\n        let ops = [];\n        if (x.length * y.length <= 250000) {\n            const n = x.length, m = y.length, w = m + 1;\n            const L = new Uint32Array((n + 1) * w);\n            for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) {\n                L[i * w + j] = x[i] === y[j] ? L[(i + 1) * w + j + 1] + 1 : Math.max(L[(i + 1) * w + j], L[i * w + j + 1]);\n            }\n            let i = 0, j = 0;\n            while (i < n &#038;&#038; j < m) {\n                if (x[i] === y[j]) { ops.push([\" \", x[i]]); i++; j++; }\n                else if (L[(i + 1) * w + j] >= L[i * w + j + 1]) { ops.push([\"-\", x[i]]); i++; }\n                else { ops.push([\"+\", y[j]]); j++; }\n            }\n            while (i < n) ops.push([\"-\", x[i++]]);\n            while (j < m) ops.push([\"+\", y[j++]]);\n        } else {\n            ops = x.map(l => [\"-\", l]).concat(y.map(l => [\"+\", l]));\n        }\n        const before = A.slice(Math.max(0, s - 2), s).map(l => [\" \", l]);\n        const after = A.slice(A.length - e, A.length - e + 2).map(l => [\" \", l]);\n        return { start: Math.max(0, s - 2) + 1, ops: before.concat(ops, after) };\n    };<\/p>\n<p>    const ehMark = (a, b) => {\n        let s = 0;\n        while (s < a.length &#038;&#038; s < b.length &#038;&#038; a[s] === b[s]) s++;\n        let e = 0;\n        while (e < a.length - s &#038;&#038; e < b.length - s &#038;&#038; a[a.length - 1 - e] === b[b.length - 1 - e]) e++;\n        const part = (str, color) => esc(str.slice(0, s)) + \"<mark style='background:\" + color + \"; color:inherit; border-radius:2px;'>\" + esc(str.slice(s, str.length - e)) + \"<\/mark>\" + esc(str.slice(str.length - e));\n        return [part(a, \"color-mix(in srgb, var(--fail) 30%, transparent)\"), part(b, \"color-mix(in srgb, var(--ok) 30%, transparent)\")];\n    };<\/p>\n<p>    const ehRefName = (path, value, rec) => {\n        if (value == null || value === \"\" || !rec) return \"\";\n        let t = null;\n        if (\/(^|\\.)(firingTriggerId|blockingTriggerId)\\[\\d+\\]$\/.test(path)) t = \"trigger\";\n        else if (\/(^|\\.)parentFolderId$\/.test(path)) t = \"folder\";\n        if (t) {\n            const it = rec.items[t] && rec.items[t].get(value);\n            return it ? it.n : \"\";\n        }\n        if (path === \"type\" && \/^cvt_\/.test(value)) {\n            let n = \"\";\n            (rec.items.customTemplate || new Map()).forEach((tpl, tid) => { if (value.endsWith(\"_\" + tid)) n = tpl.n; });\n            return n;\n        }\n        return \"\";\n    };<\/p>\n<p>    const ehRowsHtml = (rows, recBefore, recAfter) => {\n        if (!rows.length) return \"\";\n        const box = \"background:var(--bg-panel); border:1px solid var(--border); border-radius:4px; padding:6px 8px; font-family:monospace; font-size:0.82rem; white-space:pre-wrap; overflow-wrap:anywhere; margin:0;\";\n        return rows.slice(0, 200).map(r => {\n            const head = \"<\/p>\n<div style='color:var(--accent); font-family:monospace; font-size:0.8rem; margin:10px 0 4px; overflow-wrap:anywhere;'>\" + esc(r.path) + \"<\/div>\n<p>\";\n            const bRef = ehRefName(r.path, r.before, recBefore), aRef = ehRefName(r.path, r.after, recAfter);\n            const ref = (n) => (n ? \" <span style='color:var(--text-secondary);'>(\" + esc(n) + \")<\/span>\" : \"\");\n            const before = r.before == null ? null : r.before, after = r.after == null ? null : r.after;\n            if (before != null && after != null && (before.indexOf(\"\\n\") >= 0 || after.indexOf(\"\\n\") >= 0)) {\n                const d = ehLineDiff(before, after);\n                const lines = d.ops.slice(0, 400).map(op => {\n                    const bg = op[0] === \"-\" ? \"color-mix(in srgb, var(--fail) 18%, transparent)\" : op[0] === \"+\" ? \"color-mix(in srgb, var(--ok) 18%, transparent)\" : \"transparent\";\n                    return \"<\/p>\n<div style='background:\" + bg + \";'>\" + esc(op[0] + \" \" + op[1]) + \"<\/div>\n<p>\";\n                }).join(\"\");\n                const more = d.ops.length > 400 ? \"<\/p>\n<div style='color:var(--text-secondary);'>\" + esc(ehFmt(T.moreLines, { n: d.ops.length - 400 })) + \"<\/div>\n<p>\" : \"\";\n                return head + \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.78rem; margin-bottom:3px;'>\" + esc(ehFmt(T.fromLine, { n: d.start })) + \"<\/div>\n<div style='\" + box + \" white-space:pre; overflow-x:auto; overflow-wrap:normal;'>\" + lines + more + \"<\/div>\n<p>\";\n            }\n            let bHtml, aHtml;\n            if (before != null && after != null) {\n                const cut = (s) => (s.length > 2000 ? s.slice(0, 2000) + \" \u2026\" : s);\n                [bHtml, aHtml] = ehMark(cut(before), cut(after));\n            } else {\n                const cut = (s) => esc(s.length > 2000 ? s.slice(0, 2000) + \" \u2026\" : s);\n                bHtml = before == null ? \"<em style='color:var(--text-secondary);'>\" + esc(T.absent) + \"<\/em>\" : cut(before);\n                aHtml = after == null ? \"<em style='color:var(--text-secondary);'>\" + esc(T.absent) + \"<\/em>\" : cut(after);\n            }\n            return head + \"<\/p>\n<div style='display:grid; grid-template-columns:repeat(auto-fit, minmax(220px, 1fr)); gap:6px;'>\" +\n                \"<\/p>\n<div>\n<div style='color:var(--fail); font-size:0.75rem; font-weight:700;'>\" + esc(T.before) + \"<\/div>\n<pre style='\" + box + \"'>\" + bHtml + ref(bRef) + \"<\/pre>\n<\/div>\n<p>\" +\n                \"<\/p>\n<div>\n<div style='color:var(--ok); font-size:0.75rem; font-weight:700;'>\" + esc(T.after) + \"<\/div>\n<pre style='\" + box + \"'>\" + aHtml + ref(aRef) + \"<\/pre>\n<\/div>\n<\/div>\n<p>\";\n        }).join(\"\") + (rows.length > 200 ? \"<\/p>\n<div style='color:var(--text-secondary); margin-top:8px;'>\" + esc(ehFmt(T.moreFields, { n: rows.length - 200 })) + \"<\/div>\n<p>\" : \"\");\n    };<\/p>\n<p>    \/\/ Full configuration of a new element: one line per field instead of before\/after pairs.\n    const ehConfigHtml = (rows, rec) => \"<\/p>\n<div style='background:var(--bg-panel); border:1px solid var(--border); border-radius:4px; padding:8px 10px; margin-top:8px; font-family:monospace; font-size:0.82rem; overflow-x:auto;'>\" +\n        rows.map(r => {\n            const v = r.after == null ? \"\" : r.after;\n            const n = ehRefName(r.path, v, rec);\n            return \"<\/p>\n<div style='display:flex; gap:12px; flex-wrap:wrap; padding:3px 0; border-bottom:1px solid var(--border);'><span style='color:var(--accent); min-width:220px;'>\" + esc(r.path) + \"<\/span><span style='white-space:pre-wrap; overflow-wrap:anywhere; flex:1; min-width:180px; color:var(--text-primary);'>\" + esc(v.length > 4000 ? v.slice(0, 4000) + \" \u2026\" : v) + (n ? \" <span style='color:var(--text-secondary);'>(\" + esc(n) + \")<\/span>\" : \"\") + \"<\/span><\/div>\n<p>\";\n        }).join(\"\") + \"<\/p><\/div>\n<p>\";<\/p>\n<p>    const ehBadge = (text, color) => \"<span style='display:inline-block; border:1px solid \" + color + \"; color:\" + color + \"; padding:2px 8px; border-radius:4px; font-size:0.72rem; font-weight:700; letter-spacing:0.04em; text-transform:uppercase;'>\" + esc(text) + \"<\/span>\";<\/p>\n<p>    const ehDepHtml = (deps, recBefore, recAfter) => {\n        if (!deps.length) return \"\";\n        return \"<\/p>\n<div style='margin-top:14px; border-top:1px dashed var(--border); padding-top:10px;'>\n<div style='color:var(--text-primary); font-weight:700; font-size:0.88rem;'>\" + esc(T.depsTitle) + \"<\/div>\n<p>\" +\n            deps.map(d => {\n                const label = ehTypeName(d.t) + \" \u00b7 \" + (d.n || d.id) + (d.id.charAt(0) === \"?\" || d.t === \"builtInVariable\" ? \"\" : \" (ID \" + d.id + \")\");\n                const text = T[d.kind];\n                const inner = d.kind === \"depChanged\"\n                    ? \"<\/p>\n<details style='margin-top:4px;'>\n<summary style='cursor:pointer; color:var(--text-secondary); font-size:0.85rem;'>\" + esc(ehFmt(T.fieldsChanged, { n: d.rows.length })) + \"<\/summary>\n<p>\" + ehRowsHtml(d.rows, recBefore, recAfter) + \"<\/details>\n<p>\"\n                    : \"\";\n                return \"<\/p>\n<div style='margin-top:8px; font-size:0.88rem; color:var(--text-secondary);'>\" + ehBadge(text, d.kind === \"depAdded\" ? \"var(--ok)\" : d.kind === \"depChanged\" ? \"var(--lw-tool-warn)\" : \"var(--fail)\") + \" <strong style='color:var(--text-primary);'>\" + esc(label) + \"<\/strong>\" + (d.missing ? \" \u2013 \" + esc(T.depNotInContainer) : \"\") + inner + \"<\/div>\n<p>\";\n            }).join(\"\") + \"<\/p><\/div>\n<p>\";\n    };<\/p>\n<p>    const ehVersionLink = (rec) => (\/^https:\\\/\\\/tagmanager\\.google\\.com\\\/\/.test(rec.url || \"\") ? \" \u00b7 <a href='\" + ehAttr(rec.url) + \"' target='_blank' rel='noopener noreferrer'>\" + esc(T.openInGtm) + \"<\/a>\" : \"\");<\/p>\n<p>    const ehEventHtml = (ev, prevRecOf) => {\n        const rec = ev.version;\n        const color = EH_EVENT_COLORS[ev.kind];\n        const verDate = ehDate(rec.fp);\n        const entDate = ev.entry && ev.kind !== \"removed\" ? ehDate(ev.entry.f) : \"\";\n        const live = ehLiveId === rec.id ? \" \" + ehBadge(T.live, \"var(--ok)\") : \"\";\n        const title = ev.kind === \"renamed\" ? ehFmt(T.ev_renamed, { from: ev.oldName, to: ev.entry.n }) : T[\"ev_\" + ev.kind];\n        const meta = [];\n        if (verDate) meta.push(esc(T.versionDate) + \": <strong style='color:var(--text-primary);'>\" + esc(verDate) + \"<\/strong>\");\n        if (entDate) meta.push(esc(T.elementDate) + \": \" + esc(entDate));\n        if (ev.since) meta.push(esc(ehFmt(T.absentSince, { v: ev.since.id })));\n        if (ev.gap) meta.push(\"<span style='color:var(--lw-tool-warn);'>\" + esc(ehFmt(T.gapNote, { n: ev.gap })) + \"<\/span>\");\n        const rowsHtml = ev.rows.length\n            ? (ev.kind === \"baseline\" || ev.kind === \"created\"\n                ? \"<\/p>\n<details style='margin-top:10px;'>\n<summary style='cursor:pointer; color:var(--text-secondary); font-size:0.85rem;'>\" + esc(ehFmt(T.showConfig, { n: ev.rows.length })) + \"<\/summary>\n<p>\" + ehConfigHtml(ev.rows, rec) + \"<\/details>\n<p>\"\n                : \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.85rem; margin-top:10px;'>\" + esc(ehFmt(T.fieldsChanged, { n: ev.rows.length })) + \"<\/div>\n<p>\" + ehRowsHtml(ev.rows, prevRecOf(rec), rec))\n            : \"\";\n        return \"<\/p>\n<div style='background:var(--bg-body); border:1px solid var(--border); border-left:3px solid \" + color + \"; border-radius:8px; padding:16px 18px; margin-bottom:12px;'>\" +\n            \"<\/p>\n<div style='display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap; align-items:center;'>\" +\n            \"<\/p>\n<div style='display:flex; gap:10px; align-items:center; flex-wrap:wrap;'>\" + ehBadge(T[\"k_\" + ev.kind], color) + \"<strong style='color:var(--text-primary);'>\" + esc(title) + \"<\/strong><\/div>\n<p>\" +\n            \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.85rem;'>\" + esc(T.version) + \" <strong style='color:var(--accent);'>v\" + esc(rec.id) + \"<\/strong>\" + (rec.name ? \" (\" + esc(rec.name) + \")\" : \"\") + live + ehVersionLink(rec) + \"<\/div>\n<\/div>\n<p>\" +\n            (meta.length ? \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.84rem; margin-top:8px; display:flex; gap:6px 16px; flex-wrap:wrap;'>\" + meta.map(x => \"<span>\" + x + \"<\/span>\").join(\"\") + \"<\/div>\n<p>\" : \"\") +\n            (rec.desc ? \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.84rem; margin-top:6px; white-space:pre-wrap; overflow-wrap:anywhere;'><strong>\" + esc(T.versionNotes) + \":<\/strong> \" + esc(rec.desc.slice(0, 1000)) + \"<\/div>\n<p>\" : \"\") +\n            rowsHtml + ehDepHtml(ev.deps || [], prevRecOf(rec), rec) + \"<\/p><\/div>\n<p>\";\n    };<\/p>\n<p>    const ehDraftHtml = (d) => {\n        const color = EH_EVENT_COLORS.draft;\n        const entDate = ehDate(d.fpEntity);\n        const st = T[\"ws_\" + d.status] || d.status;\n        return \"<\/p>\n<div style='background:var(--bg-body); border:1px dashed \" + color + \"; border-radius:8px; padding:16px 18px; margin-bottom:12px;'>\" +\n            \"<\/p>\n<div style='display:flex; gap:10px; align-items:center; flex-wrap:wrap;'>\" + ehBadge(T.k_draft, color) + \"<strong style='color:var(--text-primary);'>\" + esc(ehFmt(T.ev_draft, { ws: d.workspace, status: st })) + \"<\/strong><\/div>\n<p>\" +\n            (entDate ? \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.84rem; margin-top:8px;'>\" + esc(T.elementDate) + \": \" + esc(entDate) + \"<\/div>\n<p>\" : \"\") +\n            (d.rows.length ? \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.85rem; margin-top:10px;'>\" + esc(ehFmt(T.draftCompared, { n: d.rows.length, v: ehLast.newest.id })) + \"<\/div>\n<p>\" + ehRowsHtml(d.rows, ehLast.newest, ehLast.newest) : \"\") +\n            \"<\/p><\/div>\n<p>\";\n    };<\/p>\n<p>    const ehDownload = (name, mime, text) => {\n        const url = URL.createObjectURL(new Blob([text], { type: mime }));\n        const a = document.createElement(\"a\");\n        a.href = url;\n        a.download = name;\n        document.body.appendChild(a);\n        a.click();\n        a.remove();\n        setTimeout(() => URL.revokeObjectURL(url), 2000);\n    };<\/p>\n<p>    const ehExportData = () => {\n        const L = ehLast;\n        return {\n            tool: \"GTM element change history \u2013 lukaswojcik.com\",\n            created: new Date().toISOString(),\n            account: L.account, container: L.container, liveVersion: ehLiveId,\n            scannedVersions: L.scanned, unreadableVersions: L.unreadable,\n            elements: L.histories.map(h => ({\n                type: h.type, id: h.id, name: h.name, names: h.names,\n                events: h.events.map(ev => ({\n                    event: ev.kind, version: ev.version.id, versionName: ev.version.name, versionNotes: ev.version.desc,\n                    versionSaved: ehDate(ev.version.fp), elementSaved: ev.entry ? ehDate(ev.entry.f) : \"\",\n                    previousName: ev.oldName || undefined, unreadableVersionsBefore: ev.gap || undefined,\n                    fields: ev.rows, dependencies: (ev.deps || []).map(d => ({ change: d.kind, type: d.t, id: d.id, name: d.n, fields: d.rows || [] }))\n                })),\n                drafts: h.drafts.map(d => ({ workspace: d.workspace, status: d.status, elementSaved: ehDate(d.fpEntity), fields: d.rows }))\n            }))\n        };\n    };<\/p>\n<p>    const ehCsv = () => {\n        const q = (v) => '\"' + String(v == null ? \"\" : v).replace(\/\"\/g, '\"\"') + '\"';\n        const lines = [[\"element_type\", \"element_id\", \"element_name\", \"version\", \"version_name\", \"version_saved\", \"event\", \"field\", \"before\", \"after\"].map(q).join(\",\")];\n        ehLast.histories.forEach(h => {\n            h.events.forEach(ev => {\n                const base = [h.type, h.id, ev.entry ? ev.entry.n : h.name, ev.version.id, ev.version.name, ehDate(ev.version.fp), ev.kind];\n                const rows = ev.rows.length ? ev.rows : [{ path: \"\", before: \"\", after: \"\" }];\n                rows.forEach(r => lines.push(base.concat([r.path, r.before, r.after]).map(q).join(\",\")));\n                (ev.deps || []).forEach(d => {\n                    (d.rows && d.rows.length ? d.rows : [{ path: \"\", before: \"\", after: \"\" }]).forEach(r => {\n                        lines.push(base.concat([d.kind + \" \" + d.t + \":\" + d.id + \" \" + d.n + (r.path ? \" \u2192 \" + r.path : \"\"), r.before, r.after]).map(q).join(\",\"));\n                    });\n                });\n            });\n            h.drafts.forEach(d => {\n                (d.rows.length ? d.rows : [{ path: \"\", before: \"\", after: \"\" }]).forEach(r => {\n                    lines.push([h.type, h.id, d.name || h.name, \"workspace\", d.workspace, ehDate(d.fpEntity), \"draft_\" + d.status, r.path, r.before, r.after].map(q).join(\",\"));\n                });\n            });\n        });\n        return \"\ufeff\" + lines.join(\"\\r\\n\");\n    };<\/p>\n<p>    const ehRender = () => {\n        const L = ehLast;\n        const rangeNote = L.isAll ? \"\" : \" \" + esc(T.rangeHint);\n        const summaryHead = \"<\/p>\n<div style='background:var(--bg-body); border:1px solid var(--border); border-radius:8px; padding:16px 18px; margin-bottom:18px; color:var(--text-secondary); font-size:0.9rem; line-height:1.6;'>\" +\n            \"<\/p>\n<div><strong style='color:var(--text-primary);'>\" + esc(T.account) + \":<\/strong> \" + esc(L.account.name) + \" (\" + esc(L.account.id) + \") \u00b7 <strong style='color:var(--text-primary);'>\" + esc(T.container) + \":<\/strong> \" + esc(L.container.name) + \" (\" + esc(L.container.publicId) + \")<\/div>\n<p>\" +\n            \"<\/p>\n<div><strong style='color:var(--text-primary);'>\" + esc(T.scanned) + \":<\/strong> \" + esc(ehFmt(T.scannedText, { n: L.scanned, from: L.from, to: L.to })) + (L.unreadable ? \" \u00b7 <span style='color:var(--lw-tool-warn);'>\" + esc(ehFmt(T.unreadable, { n: L.unreadable })) + \"<\/span>\" : \"\") + rangeNote + \"<\/div>\n<p>\" +\n            \"<\/p>\n<div><strong style='color:var(--text-primary);'>\" + esc(T.liveVersion) + \":<\/strong> \" + (ehLiveId ? \"v\" + esc(ehLiveId) : esc(T.noLive)) + \"<\/div>\n<p>\" +\n            (L.warn ? \"<\/p>\n<div style='color:var(--lw-tool-warn);'>\" + esc(L.warn) + \"<\/div>\n<p>\" : \"\") +\n            (L.draftError ? \"<\/p>\n<div style='color:var(--lw-tool-warn);'>\" + esc(T.draftError) + \" \" + esc(L.draftError) + \"<\/div>\n<p>\" : \"\") +\n            \"<\/p>\n<div style='margin-top:10px; display:flex; gap:10px; flex-wrap:wrap;'>\" +\n            \"<button type='button' id='eh-export-json' class='button' style='background:transparent; color:var(--accent); border:1px solid var(--accent); padding:7px 14px; border-radius:6px; cursor:pointer; font-weight:700;'>\" + esc(T.exportJson) + \"<\/button>\" +\n            \"<button type='button' id='eh-export-csv' class='button' style='background:transparent; color:var(--accent); border:1px solid var(--accent); padding:7px 14px; border-radius:6px; cursor:pointer; font-weight:700;'>\" + esc(T.exportCsv) + \"<\/button><\/div>\n<\/div>\n<p>\";<\/p>\n<p>        const okRecs = L.recs.filter(r => r.ok);\n        const prevRecOf = (rec) => { const i = okRecs.indexOf(rec); return i > 0 ? okRecs[i - 1] : null; };<\/p>\n<p>        const blocks = L.histories.map(h => {\n            const changes = h.events.filter(ev => ev.kind !== \"baseline\" && ev.kind !== \"touched\");\n            const lastChange = changes.length ? changes[changes.length - 1] : null;\n            const first = h.events[0];\n            const inLive = ehLiveId && L.recs.some(r => r.ok && r.id === ehLiveId && r.items[h.type] && r.items[h.type].has(h.id));\n            const facts = [\n                [T.elementId, h.id],\n                [T.firstSeen, first ? \"v\" + first.version.id + (first.kind === \"baseline\" ? \" \u2013 \" + T.beforeRange : \"\") + (ehDate(first.version.fp) ? \" \u00b7 \" + ehDate(first.version.fp) : \"\") : \"\u2013\"],\n                [T.lastChange, lastChange ? \"v\" + lastChange.version.id + \" \u00b7 \" + T[\"k_\" + lastChange.kind] + (ehDate(lastChange.version.fp) ? \" \u00b7 \" + ehDate(lastChange.version.fp) : \"\") : T.noChange],\n                [T.changeCount, String(changes.length)],\n                [T.inLive, ehLiveId ? (L.liveScanned ? (inLive ? T.yes : T.no) : T.liveOutOfRange) : \"\u2013\"]\n            ];\n            if (h.names.length > 1) facts.push([T.names, h.names.join(\" \u2192 \")]);\n            const head = \"<\/p>\n<div style='margin:22px 0 12px;'>\n<div style='display:flex; gap:10px; align-items:center; flex-wrap:wrap;'>\" + ehBadge(ehTypeName(h.type), \"var(--accent)\") + \"<strong style='color:var(--text-primary); font-size:1.1rem;'>\" + esc(h.name) + \"<\/strong><\/div>\n<p>\" +\n                \"<\/p>\n<div style='display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:8px; margin-top:10px;'>\" +\n                facts.map(f => \"<\/p>\n<div style='background:var(--bg-body); border:1px solid var(--border); border-radius:6px; padding:8px 10px;'>\n<div style='color:var(--text-secondary); font-size:0.75rem;'>\" + esc(f[0]) + \"<\/div>\n<div style='color:var(--text-primary); font-size:0.9rem; overflow-wrap:anywhere;'>\" + esc(f[1]) + \"<\/div>\n<\/div>\n<p>\").join(\"\") +\n                \"<\/p><\/div>\n<\/div>\n<p>\";\n            const drafts = h.drafts.map(ehDraftHtml).join(\"\");\n            const evs = h.events.slice().reverse().map(ev => ehEventHtml(ev, prevRecOf)).join(\"\");\n            return head + drafts + evs;\n        }).join(\"\");<\/p>\n<p>        const note = \"<\/p>\n<div style='color:var(--text-secondary); font-size:0.82rem; line-height:1.55; margin-top:18px; border-top:1px solid var(--border); padding-top:12px;'>\" + esc(T.limits) + \"<\/div>\n<p>\";\n        ehResults.innerHTML = summaryHead + (L.histories.length > 1 ? \"<\/p>\n<div style='color:var(--lw-tool-warn); font-size:0.9rem;'>\" + esc(ehFmt(T.multiMatch, { n: L.histories.length })) + \"<\/div>\n<p>\" : \"\") + blocks + note;\n        const stamp = new Date().toISOString().slice(0, 10);\n        const fname = \"gtm-element-history-\" + (L.container.publicId || \"container\") + \"-\" + stamp;\n        document.getElementById(\"eh-export-json\").addEventListener(\"click\", () => ehDownload(fname + \".json\", \"application\/json\", JSON.stringify(ehExportData(), null, 2)));\n        document.getElementById(\"eh-export-csv\").addEventListener(\"click\", () => ehDownload(fname + \".csv\", \"text\/csv;charset=utf-8\", ehCsv()));\n    };<\/p>\n<p>    const ehRun = async () => {\n        const raw = ehQuery.value;\n        if (!raw.trim()) { ehStatus.textContent = T.needQuery; ehQuery.focus(); return; }\n        if (!versionHeaders.length) { ehStatus.textContent = T.noVersions; return; }\n        const parsed = ehParse(raw);\n        const gen = ehGen;\n        const rangeVal = rangeSelect.value;\n        const toScan = rangeVal === \"all\" ? versionHeaders : versionHeaders.slice(0, Number(rangeVal));\n        const opts = { deps: document.getElementById(\"eh-deps\").checked, drafts: document.getElementById(\"eh-drafts\").checked, touched: document.getElementById(\"eh-touched\").checked };<\/p>\n<p>        ehRunBtn.disabled = true;\n        ehResults.innerHTML = \"\";\n        ehStatus.textContent = \"\";\n        const missing = toScan.filter(vh => !ehCache.has(vh.path));\n        const recsByPath = new Map();\n        toScan.forEach(vh => { if (ehCache.has(vh.path)) recsByPath.set(vh.path, ehCache.get(vh.path)); });\n        let authFailed = false;\n        if (missing.length) {\n            ehProg.style.display = \"block\";\n            let done = 0;\n            const concurrency = 3;\n            for (let i = 0; i < missing.length &#038;&#038; !authFailed; i += concurrency) {\n                await Promise.all(missing.slice(i, i + concurrency).map(async vh => {\n                    const rec = await ehFetch(vh, gen);\n                    if (!rec.ok && \/\\b401\\b\/.test(rec.err || \"\")) authFailed = true;\n                    recsByPath.set(vh.path, rec);\n                    done++;\n                    const pct = Math.round(done \/ missing.length * 100);\n                    ehProgBar.style.width = pct + \"%\";\n                    ehProgPct.textContent = pct + \"%\";\n                    ehProgText.textContent = ehFmt(T.progress, { n: done, total: missing.length });\n                }));\n                if (gen !== ehGen) { ehRunBtn.disabled = false; ehProg.style.display = \"none\"; return; }\n            }\n            ehProg.style.display = \"none\";\n        }\n        ehRunBtn.disabled = false;\n        if (authFailed) { ehStatus.innerHTML = \"<span style='color:var(--fail);'>\" + esc(T.authExpired) + \"<\/span>\"; return; }<\/p>\n<p>        const recs = toScan.map(vh => recsByPath.get(vh.path) || { id: String(vh.containerVersionId), ok: false, items: {} }).sort(ehNumSort);\n        const okRecs = recs.filter(r => r.ok);\n        if (!okRecs.length) { ehStatus.innerHTML = \"<span style='color:var(--fail);'>\" + esc(T.nothingReadable) + \"<\/span>\"; return; }<\/p>\n<p>        const types = parsed.type === \"any\" ? Object.keys(EH_TYPES) : [parsed.type];\n        const lc = parsed.q.toLowerCase();\n        const keys = new Map();\n        okRecs.forEach(rec => types.forEach(t => {\n            const m = rec.items[t];\n            if (!m) return;\n            if (parsed.id) { if (m.has(parsed.id)) keys.set(t + \":\" + parsed.id, { t: t, id: parsed.id }); return; }\n            m.forEach((it, id) => {\n                if (it.n.toLowerCase() === lc || id === parsed.q || (t === \"builtInVariable\" && id.toLowerCase() === lc)) keys.set(t + \":\" + id, { t: t, id: id });\n            });\n        }));<\/p>\n<p>        if (!keys.size) {\n            const sugg = [];\n            const newest = okRecs[okRecs.length - 1];\n            types.forEach(t => (newest.items[t] || new Map()).forEach((it, id) => {\n                if (sugg.length < 12 &#038;&#038; lc &#038;&#038; it.n.toLowerCase().indexOf(lc) >= 0) sugg.push(ehTypeName(t) + \": \" + it.n);\n            }));\n            ehStatus.innerHTML = \"\";\n            ehResults.innerHTML = \"<\/p>\n<div style='background:var(--bg-body); padding:22px; border-radius:6px; border:1px solid var(--border); color:var(--text-secondary);'>\" +\n                esc(ehFmt(T.notFound, { q: parsed.q, n: okRecs.length })) + (parsed.warn ? \"<br \/>\" + esc(parsed.warn) : \"\") +\n                (sugg.length ? \"<\/p>\n<div style='margin-top:10px;'>\" + esc(T.similar) + \"<\/div>\n<ul style='margin:6px 0 0;'>\" + sugg.map(s => \"<\/p>\n<li>\" + esc(s) + \"<\/li>\n<p>\").join(\"\") + \"<\/ul>\n<p>\" : \"\") + \"<\/p><\/div>\n<p>\";\n            return;\n        }<\/p>\n<p>        const list = Array.from(keys.values()).slice(0, 10);\n        const histories = list.map(k => {\n            const events = ehTimeline(recs, k.t, k.id, opts);\n            const names = [];\n            okRecs.forEach(r => { const it = r.items[k.t] && r.items[k.t].get(k.id); if (it && names[names.length - 1] !== it.n) names.push(it.n); });\n            return { type: k.t, id: k.id, name: names[names.length - 1] || k.id, names: names, events: events, drafts: [] };\n        });<\/p>\n<p>        let draftError = \"\";\n        if (opts.drafts) {\n            ehStatus.textContent = T.loadingDrafts;\n            try {\n                const drafts = await ehDrafts(new Set(list.map(k => k.t + \":\" + k.id)), okRecs[okRecs.length - 1]);\n                drafts.forEach(d => { const h = histories.find(x => x.type === d.t && x.id === d.id); if (h) h.drafts.push(d); });\n            } catch (e) { draftError = e.message; }\n            if (gen !== ehGen) return;\n        }<\/p>\n<p>        const cont = containersMap[contSelect.value] || {};\n        ehLast = {\n            recs: recs, histories: histories, newest: okRecs[okRecs.length - 1], warn: parsed.warn, draftError: draftError,\n            account: { id: accSelect.value, name: accountsMap[accSelect.value] || \"\" },\n            container: { id: contSelect.value, name: cont.name || \"\", publicId: cont.publicId || \"\" },\n            scanned: okRecs.length, unreadable: recs.length - okRecs.length, from: \"v\" + okRecs[0].id, to: \"v\" + okRecs[okRecs.length - 1].id,\n            isAll: rangeVal === \"all\" || toScan.length === versionHeaders.length,\n            liveScanned: !!ehLiveId && okRecs.some(r => r.id === ehLiveId)\n        };\n        ehStatus.textContent = keys.size > 10 ? ehFmt(T.tooMany, { n: keys.size }) : \"\";\n        ehRender();\n    };<\/p>\n<p>    ehRunBtn.addEventListener(\"click\", ehRun);\n    ehQuery.addEventListener(\"keydown\", (e) => { if (e.key === \"Enter\") { e.preventDefault(); ehRun(); } });\n})();\n<\/script><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Durchsucht gew\u00e4hlte Tag-, Trigger-, Variablen-, Template- und Ordnerlisten abgerufener GTM-Versionen und zeigt den \u00c4nderungsverlauf eines einzelnen Elements. Laufzeitverhalten, zentrale Einstellungen und Rechte ben\u00f6tigen einen eigenen Abgleich.<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":9283,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"template-tool-base.php","meta":{"footnotes":""},"tags":[92145],"class_list":["post-9319","page","type-page","status-publish","hentry","tag-google-tag-manager-de"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>GTM-Containerverlauf und Tiefensuche | Lukas Wojcik<\/title>\n<meta name=\"description\" content=\"GTM-Versionslisten durchsuchen oder ein einzelnes Tag, einen Trigger oder eine Variable \u00fcber die Versionen verfolgen, mit Google-Anmeldung. Der Konfigurationsverlauf belegt kein Laufzeitverhalten.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"GTM-Containerverlauf und Tiefensuche | Lukas Wojcik\" \/>\n<meta property=\"og:description\" content=\"GTM-Versionslisten mit Google-Anmeldung durchsuchen. Der Konfigurationsverlauf belegt kein Laufzeitverhalten und stellt keine Rechte wieder her.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/\" \/>\n<meta property=\"og:site_name\" content=\"Lukas Wojcik - Blog\" \/>\n<meta property=\"article:modified_time\" content=\"2026-10-23T13:00:33+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1200\" \/>\n\t<meta property=\"og:image:height\" content=\"630\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:description\" content=\"GTM-Versionslisten mit Google-Anmeldung durchsuchen. Der Konfigurationsverlauf belegt kein Laufzeitverhalten und stellt keine Rechte wieder her.\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"2 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/gtm-container-history-deep-search-engine\\\/\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/gtm-container-history-deep-search-engine\\\/\",\"name\":\"GTM-Containerverlauf und Tiefensuche | Lukas Wojcik\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\"},\"datePublished\":\"2026-08-09T13:37:30+00:00\",\"dateModified\":\"2026-10-23T13:00:33+00:00\",\"description\":\"GTM-Versionslisten durchsuchen oder ein einzelnes Tag, einen Trigger oder eine Variable \u00fcber die Versionen verfolgen, mit Google-Anmeldung. Der Konfigurationsverlauf belegt kein Laufzeitverhalten.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/gtm-container-history-deep-search-engine\\\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/gtm-container-history-deep-search-engine\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/gtm-container-history-deep-search-engine\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Toolbox\",\"item\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/de\\\/werkzeuge\\\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"GTM-Containerverlauf und Tiefensuche\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#website\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/\",\"name\":\"Lukas Wojcik - Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/#\\\/schema\\\/person\\\/895f7604f9b6b71aad9bba33af28d0f9\",\"name\":\"Lukas Wojcik\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"url\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"contentUrl\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\",\"width\":424,\"height\":636,\"caption\":\"Lukas Wojcik\"},\"logo\":{\"@id\":\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\\\/wp-content\\\/uploads\\\/2026\\\/07\\\/lw-x2.jpg\"},\"sameAs\":[\"https:\\\/\\\/www.lukaswojcik.com\\\/blog\"]}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"GTM-Containerverlauf und Tiefensuche | Lukas Wojcik","description":"GTM-Versionslisten durchsuchen oder ein einzelnes Tag, einen Trigger oder eine Variable \u00fcber die Versionen verfolgen, mit Google-Anmeldung. Der Konfigurationsverlauf belegt kein Laufzeitverhalten.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/","og_locale":"en_US","og_type":"article","og_title":"GTM-Containerverlauf und Tiefensuche | Lukas Wojcik","og_description":"GTM-Versionslisten mit Google-Anmeldung durchsuchen. Der Konfigurationsverlauf belegt kein Laufzeitverhalten und stellt keine Rechte wieder her.","og_url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/","og_site_name":"Lukas Wojcik - Blog","article_modified_time":"2026-10-23T13:00:33+00:00","og_image":[{"width":1200,"height":630,"url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/08\/og-default.jpg","type":"image\/jpeg"}],"twitter_card":"summary_large_image","twitter_description":"GTM-Versionslisten mit Google-Anmeldung durchsuchen. Der Konfigurationsverlauf belegt kein Laufzeitverhalten und stellt keine Rechte wieder her.","twitter_misc":{"Est. reading time":"2 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/","url":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/","name":"GTM-Containerverlauf und Tiefensuche | Lukas Wojcik","isPartOf":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#website"},"datePublished":"2026-08-09T13:37:30+00:00","dateModified":"2026-10-23T13:00:33+00:00","description":"GTM-Versionslisten durchsuchen oder ein einzelnes Tag, einen Trigger oder eine Variable \u00fcber die Versionen verfolgen, mit Google-Anmeldung. Der Konfigurationsverlauf belegt kein Laufzeitverhalten.","breadcrumb":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/gtm-container-history-deep-search-engine\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.lukaswojcik.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Toolbox","item":"https:\/\/www.lukaswojcik.com\/blog\/de\/werkzeuge\/"},{"@type":"ListItem","position":3,"name":"GTM-Containerverlauf und Tiefensuche"}]},{"@type":"WebSite","@id":"https:\/\/www.lukaswojcik.com\/blog\/#website","url":"https:\/\/www.lukaswojcik.com\/blog\/","name":"Lukas Wojcik - Blog","description":"","publisher":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.lukaswojcik.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.lukaswojcik.com\/blog\/#\/schema\/person\/895f7604f9b6b71aad9bba33af28d0f9","name":"Lukas Wojcik","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","url":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","contentUrl":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg","width":424,"height":636,"caption":"Lukas Wojcik"},"logo":{"@id":"https:\/\/www.lukaswojcik.com\/blog\/wp-content\/uploads\/2026\/07\/lw-x2.jpg"},"sameAs":["https:\/\/www.lukaswojcik.com\/blog"]}]}},"_links":{"self":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9319","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/comments?post=9319"}],"version-history":[{"count":6,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9319\/revisions"}],"predecessor-version":[{"id":27564,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9319\/revisions\/27564"}],"up":[{"embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/pages\/9283"}],"wp:attachment":[{"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/media?parent=9319"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lukaswojcik.com\/blog\/wp-json\/wp\/v2\/tags?post=9319"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}