Replace all ~1,086 occurrences of Wizamart/wizamart/WIZAMART/WizaMart with Orion/orion/ORION across 184 files. This includes database identifiers, email addresses, domain references, R2 bucket names, DNS prefixes, encryption salt, Celery app name, config defaults, Docker configs, CI configs, documentation, seed data, and templates. Renames homepage-wizamart.html template to homepage-orion.html. Fixes duplicate file_pattern key in api.yaml architecture rule. Co-Authored-By: Claude Opus 4.6 <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/orion/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"'
|