Files
orion/.architecture-rules/frontend.yaml
Samir Boulahtit e5dbd7ef1a
Some checks failed
CI / ruff (push) Successful in 13s
CI / pytest (push) Successful in 36m14s
CI / validate (push) Failing after 21s
CI / dependency-scanning (push) Successful in 31s
CI / docs (push) Has been skipped
CI / deploy (push) Has been skipped
feat: add JS-015 architecture rule to enforce confirm_modal over native confirm()
Prevents reintroduction of native browser confirm() dialogs by flagging
them as architecture errors during pre-commit validation. Points
developers to use confirm_modal/confirm_modal_dynamic Jinja2 macros.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 16:59:19 +01:00

980 lines
33 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-015"
name: "Use confirm_modal macros, not native confirm()"
severity: "error"
description: |
All confirmation dialogs must use the project's confirm_modal or
confirm_modal_dynamic Jinja2 macros from shared/macros/modals.html.
Never use the native browser confirm() dialog.
The modal macros provide:
- Consistent styled dialogs matching the admin/store theme
- Dark mode support
- Variant colors (danger=red, warning=yellow, info=blue)
- Icon support
- Double-confirm pattern for destructive operations
WRONG (native browser dialog):
if (!confirm('Are you sure you want to delete this?')) return;
if (!confirm(I18n.t('confirmations.delete'))) return;
RIGHT (state variable + modal macro):
// In JS: add state variable and remove confirm() guard
showDeleteModal: false,
async deleteItem() {
// No confirm() guard — modal already confirmed
await apiClient.delete('/admin/items/' + this.item.id);
}
// In template: button sets state, macro shows modal
<button @click="showDeleteModal = true">Delete</button>
{{ confirm_modal('deleteModal', 'Delete Item', 'Are you sure?',
'deleteItem()', 'showDeleteModal', 'Delete', 'Cancel', 'danger') }}
For dynamic messages (containing JS expressions):
{{ confirm_modal_dynamic('deleteModal', 'Delete Item',
"'Delete ' + item.name + '?'",
'deleteItem()', 'showDeleteModal', 'Delete', 'Cancel', 'danger') }}
pattern:
file_pattern: "static/**/js/**/*.js"
anti_patterns:
- "confirm\\("
exceptions:
- "utils.js"
- "vendor/"
- 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"'