diff --git a/docs/_overrides/main.html b/docs/_overrides/main.html
index 5490c824..88b213b7 100644
--- a/docs/_overrides/main.html
+++ b/docs/_overrides/main.html
@@ -33,13 +33,19 @@
: document.querySelector("[data-aaasm-outdated]");
if (!host) return;
- // Derive the current version directory and the versions.json URL from the
- // page location: mike serves each version under ///, with
- // versions.json one level up at //versions.json.
- var path = window.location.pathname.replace(/\/+$/, "");
- var segments = path.split("/");
- var current = segments.pop() || "";
- var versionsUrl = segments.join("/") + "/versions.json";
+ // mike serves each version under ///, with versions.json
+ // one level up at //versions.json. mkdocs renders `base_url` so it
+ // points at the deploy root (the current version directory) from *any*
+ // page depth, so the deploy-root versions.json is reliably one level
+ // above it. Resolving the bare relative `base_url` against `location`
+ // yields an absolute URL; popping only the page's last path segment (the
+ // previous approach) 404s on deep pages.
+ var versionRoot = new URL("{{ base_url }}/", window.location);
+ var versionsUrl = new URL("../versions.json", versionRoot).toString();
+
+ // The current version id is the last path segment of the deploy root.
+ var current =
+ versionRoot.pathname.replace(/\/+$/, "").split("/").pop() || "";
fetch(versionsUrl, { cache: "no-store" })
.then(function (r) { return r.ok ? r.json() : []; })
@@ -103,3 +109,44 @@
})();
{% endblock %}
+
+{#-
+ Feedback widget decoupling (AAASM-3557, fixes the AAASM-3555 regression).
+
+ Material only reveals `form.md-feedback` (form.hidden = false) and wires the
+ 👍/👎 click handlers from inside its generated `__md_analytics()`, which runs
+ only after analytics consent is granted. With opt-in consent (off by default)
+ ordinary visitors never see the widget.
+
+ This script reveals the widget for everyone and wires our own handlers so the
+ thank-you note / "open an issue" link works without consent. It deliberately
+ does NOT call gtag: when consent IS granted, Material's own handler sends the
+ GA `feedback` event. Keeping the event in Material's handler preserves the
+ GDPR posture (event stays consent-gated) and avoids double-counting.
+-#}
+{% block scripts %}
+ {{ super() }}
+
+{% endblock %}
diff --git a/docs/_overrides/partials/javascripts/outdated.html b/docs/_overrides/partials/javascripts/outdated.html
new file mode 100644
index 00000000..17ca0c3c
--- /dev/null
+++ b/docs/_overrides/partials/javascripts/outdated.html
@@ -0,0 +1,13 @@
+{#-
+ Override of Material's partials/javascripts/outdated.html (AAASM-3557).
+
+ Material's generated script calls `new URL("{{ base_url }}")` with no base
+ argument. On many pages `base_url` is a bare relative value such as `.` or
+ `..`, which is not an absolute URL, so `new URL("..")` throws
+ `TypeError: Failed to construct 'URL': Invalid URL` on every page load.
+
+ The only change here is passing `location` as the base so the relative
+ `base_url` resolves against the current page. Behaviour is otherwise identical
+ to Material's generated script.
+-#}
+