diff --git a/.architecture-rules/frontend.yaml b/.architecture-rules/frontend.yaml
index 7ce9181f..3ad91dce 100644
--- a/.architecture-rules/frontend.yaml
+++ b/.architecture-rules/frontend.yaml
@@ -243,6 +243,35 @@ template_rules:
file_pattern: "app/templates/**/*.html"
recommended_pattern: 'No items'
+ - id: "TPL-008"
+ name: "Use valid block names from base templates"
+ severity: "error"
+ description: |
+ Templates must use block names that exist in their base template.
+ Using undefined blocks silently fails (content is not rendered).
+
+ Admin base blocks: title, extra_head, alpine_data, content, extra_scripts
+ Vendor base blocks: title, extra_head, alpine_data, content, extra_scripts
+ Shop base blocks: title, description, extra_head, alpine_data, content, extra_scripts
+
+ WRONG: {% block page_scripts %}...{% endblock %} (undefined)
+ RIGHT: {% block extra_scripts %}...{% endblock %}
+ pattern:
+ file_pattern: "app/templates/admin/**/*.html"
+ valid_blocks:
+ - "title"
+ - "extra_head"
+ - "alpine_data"
+ - "content"
+ - "extra_scripts"
+ forbidden_patterns:
+ - "{% block page_scripts %}"
+ - "{% block scripts %}"
+ - "{% block js %}"
+ - "{% block footer_scripts %}"
+ exceptions:
+ - "base.html"
+
# ============================================================================
# FRONTEND COMPONENT RULES
# ============================================================================
diff --git a/app/templates/admin/messages.html b/app/templates/admin/messages.html
index 1b241082..4cfcf73d 100644
--- a/app/templates/admin/messages.html
+++ b/app/templates/admin/messages.html
@@ -331,6 +331,6 @@
{% endcall %}
{% endblock %}
-{% block page_scripts %}
+{% block extra_scripts %}
{% endblock %}
diff --git a/scripts/validate_architecture.py b/scripts/validate_architecture.py
index 1f463b6f..40696e70 100755
--- a/scripts/validate_architecture.py
+++ b/scripts/validate_architecture.py
@@ -812,6 +812,10 @@ class ArchitectureValidator:
# TPL-007: Check empty state implementation
self._check_template_empty_state(file_path, content, lines)
+ # TPL-008: Check for invalid block names
+ if is_admin:
+ self._check_valid_block_names(file_path, content, lines)
+
if is_base_or_partial:
return
@@ -1053,6 +1057,50 @@ class ArchitectureValidator:
suggestion='Add No items found',
)
+ def _check_valid_block_names(
+ self, file_path: Path, content: str, lines: list[str]
+ ):
+ """TPL-008: Check that templates use valid block names from base template"""
+ if "noqa: tpl-008" in content.lower():
+ return
+
+ # Skip base templates
+ if file_path.name == "base.html":
+ return
+
+ # Valid admin template blocks
+ valid_blocks = {"title", "extra_head", "alpine_data", "content", "extra_scripts"}
+
+ # Common invalid block names that developers might mistakenly use
+ invalid_blocks = {
+ "page_scripts": "extra_scripts",
+ "scripts": "extra_scripts",
+ "js": "extra_scripts",
+ "footer_scripts": "extra_scripts",
+ "head": "extra_head",
+ "body": "content",
+ "main": "content",
+ }
+
+ import re
+ block_pattern = re.compile(r"{%\s*block\s+(\w+)\s*%}")
+
+ for i, line in enumerate(lines, 1):
+ match = block_pattern.search(line)
+ if match:
+ block_name = match.group(1)
+ if block_name in invalid_blocks:
+ self._add_violation(
+ rule_id="TPL-008",
+ rule_name="Use valid block names from base templates",
+ severity=Severity.ERROR,
+ file_path=file_path,
+ line_number=i,
+ message=f"Invalid block name '{block_name}' - this block doesn't exist in admin/base.html",
+ context=line.strip(),
+ suggestion=f"Use '{{% block {invalid_blocks[block_name]} %}}' instead",
+ )
+
def _check_pagination_macro_usage(
self, file_path: Path, content: str, lines: list[str]
):