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: '' + - 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 ', ) + 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] ):