diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml
index 6ede255a00a20c..92c311829c0654 100644
--- a/.github/workflows/docs.yaml
+++ b/.github/workflows/docs.yaml
@@ -18,7 +18,7 @@ concurrency:
jobs:
docs:
name: build docs
- runs-on: ubuntu-latest
+ runs-on: ubuntu-24.04
steps:
- uses: commaai/timeout@v1
diff --git a/docs/css/tooltip.css b/docs/css/tooltip.css
new file mode 100644
index 00000000000000..b9a54f793f9fe5
--- /dev/null
+++ b/docs/css/tooltip.css
@@ -0,0 +1,44 @@
+[data-tooltip] {
+ position: relative;
+ display: inline-block;
+ border-bottom: 1px dotted black;
+}
+
+[data-tooltip] .tooltip-content {
+ width: max-content;
+ max-width: 25em;
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ transform: translateX(-50%);
+ background-color: white;
+ color: #404040;
+ box-shadow: 0 4px 14px 0 rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05);
+ padding: 10px;
+ font: 14px/1.5 Lato, proxima-nova, Helvetica Neue, Arial, sans-serif;
+ text-decoration: none;
+ opacity: 0;
+ visibility: hidden;
+ transition: opacity 0.1s, visibility 0s;
+ z-index: 1000;
+ pointer-events: none; /* Prevent accidental interaction */
+}
+
+[data-tooltip]:hover .tooltip-content {
+ opacity: 1;
+ visibility: visible;
+ pointer-events: auto; /* Allow interaction when visible */
+}
+
+.tooltip-content .tooltip-glossary-link {
+ display: inline-block;
+ margin-top: 8px;
+ font-size: 12px;
+ color: #007bff;
+ text-decoration: none;
+}
+
+.tooltip-content .tooltip-glossary-link:hover {
+ color: #0056b3;
+ text-decoration: underline;
+}
diff --git a/docs/glossary.toml b/docs/glossary.toml
new file mode 100644
index 00000000000000..e69de29bb2d1d6
diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py
new file mode 100644
index 00000000000000..e2fa3d51e04cb9
--- /dev/null
+++ b/docs/hooks/glossary.py
@@ -0,0 +1,68 @@
+import re
+import tomllib
+
+def load_glossary(file_path="docs/glossary.toml"):
+ with open(file_path, "rb") as f:
+ glossary_data = tomllib.load(f)
+ return glossary_data.get("glossary", {})
+
+def generate_anchor_id(name):
+ return name.replace(" ", "-").replace("_", "-").lower()
+
+def format_markdown_term(name, definition):
+ anchor_id = generate_anchor_id(name)
+ markdown = f"* [**{name.replace('_', ' ').title()}**](#{anchor_id})"
+ if definition.get("abbreviation"):
+ markdown += f" *({definition['abbreviation']})*"
+ if definition.get("description"):
+ markdown += f": {definition['description']}\n"
+ return markdown
+
+def glossary_markdown(vocabulary):
+ markdown = ""
+ for category, terms in vocabulary.items():
+ markdown += f"## {category.replace('_', ' ').title()}\n\n"
+ for name, definition in terms.items():
+ markdown += format_markdown_term(name, definition)
+ return markdown
+
+def format_tooltip_html(term_key, definition, html):
+ display_term = term_key.replace("_", " ").title()
+ clean_description = re.sub(r"\[(.+)]\(.+\)", r"\1", definition["description"])
+ glossary_link = (
+ f"Glossaryđź”—"
+ )
+ return re.sub(
+ re.escape(display_term),
+ lambda
+ match: f"{match.group(0)}{clean_description} {glossary_link}",
+ html,
+ flags=re.IGNORECASE,
+ )
+
+def apply_tooltip(_term_key, _definition, pattern, html):
+ return re.sub(
+ pattern,
+ lambda match: format_tooltip_html(_term_key, _definition, match.group(0)),
+ html,
+ flags=re.IGNORECASE,
+ )
+
+def tooltip_html(vocabulary, html):
+ for _category, terms in vocabulary.items():
+ for term_key, definition in terms.items():
+ if definition.get("description"):
+ pattern = rf"(?)(?!\([^)]*\))"
+ html = apply_tooltip(term_key, definition, pattern, html)
+ return html
+
+# Page Hooks
+def on_page_markdown(markdown, **kwargs):
+ glossary = load_glossary()
+ return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(glossary))
+
+def on_page_content(html, **kwargs):
+ if kwargs.get("page").title == "Glossary":
+ return html
+ glossary = load_glossary()
+ return tooltip_html(glossary, html)
diff --git a/mkdocs.yml b/mkdocs.yml
index 58527ea7ee021d..a66d1c76d45bba 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -8,6 +8,10 @@ strict: true
docs_dir: docs
site_dir: docs_site/
+hooks:
+ - docs/hooks/glossary.py
+extra_css:
+ - css/tooltip.css
theme:
name: readthedocs
navigation_depth: 3