diff --git a/scripts/validate_architecture.py b/scripts/validate_architecture.py index c784c9e5..a9e5cb44 100755 --- a/scripts/validate_architecture.py +++ b/scripts/validate_architecture.py @@ -511,6 +511,173 @@ class ArchitectureValidator: suggestion='', ) + def _check_alerts_macro_usage(self, file_path: Path, content: str, lines: list[str]): + """FE-003: Check for inline loading/error states that should use alerts macro""" + # Check if already using the alerts macro + uses_macro = any("from 'shared/macros/alerts.html'" in line for line in lines) + if uses_macro: + return + + # Check for noqa comment + has_noqa = any("noqa: fe-003" in line.lower() for line in lines) + if has_noqa: + return + + # Look for inline loading states + for i, line in enumerate(lines, 1): + # Loading state pattern: text-center py-12 with loading content + if 'x-show="loading"' in line or "x-show='loading'" in line: + # Check if next few lines have spinner pattern + context_lines = "\n".join(lines[i-1:i+3]) + if "text-center" in context_lines and "py-12" in context_lines: + self._add_violation( + rule_id="FE-003", + rule_name="Use alerts macro", + severity=Severity.WARNING, + file_path=file_path, + line_number=i, + message="Inline loading state found - use loading_state macro", + context=line.strip()[:60], + suggestion="{% from 'shared/macros/alerts.html' import loading_state %}\n{{ loading_state('Loading...') }}", + ) + return # Only report once per file + + # Error state pattern: bg-red-100 border-red-400 + if "bg-red-100" in line and "border-red-400" in line: + self._add_violation( + rule_id="FE-003", + rule_name="Use alerts macro", + severity=Severity.WARNING, + file_path=file_path, + line_number=i, + message="Inline error state found - use error_state macro", + context=line.strip()[:60], + suggestion="{% from 'shared/macros/alerts.html' import error_state %}\n{{ error_state('Error', 'error') }}", + ) + return + + def _check_modals_macro_usage(self, file_path: Path, content: str, lines: list[str]): + """FE-004: Check for inline modals that should use modals macro""" + # Check if already using the modals macro + uses_macro = any("from 'shared/macros/modals.html'" in line for line in lines) + if uses_macro: + return + + # Check for noqa comment + has_noqa = any("noqa: fe-004" in line.lower() for line in lines) + if has_noqa: + return + + # Look for modal patterns: fixed inset-0 with role="dialog" or modal backdrop + for i, line in enumerate(lines, 1): + if "fixed inset-0" in line and ("z-50" in line or "z-30" in line or "z-40" in line): + # Check context for modal indicators + context_lines = "\n".join(lines[max(0, i-1):min(len(lines), i+5)]) + if 'role="dialog"' in context_lines or "bg-opacity-50" in context_lines or "bg-black/50" in context_lines: + self._add_violation( + rule_id="FE-004", + rule_name="Use modals macro", + severity=Severity.WARNING, + file_path=file_path, + line_number=i, + message="Inline modal found - use modal macro for consistency", + context=line.strip()[:60], + suggestion="{% from 'shared/macros/modals.html' import modal %}\n{% call modal('myModal', 'Title', 'isModalOpen') %}...{% endcall %}", + ) + return + + def _check_tables_macro_usage(self, file_path: Path, content: str, lines: list[str]): + """FE-005: Check for inline table wrappers that should use tables macro""" + # Check if already using the tables macro + uses_macro = any("from 'shared/macros/tables.html'" in line for line in lines) + if uses_macro: + return + + # Check for noqa comment + has_noqa = any("noqa: fe-005" in line.lower() for line in lines) + if has_noqa: + return + + # Look for table wrapper pattern: overflow-hidden rounded-lg shadow-xs + for i, line in enumerate(lines, 1): + if "overflow-hidden" in line and "rounded-lg" in line and "shadow-xs" in line: + # Check if there's a table nearby + context_lines = "\n".join(lines[i-1:min(len(lines), i+10)]) + if "