Files
orion/.architecture-rules.yaml
Samir Boulahtit 2b899d5a52 feat: add JS-009 rule for Utils.showToast() and update naming docs
Architecture rules:
- Add JS-009: Use Utils.showToast() instead of alert() or window.showToast
- Supports inline noqa comments to suppress warnings

Documentation:
- Update naming-conventions.md to emphasize plural table names (industry standard)
- Document that plural table names follow Rails/Django/Laravel conventions

Schema:
- Add from_attributes to VendorUserResponse for ORM compatibility

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 22:37:28 +01:00

1488 lines
52 KiB
YAML

# Architecture Rules Configuration
# This file defines the key architectural decisions and patterns that must be followed
# across the application. The validator script uses these rules to check compliance.
version: "2.0"
project: "letzshop-product-import"
description: "Comprehensive architectural rules for multi-tenant e-commerce platform"
# ============================================================================
# CORE ARCHITECTURAL PRINCIPLES
# ============================================================================
principles:
- name: "Separation of Concerns"
description: "API endpoints should only handle HTTP concerns. Business logic belongs in services."
- name: "Layered Architecture"
description: "Routes → Services → Models. Each layer has specific responsibilities."
- name: "Type Safety"
description: "Use Pydantic models for request/response validation. Use SQLAlchemy models for database."
- name: "Proper Exception Handling"
description: "Services throw domain exceptions. Global handler converts to HTTP responses. Endpoints do NOT catch or raise HTTPException."
- name: "Multi-Tenancy"
description: "All queries must be scoped to vendor_id. No cross-vendor data access."
- name: "Consistent Naming"
description: "API files: plural, Services: singular+service, Models: singular"
# ============================================================================
# API ENDPOINT RULES (app/api/v1/**/*.py)
# ============================================================================
api_endpoint_rules:
- id: "API-001"
name: "Endpoint must use Pydantic models for request/response"
severity: "error"
description: |
All API endpoints must use Pydantic models (BaseModel) for request bodies
and response models. Never use raw dicts or SQLAlchemy models directly.
WHY THIS MATTERS:
- Type safety: Pydantic validates response structure at runtime
- Documentation: OpenAPI/Swagger auto-generates accurate docs
- Contract stability: Schema changes are explicit and reviewable
- IDE support: Consumers get autocomplete and type hints
- Prevents bugs: Field name mismatches caught immediately
COMMON VIOLATION: Service returns dict, frontend expects different field names.
Example: Service returns {"total_imports": 5} but frontend expects {"total": 5}.
With response_model, this mismatch is caught immediately.
SCHEMA LOCATION: All response schemas must be defined in models/schema/*.py,
never inline in endpoint files. This ensures schemas are reusable and discoverable.
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "return dict"
- "-> dict"
- "return db_object"
- "return {" # Returning inline dict literal
example_good: |
# In models/schema/stats.py
class ImportStatsResponse(BaseModel):
total: int
pending: int
completed: int
failed: int
# In app/api/v1/admin/marketplace.py
@router.get("/stats", response_model=ImportStatsResponse)
def get_import_statistics(db: Session = Depends(get_db)):
return stats_service.get_import_statistics(db)
example_bad: |
# ❌ WRONG: No response_model, returns raw dict
@router.get("/stats")
def get_import_statistics(db: Session = Depends(get_db)):
return stats_service.get_import_statistics(db) # Returns dict
# ❌ WRONG: Schema defined inline in endpoint file
class MyResponse(BaseModel): # Should be in models/schema/
...
@router.get("/data", response_model=MyResponse)
def get_data(): ...
- id: "API-002"
name: "Endpoint must NOT contain business logic"
severity: "error"
description: |
API endpoints should only handle HTTP concerns (validation, auth, response formatting).
All business logic must be delegated to service layer.
Transaction control (db.commit) IS allowed at endpoint level - this is the recommended
pattern for request-scoped transactions. One request = one transaction.
What's NOT allowed:
- db.add() - creating entities is business logic
- db.query() - complex queries are business logic
- db.delete() - deleting entities is business logic
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "db.add("
- "db.delete("
- "db.query("
# NOTE: db.commit() is intentionally NOT listed - it's allowed for transaction control
- id: "API-003"
name: "Endpoint must NOT raise ANY exceptions directly"
severity: "error"
description: |
API endpoints should NOT raise exceptions directly. Endpoints are a thin
orchestration layer that:
1. Accepts request parameters (validated by Pydantic)
2. Calls dependencies for authentication/authorization (deps.py raises exceptions)
3. Calls services for business logic (services raise domain exceptions)
4. Returns response (formatted by Pydantic)
Exception raising belongs in:
- Dependencies (app/api/deps.py) - authentication/authorization validation
- Services (app/services/) - business logic validation
The global exception handler catches all WizamartException subclasses and
converts them to appropriate HTTP responses.
WRONG (endpoint raises exception):
@router.get("/orders")
def get_orders(current_user: User = Depends(get_current_vendor_api)):
if not hasattr(current_user, "token_vendor_id"): # ❌ Redundant check
raise InvalidTokenException("...") # ❌ Don't raise here
return order_service.get_orders(db, current_user.token_vendor_id)
RIGHT (dependency guarantees, endpoint trusts):
@router.get("/orders")
def get_orders(current_user: User = Depends(get_current_vendor_api)):
# Dependency guarantees token_vendor_id is present
return order_service.get_orders(db, current_user.token_vendor_id)
pattern:
file_pattern: "app/api/v1/**/*.py"
anti_patterns:
- "raise HTTPException"
- "raise InvalidTokenException"
- "raise InsufficientPermissionsException"
- "if not hasattr\\(current_user.*token_vendor"
exceptions:
- "app/exceptions/handler.py" # Handler is allowed to use HTTPException
- id: "API-004"
name: "Endpoint must have proper authentication/authorization"
severity: "warning"
description: |
Protected endpoints must use Depends() for authentication.
Use get_current_user, get_current_admin, etc.
Auto-excluded files:
- */auth.py - Authentication endpoints (login, logout, register) are intentionally public
Public endpoint markers (place on line before or after decorator):
- # public - Descriptive marker for intentionally unauthenticated endpoints
- # noqa: API-004 - Standard noqa style to suppress warning
Example:
# public - Stripe webhook receives external callbacks
@router.post("/webhook/stripe")
def stripe_webhook(request: Request):
...
pattern:
file_pattern: "app/api/v1/**/*.py"
required_if_not_public:
- "Depends(get_current_"
auto_exclude_files:
- "*/auth.py"
public_markers:
- "# public"
- "# noqa: api-004"
- id: "API-005"
name: "Multi-tenant endpoints must scope queries to vendor_id"
severity: "error"
description: |
All queries in vendor/shop contexts must filter by vendor_id.
Use request.state.vendor_id from middleware.
pattern:
file_pattern: "app/api/v1/vendor/**/*.py"
file_pattern: "app/api/v1/shop/**/*.py"
discouraged_patterns:
- "db.query(.*).all()" # Without vendor filter
# ============================================================================
# SERVICE LAYER RULES (app/services/**/*.py)
# ============================================================================
service_layer_rules:
- id: "SVC-001"
name: "Service must NOT raise HTTPException"
severity: "error"
description: |
Services are business logic layer - they should NOT know about HTTP.
Raise domain-specific exceptions instead (ValueError, custom exceptions).
pattern:
file_pattern: "app/services/**/*.py"
anti_patterns:
- "raise HTTPException"
- "from fastapi import HTTPException"
- id: "SVC-002"
name: "Service must use proper exception handling"
severity: "error"
description: |
Services should raise meaningful domain exceptions, not generic Exception.
Create custom exception classes for business rule violations.
pattern:
file_pattern: "app/services/**/*.py"
discouraged_patterns:
- "raise Exception\\("
- id: "SVC-003"
name: "Service methods must accept db session as parameter"
severity: "error"
description: |
Service methods should receive database session as a parameter for testability
and transaction control. Never create session inside service.
pattern:
file_pattern: "app/services/**/*.py"
required_in_method_signature:
- "db: Session"
anti_patterns:
- "SessionLocal()"
- "get_db()"
- id: "SVC-004"
name: "Service must use Pydantic models for input validation"
severity: "warning"
description: |
Service methods should accept Pydantic models for complex inputs
to ensure type safety and validation.
pattern:
file_pattern: "app/services/**/*.py"
encouraged_patterns:
- "BaseModel"
- id: "SVC-005"
name: "Service must scope queries to vendor_id in multi-tenant contexts"
severity: "error"
description: |
All database queries must be scoped to vendor_id to prevent cross-tenant data access.
pattern:
file_pattern: "app/services/**/*.py"
check: "vendor_scoping"
- id: "SVC-006"
name: "Service must NOT call db.commit()"
severity: "warning"
description: |
Services should NOT commit transactions. Transaction control belongs at the
API endpoint level where one request = one transaction.
This allows:
- Composing multiple service calls in a single transaction
- Clean rollback on any failure
- Easier testing of services in isolation
The endpoint should call db.commit() after all service operations succeed.
pattern:
file_pattern: "app/services/**/*.py"
anti_patterns:
- "db.commit()"
exceptions:
- "log_service.py" # Audit logs may need immediate commits
- id: "SVC-007"
name: "Service return types must match API response schemas"
severity: "error"
description: |
When a service method's return value will be used as an API response,
the returned dict keys MUST match the corresponding Pydantic schema fields.
This prevents the common bug where:
- Service returns {"total_imports": 5, "completed_imports": 3}
- Schema expects {"total": 5, "completed": 3}
- Frontend receives wrong/empty values
RECOMMENDED PATTERNS:
1. Return Pydantic model directly from service:
def get_stats(self, db: Session) -> StatsResponse:
return StatsResponse(total=count, completed=done)
2. Return dict with schema-matching keys:
def get_stats(self, db: Session) -> dict:
return {"total": count, "completed": done} # Matches StatsResponse
3. Document the expected schema in service docstring:
def get_stats(self, db: Session) -> dict:
\"\"\"
Returns dict compatible with StatsResponse schema.
Keys: total, pending, completed, failed
\"\"\"
TESTING: Write tests that validate service output against schema:
result = service.get_stats(db)
StatsResponse(**result) # Raises if keys don't match
pattern:
file_pattern: "app/services/**/*.py"
check: "schema_compatibility"
# ============================================================================
# MODEL RULES (models/database/*.py, models/schema/*.py)
# ============================================================================
model_rules:
- id: "MDL-001"
name: "Database models must use SQLAlchemy Base"
severity: "error"
description: |
All database models must inherit from SQLAlchemy Base and use proper
column definitions with types and constraints.
pattern:
file_pattern: "models/database/**/*.py"
required_patterns:
- "class.*\\(Base\\):"
- id: "MDL-002"
name: "Use Pydantic models separately from SQLAlchemy models"
severity: "error"
description: |
Never mix SQLAlchemy and Pydantic in the same model.
SQLAlchemy = database schema, Pydantic = API validation/serialization.
pattern:
file_pattern: "models/**/*.py"
anti_patterns:
- "class.*\\(Base, BaseModel\\):"
- id: "MDL-003"
name: "Pydantic models must use from_attributes for ORM mode"
severity: "error"
description: |
Pydantic response models must enable from_attributes to work with SQLAlchemy models.
pattern:
file_pattern: "models/schema/**/*.py"
required_in_response_models:
- "from_attributes = True"
- id: "MDL-004"
name: "Database tables use plural names"
severity: "warning"
description: |
Database table names should be plural lowercase following industry standard
conventions (Rails, Django, Laravel, most ORMs). A table represents a
collection of entities, so plural names are natural: 'users', 'orders',
'products'. This reads naturally in SQL: SELECT * FROM users WHERE id = 1.
Examples:
- Good: users, vendors, products, orders, order_items, cart_items
- Bad: user, vendor, product, order
Junction/join tables use both entity names in plural:
- Good: vendor_users, order_items, product_translations
pattern:
file_pattern: "models/database/**/*.py"
check: "table_naming_plural"
# ============================================================================
# EXCEPTION HANDLING RULES
# ============================================================================
exception_rules:
- id: "EXC-001"
name: "Define custom exceptions in exceptions module"
severity: "warning"
description: |
Create domain-specific exceptions in app/exceptions/ for better
error handling and clarity.
pattern:
file_pattern: "app/exceptions/**/*.py"
encouraged_structure: |
class VendorError(Exception):
"""Base exception for vendor-related errors"""
pass
- id: "EXC-002"
name: "Never use bare except"
severity: "error"
description: |
Always specify exception types. Bare except catches everything including
KeyboardInterrupt and SystemExit.
pattern:
file_pattern: "**/*.py"
anti_patterns:
- "except:"
- "except\\s*:"
- id: "EXC-003"
name: "Log all exceptions with context"
severity: "warning"
description: |
When catching exceptions, log them with context and stack trace.
pattern:
file_pattern: "app/services/**/*.py"
encouraged_patterns:
- "logger.error"
- "exc_info=True"
- id: "EXC-004"
name: "Domain exceptions must inherit from WizamartException"
severity: "error"
description: |
All custom domain exceptions must inherit from WizamartException (or its
subclasses like ResourceNotFoundException, ValidationException, etc.).
This ensures the global exception handler catches and converts them properly.
pattern:
file_pattern: "app/exceptions/**/*.py"
required_base_class: "WizamartException"
example_good: |
class VendorNotFoundException(ResourceNotFoundException):
def __init__(self, vendor_code: str):
super().__init__(resource_type="Vendor", identifier=vendor_code)
- id: "EXC-005"
name: "Exception handler must be registered"
severity: "error"
description: |
The global exception handler must be set up in app initialization to
catch WizamartException and convert to HTTP responses.
pattern:
file_pattern: "app/main.py"
required_patterns:
- "setup_exception_handlers"
# ============================================================================
# NAMING CONVENTION RULES
# ============================================================================
naming_rules:
- id: "NAM-001"
name: "API files use PLURAL names"
severity: "error"
description: |
API endpoint files should use plural names (vendors.py, products.py)
pattern:
file_pattern: "app/api/v1/**/*.py"
check: "plural_naming"
exceptions:
- "__init__.py"
- "auth.py"
- "health.py"
- id: "NAM-002"
name: "Service files use SINGULAR + 'service' suffix"
severity: "error"
description: |
Service files should use singular name + _service (vendor_service.py)
pattern:
file_pattern: "app/services/**/*.py"
check: "service_naming"
- id: "NAM-003"
name: "Model files use SINGULAR names"
severity: "error"
description: |
Both database and schema model files use singular names (product.py)
pattern:
file_pattern: "models/**/*.py"
check: "singular_naming"
- id: "NAM-004"
name: "Use consistent terminology: vendor not shop"
severity: "warning"
description: |
Use 'vendor' consistently, not 'shop' (except for shop frontend)
pattern:
file_pattern: "app/**/*.py"
discouraged_terms:
- "shop_id" # Use vendor_id
- "shop_service" # Use vendor_service
- id: "NAM-005"
name: "Use consistent terminology: inventory not stock"
severity: "warning"
description: |
Use 'inventory' consistently, not 'stock'
pattern:
file_pattern: "app/**/*.py"
discouraged_terms:
- "stock_service" # Use inventory_service
# ============================================================================
# JAVASCRIPT ARCHITECTURE RULES (Frontend)
# ============================================================================
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('✅" # Bootstrap messages allowed
auto_exclude_files:
- "init-*.js" # Init files run before logger is available
- 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;
- 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-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" # The apiClient implementation itself
- 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');
alert(errorMessage);
WRONG (undefined function):
window.showToast('Success', 'success');
if (window.showToast) { window.showToast(...); } else { alert(...); }
RIGHT (Utils helper):
Utils.showToast('Product saved successfully', 'success');
Utils.showToast('Failed to save product', 'error');
Utils.showToast('Please fill all required fields', 'warning');
pattern:
file_pattern: "static/**/js/**/*.js"
anti_patterns:
- "alert\\("
- "window\\.showToast"
exceptions:
- "utils.js" # The Utils implementation itself
# ============================================================================
# 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>'
# ============================================================================
# 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.
WRONG (inline pagination):
<div class="grid px-4 py-3 text-xs font-semibold...">
<span class="flex items-center col-span-3">
Showing ...
</span>
<nav aria-label="Table navigation">...
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" # Showcase page
- 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.
WRONG (inline SVG):
<svg class="w-4 h-4" viewBox="0 0 20 20">...</svg>
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" # Base templates may define SVG sprites
- "components.html" # Showcase page
- "shared/macros/" # Macros may contain SVGs for compatibility
- 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.
Recommended macros:
- table_wrapper() - Wraps table with overflow and shadow
- table_header(['Column1', 'Column2']) - Consistent header styling
- table_empty_state(colspan, 'icon', 'Title', 'Message') - Empty states
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.
Recommended macros:
- form_input(label, name, x_model, ...) - Text inputs with validation
- form_select(label, x_model, options) - Select dropdowns
- form_textarea(label, x_model, rows) - Textareas
- form_checkbox(label, x_model) - Checkboxes
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.
WRONG (raw number input):
<input type="number" x-model="quantity" min="1" max="99" />
RIGHT (use macro):
{% from 'shared/macros/inputs.html' import number_stepper %}
{{ number_stepper(model='quantity', min=1, max=99) }}
Exceptions (use noqa):
- ID fields where users type specific values (add {# noqa: FE-008 #})
- Fields that are better as regular text inputs
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" # The macro file itself
- "components.html" # Showcase page
- 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.
Features:
- Size variants (sm, md, lg)
- Sale badge, new badge, out-of-stock badge
- Star ratings with review count
- Wishlist toggle button
- Quick add to cart button
Usage:
{% from 'shared/macros/shop/product-card.html' import product_card %}
{{ product_card(product_var='item', size='md') }}
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.
Features:
- Responsive column count (auto or fixed)
- Loading state with skeleton placeholders
- Empty state with customizable icon/message
- Integrated product_card component
Usage:
{% from 'shared/macros/shop/product-grid.html' import product_grid %}
{{ product_grid(products_var='products', loading_var='loading') }}
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.
Available macros:
- add_to_cart_button() - Simple add to cart button
- add_to_cart_form() - Complete form with quantity selector
- buy_now_button() - Direct checkout button
- shop_quantity_selector() - Stock-aware quantity selector
Usage:
{% from 'shared/macros/shop/add-to-cart.html' import add_to_cart_form %}
{{ add_to_cart_form(product_var='product') }}
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.
Available macros:
- mini_cart_icon() - Cart icon with item count badge
- mini_cart_dropdown() - Expandable cart preview
- mini_cart() - Combined icon + dropdown
- cart_item() - Individual cart item display
- cart_summary() - Subtotal, shipping, total
Usage:
{% from 'shared/macros/shop/mini-cart.html' import mini_cart %}
{{ mini_cart(cart_var='cart', show_var='showCart') }}
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.
Available macros:
- product_gallery() - Full gallery with thumbnails, zoom, lightbox
- gallery_thumbnails() - Standalone thumbnail strip
- simple_image_viewer() - Single image with optional lightbox
Features:
- Thumbnail navigation
- Hover zoom on main image
- Fullscreen lightbox with keyboard navigation
- Responsive design
Usage:
{% from 'shared/macros/shop/product-gallery.html' import product_gallery %}
{{ product_gallery(images_var='product.images', enable_zoom=true) }}
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.
Available macros:
- variant_selector() - Generic selector (buttons/dropdown/swatches)
- size_selector() - Specialized for sizes with size guide link
- color_swatches() - Color selection with hex preview
- multi_variant_selector() - Multiple option types (size + color)
Features:
- Out of stock indicators
- Low stock warnings
- Selected state highlighting
Usage:
{% from 'shared/macros/shop/variant-selector.html' import size_selector, color_swatches %}
{{ size_selector(sizes_var='product.sizes') }}
{{ color_swatches(colors_var='product.colors') }}
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.
Available macros:
- product_info() - Complete info block (title, price, rating, stock)
- product_price() - Price display with sale support
- product_rating() - Star rating with review count
- stock_status() - Stock availability indicator
- product_badges() - Sale/New/Bestseller badges
- trust_indicators() - Shipping/returns/secure badges
Usage:
{% from 'shared/macros/shop/product-info.html' import product_info %}
{{ product_info(product_var='product', show_vendor=true) }}
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.
Available macros:
- product_tabs() - Complete tabbed container
- tab_description() - Description content panel
- tab_specifications() - Specs table panel
- tab_reviews() - Reviews list with summary
- tab_shipping() - Shipping & returns panel
- tab_warranty() - Warranty info panel
- review_card() - Individual review display
Usage:
{% from 'shared/macros/shop/product-tabs.html' import product_tabs %}
{{ product_tabs(tabs=['description', 'specifications', 'reviews', 'shipping']) }}
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.
Available macros:
- category_nav() - Sidebar with nested collapsible categories
- category_tree() - Flat list (vertical or horizontal)
- category_menu() - Horizontal dropdown menu
- category_drawer() - Mobile full-screen drawer
Features:
- Product counts per category
- Active state tracking
- Auto-expand parent of active category
- Collapsible nested levels
Usage:
{% from 'shared/macros/shop/category-nav.html' import category_nav, category_tree %}
{{ category_nav(categories_var='categories', current_var='currentCategory') }}
{{ category_tree(categories_var='categories', layout='horizontal') }}
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.
Available macros:
- shop_breadcrumbs() - Standard breadcrumb trail (static or dynamic)
- auto_breadcrumbs() - Auto-generated from category hierarchy
- compact_breadcrumbs() - Mobile-friendly parent + current
Features:
- Home link with icon
- Customizable separator icon
- Support for icons per item
- Responsive design
Usage:
{% from 'shared/macros/shop/breadcrumbs.html' import shop_breadcrumbs %}
{{ shop_breadcrumbs(items=[
{'label': 'Electronics', 'url': '/electronics'},
{'label': 'Headphones'}
]) }}
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.
Available macros:
- search_bar() - Basic search input with optional button
- search_autocomplete() - Search with dropdown suggestions
- mobile_search() - Full-screen mobile search overlay
- search_trigger() - Button to open mobile search
- instant_search_results() - Inline results component
Features:
- Autocomplete suggestions
- Recent searches (localStorage)
- Popular searches
- Size variants (sm, md, lg)
Usage:
{% from 'shared/macros/shop/search-bar.html' import search_bar, search_autocomplete %}
{{ search_bar(placeholder='Search products...') }}
{{ search_autocomplete(search_endpoint='/api/search') }}
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.
Available macros:
- filter_sidebar() - Complete filter panel with all filter types
- price_filter() - Standalone price range filter
- rating_filter() - Standalone star rating filter
- sort_dropdown() - Product sorting dropdown
- mobile_filter_drawer() - Full-screen mobile filter panel
- filter_trigger() - Button to open mobile filters
Features:
- Category & brand checkboxes
- Price range with dual inputs
- Color swatches & size buttons
- Star rating filter
- Collapsible filter groups
- Active filter tags with clear
Usage:
{% from 'shared/macros/shop/filter-sidebar.html' import filter_sidebar, sort_dropdown %}
{{ filter_sidebar(filters_var='filters', active_filters_var='activeFilters') }}
{{ sort_dropdown(value_var='sortBy', on_change='sortProducts()') }}
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.
Available macros:
- star_rating() - Display star ratings (static or dynamic)
- rating_input() - Interactive rating input for reviews
- rating_summary() - Rating distribution with bars
- compact_rating() - Inline compact rating for lists
Features:
- Half-star and exact precision support
- Multiple sizes (xs, sm, md, lg, xl)
- Alpine.js dynamic binding
- Interactive input with hover effects
- Rating distribution visualization
Usage:
{% from 'shared/macros/shop/star-rating.html' import star_rating, rating_input %}
{{ star_rating(rating=4.5, show_count=true, count=127) }}
{{ rating_input(model='userRating', size='lg', allow_half=true) }}
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.
Available macros:
- review_card() - Single review display with actions
- review_list() - Sortable list of reviews
- review_form() - Review submission form
- review_summary_section() - Complete rating summary
Features:
- Verified purchase badges
- Helpful/not helpful voting
- Image attachments
- Sort by newest/oldest/helpful
- Star rating input integration
Usage:
{% from 'shared/macros/shop/reviews.html' import review_card, review_form %}
{{ review_card(review_var='review', on_helpful='markHelpful(review.id)') }}
{{ review_form(rating_model='rating', on_submit='submitReview()') }}
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.
Available macros:
- trust_badges() - Grid or flex layout of trust badges
- trust_banner() - Horizontal trust banner
- payment_icons() - Payment method icons
- guarantee_badge() - Money-back/satisfaction guarantee
- security_seals() - SSL and checkout security seals
- checkout_trust_section() - Complete checkout trust block
Badge types: secure_payment, free_shipping, easy_returns,
support_24_7, money_back, ssl_secured, fast_delivery, quality_guarantee
Usage:
{% from 'shared/macros/shop/trust-badges.html' import trust_badges, payment_icons %}
{{ trust_badges(badges=['secure_payment', 'free_shipping'], layout='grid') }}
{{ payment_icons(methods=['visa', 'mastercard', 'paypal']) }}
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"'
# ============================================================================
# MIDDLEWARE RULES
# ============================================================================
middleware_rules:
- id: "MDW-001"
name: "Middleware files use simple nouns without _middleware suffix"
severity: "warning"
description: |
Middleware files should be named with simple nouns (auth.py, not auth_middleware.py)
pattern:
file_pattern: "middleware/**/*.py"
check: "middleware_naming"
- id: "MDW-002"
name: "Vendor context must be injected for vendor/shop routes"
severity: "error"
description: |
Vendor context middleware must set request.state.vendor_id and request.state.vendor
pattern:
file_pattern: "middleware/vendor_context.py"
required: true
# ============================================================================
# MULTI-TENANCY RULES
# ============================================================================
multi_tenancy_rules:
- id: "MT-001"
name: "All queries must be scoped to vendor_id"
severity: "error"
description: |
In vendor/shop contexts, all database queries must filter by vendor_id
pattern:
file_pattern: "app/services/**/*.py"
context: "vendor_shop"
required_pattern: ".filter\\(.*vendor_id.*\\)"
- id: "MT-002"
name: "No cross-vendor data access"
severity: "error"
description: |
Queries must never access data from other vendors
pattern:
file_pattern: "app/services/**/*.py"
enforcement: "database_query_level"
# ============================================================================
# AUTHENTICATION & AUTHORIZATION RULES
# ============================================================================
auth_rules:
- id: "AUTH-001"
name: "Use JWT tokens in Authorization header"
severity: "error"
description: |
Authentication must use JWT tokens in Authorization: Bearer header
pattern:
file_pattern: "app/api/**/*.py"
enforcement: "middleware"
- id: "AUTH-002"
name: "Role-based access control with Depends"
severity: "error"
description: |
Use Depends(get_current_admin/vendor/customer) for role checks
pattern:
file_pattern: "app/api/v1/**/*.py"
required: "Depends\\(get_current_"
- id: "AUTH-003"
name: "Never store plain passwords"
severity: "error"
description: |
Always hash passwords with bcrypt before storing
pattern:
file_pattern: "app/services/auth_service.py"
required: "bcrypt"
- id: "AUTH-004"
name: "Vendor context pattern - use appropriate dependency for endpoint type"
severity: "error"
description: |
Two vendor context patterns exist - use the appropriate one:
1. SHOP ENDPOINTS (public, no authentication required):
- Use: vendor: Vendor = Depends(require_vendor_context())
- Vendor is detected from URL/subdomain/domain
- File pattern: app/api/v1/shop/**/*.py
- Mark as public with: # public
2. VENDOR API ENDPOINTS (authenticated):
- Use: current_user.token_vendor_id from JWT token
- Or use permission dependencies: require_vendor_permission(), require_vendor_owner
- These dependencies get vendor from token and set request.state.vendor
- File pattern: app/api/v1/vendor/**/*.py
DEPRECATED for vendor APIs:
- require_vendor_context() - only for shop endpoints
- getattr(request.state, "vendor", None) without permission dependency
See: docs/backend/vendor-in-token-architecture.md
pattern:
file_pattern: "app/api/v1/vendor/**/*.py"
anti_patterns:
- "require_vendor_context\\(\\)"
file_pattern: "app/api/v1/shop/**/*.py"
required_patterns:
- "require_vendor_context\\(\\)|# public"
# ============================================================================
# CODE QUALITY RULES
# ============================================================================
code_quality_rules:
- id: "QUAL-001"
name: "All code must be formatted with Ruff"
severity: "error"
description: |
Run 'make format' before committing
enforcement: "pre_commit"
- id: "QUAL-002"
name: "All code must pass Ruff linting"
severity: "error"
description: |
Run 'make lint' before committing
enforcement: "pre_commit"
- id: "QUAL-003"
name: "Type hints recommended for functions"
severity: "warning"
description: |
Add type hints to function parameters and return types
pattern:
file_pattern: "app/**/*.py"
encouraged: true
# ============================================================================
# VALIDATION SEVERITY LEVELS
# ============================================================================
severity_levels:
error:
description: "Critical architectural violation - must be fixed"
exit_code: 1
warning:
description: "Pattern deviation - should be fixed"
exit_code: 0
info:
description: "Suggestion for improvement"
exit_code: 0
# ============================================================================
# IGNORED PATTERNS
# ============================================================================
ignore:
files:
- "**/*_test.py"
- "**/test_*.py"
- "**/__pycache__/**"
- "**/migrations/**"
- "**/alembic/versions/**"
- "**/node_modules/**"
- "**/.venv/**"
- "**/venv/**"
- ".venv/**"
- "venv/**"
- "**/build/**"
- "**/dist/**"
patterns:
# Allow HTTPException in specific files
- file: "app/core/exceptions.py"
pattern: "HTTPException"
reason: "Exception handling utilities"
- file: "app/exceptions/handler.py"
pattern: "HTTPException"
reason: "Exception handler converts to HTTP"
# ============================================================================
# DOCUMENTATION
# ============================================================================
documentation:
architecture: "docs/architecture/overview.md"
backend: "docs/backend/overview.md"
frontend: "docs/frontend/overview.md"
contributing: "docs/development/contributing.md"
code_quality: "docs/development/code-quality.md"