- Add /admin/admin-users routes for managing admin users (super admin only) - Remove vendor role from user creation form (vendors created via company hierarchy) - Add admin-users.html and admin-user-detail.html templates - Add admin-users.js and admin-user-detail.js for frontend logic - Move database operations to admin_platform_service (list, get, create, delete, toggle status) - Update sidebar to show Admin Users section only for super admins - Add isSuperAdmin computed property to init-alpine.js - Fix /api/v1 prefix issues in JS files (apiClient already adds prefix) - Update architecture rule JS-012 to catch more variable patterns (url, endpoint, path) - Replace inline SVGs with $icon() helper in select-platform.html Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
936 lines
31 KiB
YAML
936 lines
31 KiB
YAML
# Architecture Rules - Frontend Rules
|
|
# Combined rules for JavaScript, Templates, Components, and Styling
|
|
|
|
# ============================================================================
|
|
# JAVASCRIPT ARCHITECTURE RULES
|
|
# ============================================================================
|
|
|
|
javascript_rules:
|
|
|
|
- id: "JS-001"
|
|
name: "Use centralized logger, not console"
|
|
severity: "error"
|
|
description: |
|
|
Use window.LogConfig.createLogger() for consistent logging.
|
|
Never use console.log, console.error, console.warn directly.
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "console\\.log"
|
|
- "console\\.error"
|
|
- "console\\.warn"
|
|
exceptions:
|
|
- "// eslint-disable"
|
|
- "console.log('✅"
|
|
auto_exclude_files:
|
|
- "init-*.js"
|
|
- "vendor/"
|
|
|
|
- id: "JS-002"
|
|
name: "Use lowercase apiClient for API calls"
|
|
severity: "error"
|
|
description: |
|
|
Use lowercase 'apiClient' consistently, not 'ApiClient' or 'API_CLIENT'
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "ApiClient\\."
|
|
- "API_CLIENT\\."
|
|
required_pattern: "apiClient\\."
|
|
|
|
- id: "JS-003"
|
|
name: "Alpine components must spread ...data()"
|
|
severity: "error"
|
|
description: |
|
|
All Alpine.js components must inherit base layout data using spread operator
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
required_in_alpine_components:
|
|
- "\\.\\.\\.data\\(\\)"
|
|
|
|
- id: "JS-004"
|
|
name: "Alpine components must set currentPage"
|
|
severity: "error"
|
|
description: |
|
|
All Alpine.js page components must set a currentPage identifier
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
required_in_alpine_components:
|
|
- "currentPage:"
|
|
|
|
- id: "JS-005"
|
|
name: "Initialization methods must include guard"
|
|
severity: "error"
|
|
description: |
|
|
Init methods should prevent duplicate initialization with guard
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
recommended_pattern: |
|
|
if (window._pageInitialized) return;
|
|
window._pageInitialized = true;
|
|
auto_exclude_files:
|
|
- "vendor/"
|
|
|
|
- id: "JS-006"
|
|
name: "All async operations must have try/catch with error logging"
|
|
severity: "error"
|
|
description: |
|
|
All API calls and async operations must have error handling
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
check: "async_error_handling"
|
|
|
|
- id: "JS-013"
|
|
name: "Components overriding init() must call parent init"
|
|
severity: "error"
|
|
description: |
|
|
When an Alpine.js component spreads ...data() and defines its own init() method,
|
|
it MUST call the parent init() first. The parent init() sets critical properties
|
|
like vendorCode (from URL), currentUser, and theme preference.
|
|
|
|
Without calling parent init(), properties like vendorCode will be null, causing
|
|
API calls like `/vendor/${this.vendorCode}/settings` to fail with
|
|
"Endpoint not found: /api/v1/vendor/null/settings".
|
|
|
|
WRONG (parent init never called):
|
|
function vendorSettings() {
|
|
return {
|
|
...data(),
|
|
async init() {
|
|
await this.loadSettings(); // this.vendorCode is null!
|
|
}
|
|
};
|
|
}
|
|
|
|
RIGHT (call parent init first):
|
|
function vendorSettings() {
|
|
return {
|
|
...data(),
|
|
async init() {
|
|
// IMPORTANT: Call parent init first to set vendorCode from URL
|
|
const parentInit = data().init;
|
|
if (parentInit) {
|
|
await parentInit.call(this);
|
|
}
|
|
|
|
await this.loadSettings(); // this.vendorCode is now set
|
|
}
|
|
};
|
|
}
|
|
|
|
This pattern is required for ALL page-specific JavaScript files that:
|
|
1. Use ...data() to inherit base layout functionality
|
|
2. Define their own init() method
|
|
pattern:
|
|
file_pattern: "static/vendor/js/**/*.js"
|
|
check: "parent_init_call"
|
|
required_when:
|
|
- "contains: '...data()'"
|
|
- "contains: 'async init()'"
|
|
required_pattern:
|
|
- "data\\(\\)\\.init"
|
|
- "parentInit"
|
|
exceptions:
|
|
- "init-alpine.js"
|
|
- "login.js"
|
|
|
|
- id: "JS-014"
|
|
name: "Vendor API calls must not include vendorCode in path"
|
|
severity: "error"
|
|
description: |
|
|
Vendor API endpoints use JWT token authentication, NOT URL path parameters.
|
|
The vendor is identified from the JWT token via get_current_vendor_api dependency.
|
|
|
|
Do NOT include vendorCode in API paths for authenticated vendor endpoints.
|
|
|
|
WRONG (vendorCode in API path):
|
|
apiClient.get(`/vendor/${this.vendorCode}/orders`)
|
|
apiClient.post(`/vendor/${this.vendorCode}/products`, data)
|
|
|
|
RIGHT (no vendorCode, uses JWT):
|
|
apiClient.get(`/vendor/orders`)
|
|
apiClient.post(`/vendor/products`, data)
|
|
|
|
EXCEPTIONS (these endpoints DO use vendorCode in path):
|
|
- /vendor/{vendor_code} - Public vendor info (info.router)
|
|
- /vendor/{vendor_code}/content-pages/* - Content pages management
|
|
- Page URLs (not API calls) like window.location.href = `/vendor/${vendorCode}/...`
|
|
|
|
Why this matters:
|
|
- Including vendorCode causes 404 errors ("/vendor/wizamart/orders" not found)
|
|
- The JWT token already identifies the vendor
|
|
- Consistent with the API design pattern
|
|
pattern:
|
|
file_pattern: "static/vendor/js/**/*.js"
|
|
anti_patterns:
|
|
- "apiClient\\.(get|post|put|delete|patch)\\s*\\(\\s*`/vendor/\\$\\{this\\.vendorCode\\}/(orders|products|customers|inventory|analytics|dashboard|profile|settings|team|notifications|invoices|payments|media|marketplace|letzshop|billing|features|usage)"
|
|
exceptions:
|
|
- "init-alpine.js"
|
|
- "login.js"
|
|
- "content-pages.js"
|
|
- "content-page-edit.js"
|
|
|
|
- id: "JS-007"
|
|
name: "Set loading state before async operations"
|
|
severity: "warning"
|
|
description: |
|
|
Loading state should be set before and cleared after async operations
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
recommended_pattern: |
|
|
loading = true;
|
|
try {
|
|
// operation
|
|
} finally {
|
|
loading = false;
|
|
}
|
|
|
|
- id: "JS-008"
|
|
name: "Use apiClient for API calls, not raw fetch()"
|
|
severity: "error"
|
|
description: |
|
|
All API calls must use the apiClient helper instead of raw fetch().
|
|
The apiClient automatically:
|
|
- Adds Authorization header with JWT token from cookies
|
|
- Sets Content-Type headers
|
|
- Handles error responses consistently
|
|
- Provides logging integration
|
|
|
|
WRONG (raw fetch):
|
|
const response = await fetch('/api/v1/admin/products/123');
|
|
|
|
RIGHT (apiClient):
|
|
const response = await apiClient.get('/admin/products/123');
|
|
const result = await apiClient.post('/admin/products', data);
|
|
await apiClient.delete('/admin/products/123');
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "fetch\\('/api/"
|
|
- 'fetch\\("/api/'
|
|
- "fetch\\(`/api/"
|
|
exceptions:
|
|
- "init-api-client.js"
|
|
|
|
- id: "JS-009"
|
|
name: "Use Utils.showToast() for notifications, not alert() or window.showToast"
|
|
severity: "error"
|
|
description: |
|
|
All user notifications must use Utils.showToast() from static/shared/js/utils.js.
|
|
Never use browser alert() dialogs or undefined window.showToast.
|
|
|
|
Utils.showToast() provides:
|
|
- Consistent styling (Tailwind-based toast in bottom-right corner)
|
|
- Automatic fade-out after duration
|
|
- Color-coded types (success=green, error=red, warning=yellow, info=blue)
|
|
|
|
WRONG (browser dialog):
|
|
alert('Product saved successfully');
|
|
|
|
RIGHT (Utils helper):
|
|
Utils.showToast('Product saved successfully', 'success');
|
|
Utils.showToast('Failed to save product', 'error');
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "alert\\("
|
|
- "window\\.showToast"
|
|
exceptions:
|
|
- "utils.js"
|
|
|
|
- id: "JS-010"
|
|
name: "Use PlatformSettings for pagination rows per page"
|
|
severity: "error"
|
|
description: |
|
|
All pages with tables MUST use window.PlatformSettings.getRowsPerPage()
|
|
to load the platform-configured rows per page setting. This ensures
|
|
consistent pagination behavior across the entire admin and vendor interface.
|
|
|
|
The setting is configured at /admin/settings under the Display tab.
|
|
Settings are cached client-side for 5 minutes to minimize API calls.
|
|
|
|
Required pattern in init() method:
|
|
async init() {
|
|
// Guard against multiple initialization
|
|
if (window._pageNameInitialized) return;
|
|
window._pageNameInitialized = true;
|
|
|
|
// REQUIRED: Load platform settings for pagination
|
|
if (window.PlatformSettings) {
|
|
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
|
}
|
|
|
|
await this.loadData();
|
|
}
|
|
|
|
WRONG (hardcoded pagination):
|
|
pagination: {
|
|
page: 1,
|
|
per_page: 50, // Hardcoded!
|
|
total: 0
|
|
}
|
|
|
|
RIGHT (platform settings):
|
|
pagination: {
|
|
page: 1,
|
|
per_page: 20, // Default, overridden by PlatformSettings
|
|
total: 0
|
|
}
|
|
|
|
async init() {
|
|
if (window.PlatformSettings) {
|
|
this.pagination.per_page = await window.PlatformSettings.getRowsPerPage();
|
|
}
|
|
}
|
|
|
|
Documentation: docs/frontend/shared/platform-settings.md
|
|
pattern:
|
|
file_pattern: "static/admin/js/**/*.js"
|
|
required_in_pages_with_pagination:
|
|
- "PlatformSettings\\.getRowsPerPage"
|
|
- "window\\.PlatformSettings"
|
|
exceptions:
|
|
- "init-alpine.js"
|
|
- "init-api-client.js"
|
|
- "settings.js"
|
|
|
|
- id: "JS-011"
|
|
name: "Use standard pagination object structure"
|
|
severity: "error"
|
|
description: |
|
|
All pages with tables MUST use the standard nested pagination object
|
|
structure. This ensures compatibility with the pagination macro and
|
|
consistent behavior across all pages.
|
|
|
|
REQUIRED structure:
|
|
pagination: {
|
|
page: 1,
|
|
per_page: 20,
|
|
total: 0,
|
|
pages: 0
|
|
}
|
|
|
|
WRONG (flat structure):
|
|
page: 1,
|
|
limit: 20,
|
|
total: 0,
|
|
skip: 0
|
|
|
|
WRONG (different property names):
|
|
pagination: {
|
|
currentPage: 1,
|
|
itemsPerPage: 20
|
|
}
|
|
|
|
Required computed properties:
|
|
- totalPages
|
|
- startIndex
|
|
- endIndex
|
|
- pageNumbers
|
|
|
|
Required methods:
|
|
- previousPage()
|
|
- nextPage()
|
|
- goToPage(pageNum)
|
|
|
|
Documentation: docs/frontend/shared/pagination.md
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
required_in_pages_with_pagination:
|
|
- "pagination:"
|
|
- "pagination\\.page"
|
|
- "pagination\\.per_page"
|
|
anti_patterns_in_pagination_pages:
|
|
- "^\\s*page:\\s*\\d"
|
|
- "^\\s*limit:\\s*\\d"
|
|
- "^\\s*skip:\\s*"
|
|
exceptions:
|
|
- "init-alpine.js"
|
|
|
|
- id: "JS-012"
|
|
name: "Do not include /api/v1 prefix in API endpoints"
|
|
severity: "error"
|
|
description: |
|
|
When using apiClient.get(), apiClient.post(), etc., do NOT include
|
|
the /api/v1 prefix in the endpoint path. The apiClient automatically
|
|
prepends this prefix.
|
|
|
|
CORRECT:
|
|
apiClient.get('/admin/vendors')
|
|
apiClient.post('/admin/products')
|
|
const url = '/admin/vendors'
|
|
|
|
WRONG (causes double prefix /api/v1/api/v1/...):
|
|
apiClient.get('/api/v1/admin/vendors')
|
|
const url = '/api/v1/admin/vendors'
|
|
const endpoint = '/api/v1/admin/products'
|
|
|
|
Exception: Direct fetch() calls without apiClient should use full path.
|
|
|
|
Documentation: docs/frontend/shared/api-client.md
|
|
pattern:
|
|
file_pattern: "static/**/js/**/*.js"
|
|
anti_patterns:
|
|
- "apiClient\\.(get|post|put|delete|patch)\\s*\\(\\s*['\"`]/api/v1"
|
|
- "(const|let|var)\\s+(url|endpoint|apiEndpoint|apiUrl|path)\\s*=\\s*['\"`]/api/v1"
|
|
- "\\$\\{.*\\}/api/v1"
|
|
exceptions:
|
|
- "init-api-client.js"
|
|
- "api-client.js"
|
|
|
|
# ============================================================================
|
|
# TEMPLATE RULES (Jinja2)
|
|
# ============================================================================
|
|
|
|
template_rules:
|
|
|
|
- id: "TPL-001"
|
|
name: "Admin templates must extend admin/base.html"
|
|
severity: "error"
|
|
description: |
|
|
All admin templates must extend the base template for consistency.
|
|
|
|
Auto-excluded files:
|
|
- login.html - Standalone login page (no sidebar/navigation)
|
|
- errors/*.html - Error pages extend errors/base.html instead
|
|
- test-*.html - Test/development templates
|
|
|
|
Standalone template markers (place in first 5 lines):
|
|
- {# standalone #} - Mark template as intentionally standalone
|
|
- {# noqa: TPL-001 #} - Standard noqa style to suppress error
|
|
- <!-- standalone --> - HTML comment style
|
|
pattern:
|
|
file_pattern: "app/templates/admin/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]admin/base\\.html['\"] %}"
|
|
auto_exclude_files:
|
|
- "login.html"
|
|
- "errors/"
|
|
- "test-"
|
|
standalone_markers:
|
|
- "{# standalone #}"
|
|
- "{# noqa: tpl-001 #}"
|
|
- "<!-- standalone -->"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-002"
|
|
name: "Vendor templates must extend vendor/base.html"
|
|
severity: "error"
|
|
description: "All vendor templates must extend the base template"
|
|
pattern:
|
|
file_pattern: "app/templates/vendor/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]vendor/base\\.html['\"] %}"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-003"
|
|
name: "Shop templates must extend shop/base.html"
|
|
severity: "error"
|
|
description: "All shop templates must extend the base template"
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
required_patterns:
|
|
- "{% extends ['\"]shop/base\\.html['\"] %}"
|
|
exceptions:
|
|
- "base.html"
|
|
- "partials/"
|
|
|
|
- id: "TPL-004"
|
|
name: "Use x-text for dynamic text content (prevents XSS)"
|
|
severity: "warning"
|
|
description: |
|
|
Use x-text directive for dynamic content to prevent XSS vulnerabilities
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<p x-text="item.name"></p>'
|
|
|
|
- id: "TPL-005"
|
|
name: "Use x-html ONLY for safe content"
|
|
severity: "error"
|
|
description: |
|
|
Use x-html only for trusted content like icons, never for user-generated content
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
safe_usage:
|
|
- 'x-html="\\$icon\\('
|
|
|
|
- id: "TPL-006"
|
|
name: "Implement loading state for data loads"
|
|
severity: "warning"
|
|
description: |
|
|
All templates that load data should show loading state
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<div x-show="loading">Loading...</div>'
|
|
|
|
- id: "TPL-007"
|
|
name: "Implement empty state when no data"
|
|
severity: "warning"
|
|
description: |
|
|
Show empty state when lists have no items
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: '<template x-if="items.length === 0">No items</template>'
|
|
|
|
- id: "TPL-008"
|
|
name: "Use table_header_custom for custom headers, not table_header"
|
|
severity: "error"
|
|
description: |
|
|
When using {% call %} to create custom table headers with th_sortable
|
|
or custom <th> elements, you MUST use table_header_custom(), not table_header().
|
|
|
|
The table_header() macro takes a columns list and does NOT support caller().
|
|
Using {% call table_header() %} causes a Jinja2 error:
|
|
"macro 'table_header' was invoked with two values for the special caller argument"
|
|
|
|
WRONG (causes 500 error):
|
|
{% call table_header() %}
|
|
{{ th_sortable('name', 'Name', 'sortBy', 'sortOrder') }}
|
|
<th class="px-4 py-3">Actions</th>
|
|
{% endcall %}
|
|
|
|
RIGHT (supports caller):
|
|
{% call table_header_custom() %}
|
|
{{ th_sortable('name', 'Name', 'sortBy', 'sortOrder') }}
|
|
<th class="px-4 py-3">Actions</th>
|
|
{% endcall %}
|
|
|
|
OR for simple headers (list of column names):
|
|
{{ table_header(['Name', 'Email', 'Status', 'Actions']) }}
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "{%\\s*call\\s+table_header\\s*\\(\\s*\\)\\s*%}"
|
|
required_alternative: "table_header_custom"
|
|
|
|
- id: "TPL-009"
|
|
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"
|
|
|
|
- id: "TPL-013"
|
|
name: "Use new pagination macro API"
|
|
severity: "error"
|
|
description: |
|
|
The pagination macro was simplified to only accept show_condition.
|
|
It now relies on standardized Alpine.js component properties.
|
|
|
|
OLD (deprecated - will break):
|
|
{{ pagination(
|
|
current_page='pagination.page',
|
|
total_pages='totalPages',
|
|
...
|
|
) }}
|
|
|
|
NEW (correct):
|
|
{{ pagination(show_condition="!loading && pagination.total > 0") }}
|
|
|
|
Required Alpine.js component properties:
|
|
- pagination.page, pagination.total
|
|
- totalPages, pageNumbers
|
|
- startIndex, endIndex
|
|
- previousPage(), nextPage(), goToPage()
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "pagination\\s*\\([^)]*current_page\\s*="
|
|
- "pagination\\s*\\([^)]*total_pages\\s*="
|
|
- "pagination\\s*\\([^)]*page_numbers\\s*="
|
|
exceptions:
|
|
- "shared/macros/pagination.html"
|
|
|
|
- id: "TPL-015"
|
|
name: "Use correct page_header macro API"
|
|
severity: "error"
|
|
description: |
|
|
The page_header macro does not accept a buttons=[] parameter.
|
|
Use action_label/action_onclick for a single action button,
|
|
or use page_header_flex with {% call %} for multiple buttons.
|
|
|
|
OLD (deprecated - will break):
|
|
{{ page_header('Title', buttons=[
|
|
{'text': 'Add', 'icon': 'plus', 'click': 'doSomething()'}
|
|
]) }}
|
|
|
|
NEW (single action button):
|
|
{{ page_header('Title', action_label='Add', action_icon='plus', action_onclick='doSomething()') }}
|
|
|
|
NEW (multiple buttons - use page_header_flex):
|
|
{% call page_header_flex(title='Title') %}
|
|
<button @click="action1()">Button 1</button>
|
|
<button @click="action2()">Button 2</button>
|
|
{% endcall %}
|
|
|
|
Parameters for page_header:
|
|
- title, subtitle
|
|
- action_label, action_url, action_icon, action_onclick
|
|
- back_url, back_label
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "page_header\\s*\\([^)]*buttons\\s*="
|
|
exceptions:
|
|
- "shared/macros/headers.html"
|
|
|
|
- id: "TPL-014"
|
|
name: "Use new modal_simple macro API with call block"
|
|
severity: "error"
|
|
description: |
|
|
The modal_simple macro now uses {% call %}...{% endcall %} syntax.
|
|
Content (including buttons) goes inside the call block.
|
|
|
|
OLD (deprecated - will break):
|
|
{{ modal_simple(
|
|
show_var='showModal',
|
|
title='Title',
|
|
icon='exclamation',
|
|
confirm_text='OK',
|
|
confirm_fn='doSomething()'
|
|
) }}
|
|
<template x-if="showModal">...</template>
|
|
|
|
NEW (correct):
|
|
{% call modal_simple('modalId', 'Title', show_var='showModal') %}
|
|
<div class="space-y-4">
|
|
<p>Modal content here</p>
|
|
<div class="flex justify-end gap-3">
|
|
<button @click="showModal = false">Cancel</button>
|
|
<button @click="doSomething()">OK</button>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
|
|
Parameters: modal_simple(id, title, show_var='isModalOpen', size='md')
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "\\{\\{\\s*modal_simple\\s*\\("
|
|
- "modal_simple\\s*\\([^)]*icon\\s*="
|
|
- "modal_simple\\s*\\([^)]*confirm_text\\s*="
|
|
- "modal_simple\\s*\\([^)]*confirm_fn\\s*="
|
|
exceptions:
|
|
- "shared/macros/modals.html"
|
|
|
|
# ============================================================================
|
|
# FRONTEND COMPONENT RULES
|
|
# ============================================================================
|
|
|
|
frontend_component_rules:
|
|
|
|
- id: "FE-001"
|
|
name: "Use pagination macro instead of inline HTML"
|
|
severity: "warning"
|
|
description: |
|
|
Use the shared pagination macro instead of duplicating pagination HTML.
|
|
Import from shared/macros/pagination.html.
|
|
|
|
RIGHT (use macro):
|
|
{% from 'shared/macros/pagination.html' import pagination %}
|
|
{{ pagination() }}
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- 'aria-label="Table navigation"'
|
|
- "previousPage\\(\\).*nextPage\\(\\)"
|
|
exceptions:
|
|
- "shared/macros/pagination.html"
|
|
- "components.html"
|
|
|
|
- id: "FE-002"
|
|
name: "Use $icon() helper instead of inline SVGs"
|
|
severity: "warning"
|
|
description: |
|
|
Use the Alpine.js $icon() helper for consistent iconography.
|
|
Do not use inline <svg> elements.
|
|
|
|
RIGHT (icon helper):
|
|
<span x-html="$icon('arrow-left', 'w-4 h-4')"></span>
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- "<svg.*viewBox.*>.*</svg>"
|
|
exceptions:
|
|
- "base.html"
|
|
- "components.html"
|
|
- "shared/macros/"
|
|
|
|
- id: "FE-003"
|
|
name: "Use table macros for consistent table styling"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared table macros for consistent table styling.
|
|
Import from shared/macros/tables.html.
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/tables.html' import"
|
|
|
|
- id: "FE-004"
|
|
name: "Use form macros for consistent form styling"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared form macros for consistent input styling and validation.
|
|
Import from shared/macros/forms.html.
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/forms.html' import"
|
|
|
|
- id: "FE-008"
|
|
name: "Use number_stepper macro for quantity inputs"
|
|
severity: "warning"
|
|
description: |
|
|
Use the shared number_stepper macro instead of raw <input type="number">.
|
|
This ensures consistent styling, proper dark mode support, and hides
|
|
native browser spinners that render inconsistently.
|
|
|
|
RIGHT (use macro):
|
|
{% from 'shared/macros/inputs.html' import number_stepper %}
|
|
{{ number_stepper(model='quantity', min=1, max=99) }}
|
|
|
|
Suppress with:
|
|
- {# noqa: FE-008 #} on the line or at file level
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
anti_patterns:
|
|
- 'type="number"'
|
|
- "type='number'"
|
|
exceptions:
|
|
- "shared/macros/inputs.html"
|
|
- "components.html"
|
|
|
|
- id: "FE-009"
|
|
name: "Use product_card macro for product displays"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared product_card macro for consistent product presentation.
|
|
Import from shared/macros/shop/product-card.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/product-card.html' import"
|
|
|
|
- id: "FE-010"
|
|
name: "Use product_grid macro for product listings"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared product_grid macro for responsive product grids.
|
|
Import from shared/macros/shop/product-grid.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/product-grid.html' import"
|
|
|
|
- id: "FE-011"
|
|
name: "Use add_to_cart macros for cart interactions"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared add-to-cart macros for consistent cart functionality.
|
|
Import from shared/macros/shop/add-to-cart.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/add-to-cart.html' import"
|
|
|
|
- id: "FE-012"
|
|
name: "Use mini_cart macro for cart dropdown"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared mini_cart macros for header cart functionality.
|
|
Import from shared/macros/shop/mini-cart.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/mini-cart.html' import"
|
|
|
|
- id: "FE-013"
|
|
name: "Use product_gallery macro for image galleries"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared product_gallery macros for product image displays.
|
|
Import from shared/macros/shop/product-gallery.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/product-gallery.html' import"
|
|
|
|
- id: "FE-014"
|
|
name: "Use variant_selector macros for product options"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared variant_selector macros for product variant selection.
|
|
Import from shared/macros/shop/variant-selector.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/variant-selector.html' import"
|
|
|
|
- id: "FE-015"
|
|
name: "Use product_info macros for product details"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared product_info macros for product detail sections.
|
|
Import from shared/macros/shop/product-info.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/product-info.html' import"
|
|
|
|
- id: "FE-016"
|
|
name: "Use product_tabs macro for product content tabs"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared product_tabs macros for tabbed product information.
|
|
Import from shared/macros/shop/product-tabs.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/product-tabs.html' import"
|
|
|
|
- id: "FE-017"
|
|
name: "Use category_nav macros for category navigation"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared category_nav macros for category navigation sidebars and menus.
|
|
Import from shared/macros/shop/category-nav.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/category-nav.html' import"
|
|
|
|
- id: "FE-018"
|
|
name: "Use breadcrumbs macros for breadcrumb navigation"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared breadcrumbs macros for navigation trails.
|
|
Import from shared/macros/shop/breadcrumbs.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/breadcrumbs.html' import"
|
|
|
|
- id: "FE-019"
|
|
name: "Use search_bar macros for product search"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared search_bar macros for product search functionality.
|
|
Import from shared/macros/shop/search-bar.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/search-bar.html' import"
|
|
|
|
- id: "FE-020"
|
|
name: "Use filter_sidebar macros for product filtering"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared filter_sidebar macros for product filtering panels.
|
|
Import from shared/macros/shop/filter-sidebar.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/filter-sidebar.html' import"
|
|
|
|
- id: "FE-021"
|
|
name: "Use star_rating macros for rating displays"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared star_rating macros for all rating displays and inputs.
|
|
Import from shared/macros/shop/star-rating.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/star-rating.html' import"
|
|
|
|
- id: "FE-022"
|
|
name: "Use review macros for review displays"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared review macros for product reviews.
|
|
Import from shared/macros/shop/reviews.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/reviews.html' import"
|
|
|
|
- id: "FE-023"
|
|
name: "Use trust_badges macros for trust signals"
|
|
severity: "info"
|
|
description: |
|
|
Use the shared trust_badges macros for security and trust indicators.
|
|
Import from shared/macros/shop/trust-badges.html.
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
encouraged_patterns:
|
|
- "{% from 'shared/macros/shop/trust-badges.html' import"
|
|
|
|
# ============================================================================
|
|
# FRONTEND STYLING RULES
|
|
# ============================================================================
|
|
|
|
styling_rules:
|
|
|
|
- id: "CSS-001"
|
|
name: "Use Tailwind utility classes"
|
|
severity: "warning"
|
|
description: |
|
|
Prefer Tailwind utility classes over custom CSS
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
encouraged: true
|
|
|
|
- id: "CSS-002"
|
|
name: "Support dark mode with dark: prefix"
|
|
severity: "warning"
|
|
description: |
|
|
All color classes should include dark mode variants
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: 'class="bg-white dark:bg-gray-800"'
|
|
|
|
- id: "CSS-003"
|
|
name: "Shop templates use vendor theme CSS variables"
|
|
severity: "error"
|
|
description: |
|
|
Shop templates must use CSS variables for vendor-specific theming
|
|
pattern:
|
|
file_pattern: "app/templates/shop/**/*.html"
|
|
required_pattern: 'var\\(--color-primary\\)'
|
|
|
|
- id: "CSS-004"
|
|
name: "Mobile-first responsive design"
|
|
severity: "warning"
|
|
description: |
|
|
Use mobile-first responsive classes
|
|
pattern:
|
|
file_pattern: "app/templates/**/*.html"
|
|
recommended_pattern: 'class="grid-cols-1 md:grid-cols-2 lg:grid-cols-4"'
|