diff --git a/app/templates/v2/components/icon.html b/app/templates/v2/components/icon.html
index 3f1ec60..86eaa51 100644
--- a/app/templates/v2/components/icon.html
+++ b/app/templates/v2/components/icon.html
@@ -1 +1,2 @@
-{% macro icon(name, size=16, cls="") %}{% include "v2/icons/phosphor/" ~ name ~ ".svg" %}{% endmacro %}
+{# `ignore missing`: fehlendes Icon-File rendert leeren Span statt 500. #}
+{% macro icon(name, size=16, cls="") %}{% include "v2/icons/phosphor/" ~ name ~ ".svg" ignore missing %}{% endmacro %}
diff --git a/tests/test_icons.py b/tests/test_icons.py
new file mode 100644
index 0000000..c6ac2c6
--- /dev/null
+++ b/tests/test_icons.py
@@ -0,0 +1,63 @@
+"""Verify dass jeder im Template-Code referenzierte Icon-Name als
+phosphor/.svg-File existiert.
+
+Hintergrund: Vor diesem Test gab es einen 500 nach Login, weil
+`base.html` `icon("scales")` aufrief, das SVG aber nicht im Repo lag
+(commit 741faae). Mit `ignore missing` rendert das Macro jetzt leer
+statt zu crashen — aber wir wollen trotzdem in CI sehen, dass kein
+Aufruf ins Leere läuft.
+"""
+from __future__ import annotations
+
+import re
+from pathlib import Path
+
+import pytest
+
+
+REPO_ROOT = Path(__file__).resolve().parent.parent
+TEMPLATES_DIR = REPO_ROOT / "app" / "templates"
+ICONS_DIR = TEMPLATES_DIR / "v2" / "icons" / "phosphor"
+
+# Matcht: icon("name", …) oder icon('name', …) — Default-Quote +/-.
+ICON_CALL_PATTERN = re.compile(r"""\bicon\(\s*['"]([a-z0-9-]+)['"]""")
+
+
+def _collect_referenced_icons() -> set[str]:
+ """Sammle alle Icon-Namen, die irgendwo in templates/ aufgerufen werden."""
+ referenced: set[str] = set()
+ for path in TEMPLATES_DIR.rglob("*.html"):
+ text = path.read_text(encoding="utf-8")
+ for m in ICON_CALL_PATTERN.finditer(text):
+ referenced.add(m.group(1))
+ return referenced
+
+
+def _existing_icons() -> set[str]:
+ """Set der existierenden phosphor-Icon-Namen (ohne .svg)."""
+ if not ICONS_DIR.exists():
+ return set()
+ return {p.stem for p in ICONS_DIR.glob("*.svg")}
+
+
+class TestIconCoverage:
+ def test_phosphor_dir_exists(self):
+ assert ICONS_DIR.is_dir(), f"erwartetes Verzeichnis fehlt: {ICONS_DIR}"
+
+ def test_at_least_one_icon_present(self):
+ icons = _existing_icons()
+ assert icons, "keine Icon-Files im phosphor/-Verzeichnis"
+
+ def test_at_least_one_call_referenced(self):
+ refs = _collect_referenced_icons()
+ assert refs, "keine icon('name')-Aufrufe in Templates gefunden"
+
+ def test_every_referenced_icon_has_file(self):
+ """Jeder im Template referenzierte Icon-Name muss als SVG-File existieren."""
+ referenced = _collect_referenced_icons()
+ existing = _existing_icons()
+ missing = referenced - existing
+ assert not missing, (
+ f"{len(missing)} referenzierte Icons fehlen in "
+ f"{ICONS_DIR.relative_to(REPO_ROOT)}: {sorted(missing)}"
+ )