feat(static-assets): cache-bust JS/CSS via ?v=<commit-sha>, immutable in prod
All checks were successful
CI / ruff (push) Successful in 18s
CI / pytest (push) Successful in 2h50m43s
CI / validate (push) Successful in 33s
CI / dependency-scanning (push) Successful in 33s
CI / docs (push) Successful in 50s
CI / deploy (push) Successful in 1m15s

Adds a `static_v(request, name, path=...)` Jinja helper that appends
?v=<commit-sha> from app.core.build_info, plus a CachedStaticFiles
subclass that serves Cache-Control: public, max-age=31536000, immutable
in production and no-cache in development. Browsers refetch JS/CSS
automatically on every deploy without the user having to hard-reload.

- New: app/core/static_files.py (CachedStaticFiles)
- Updated: app/templates_config.py (static_v helper)
- Updated: main.py (use CachedStaticFiles for *_static mounts)
- Codemod: 143 url_for('*_static', path='*.js'|'*.css') → static_v(...)
  across 123 templates. Images/fonts/JSON locales intentionally
  unchanged (out of scope).
- Arch rule: FE-024 (warning) flags raw url_for on JS/CSS to prevent
  drift. Note: FE-008 was already taken by the number_stepper rule.
- docs/proposals/static-asset-cache-busting.md marked Done.

Closes plan from docs/proposals/static-asset-cache-busting.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-18 19:35:59 +02:00
parent 236fee015e
commit 54247ca4f0
129 changed files with 281 additions and 146 deletions

View File

@@ -936,6 +936,33 @@ frontend_component_rules:
encouraged_patterns: encouraged_patterns:
- "{% from 'shared/macros/shop/trust-badges.html' import" - "{% from 'shared/macros/shop/trust-badges.html' import"
- id: "FE-024"
name: "Use static_v() for JS/CSS, not raw url_for()"
severity: "warning"
description: |
Static .js and .css URLs must use the cache-busting `static_v()` helper
so browsers refetch automatically after each deploy. The helper appends
`?v=<commit-sha>` from app.core.build_info — same SHA across one deploy,
flips on the next.
WRONG (browser keeps cached file after deploy):
<script src="{{ url_for('foo_static', path='admin/js/app.js') }}"></script>
<link href="{{ url_for('foo_static', path='admin/css/site.css') }}" rel="stylesheet">
RIGHT:
<script src="{{ static_v(request, 'foo_static', path='admin/js/app.js') }}"></script>
<link href="{{ static_v(request, 'foo_static', path='admin/css/site.css') }}" rel="stylesheet">
Images, fonts, and JSON locale files are intentionally out of scope —
keep using `url_for()` for those.
pattern:
file_pattern: "app/**/templates/**/*.html"
anti_patterns:
- "url_for\\(\\s*['\"]\\w+_static['\"]\\s*,\\s*path=['\"][^'\"]+\\.(?:js|css)['\"]"
exceptions:
- "base.html"
- "partials/"
# ============================================================================ # ============================================================================
# FRONTEND STYLING RULES # FRONTEND STYLING RULES
# ============================================================================ # ============================================================================

31
app/core/static_files.py Normal file
View File

@@ -0,0 +1,31 @@
# app/core/static_files.py
"""Static file serving with cache-busting-aware headers.
In production, JS/CSS/etc. URLs are versioned by `static_v(...)` in
`app/templates_config.py` (appends `?v=<commit-sha>`), so the file at a
given URL never changes within a deploy. We can therefore tell browsers
to cache it for a year and skip revalidation.
In development the version still flips per commit, but the developer often
edits files without committing, so we serve `no-cache` to force conditional
GETs (the browser still gets a 304 when nothing changed).
"""
from fastapi.staticfiles import StaticFiles
from starlette.responses import FileResponse
from app.core.environment import is_development
_IMMUTABLE = "public, max-age=31536000, immutable"
_NO_CACHE = "no-cache"
class CachedStaticFiles(StaticFiles):
"""StaticFiles that sets Cache-Control based on environment."""
def file_response(self, *args, **kwargs) -> FileResponse:
response = super().file_response(*args, **kwargs)
response.headers["Cache-Control"] = (
_NO_CACHE if is_development() else _IMMUTABLE
)
return response

View File

@@ -227,5 +227,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('analytics_static', path='store/js/analytics.js') }}"></script> <script defer src="{{ static_v(request, 'analytics_static', path='store/js/analytics.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -203,5 +203,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('billing_static', path='admin/js/billing-history.js') }}"></script> <script defer src="{{ static_v(request, 'billing_static', path='admin/js/billing-history.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -474,5 +474,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('billing_static', path='admin/js/subscription-tiers.js') }}"></script> <script defer src="{{ static_v(request, 'billing_static', path='admin/js/subscription-tiers.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -316,5 +316,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('billing_static', path='admin/js/subscriptions.js') }}"></script> <script defer src="{{ static_v(request, 'billing_static', path='admin/js/subscriptions.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -465,6 +465,6 @@
document.head.appendChild(script); document.head.appendChild(script);
})(); })();
</script> </script>
<script defer src="{{ url_for('cms_static', path='shared/js/media-picker.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='shared/js/media-picker.js') }}"></script>
<script defer src="{{ url_for('catalog_static', path='admin/js/product-create.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='admin/js/product-create.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -354,5 +354,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('catalog_static', path='admin/js/product-detail.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='admin/js/product-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -498,6 +498,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='shared/js/media-picker.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='shared/js/media-picker.js') }}"></script>
<script defer src="{{ url_for('catalog_static', path='admin/js/product-edit.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='admin/js/product-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -336,5 +336,5 @@
document.head.appendChild(script); document.head.appendChild(script);
})(); })();
</script> </script>
<script defer src="{{ url_for('catalog_static', path='admin/js/products.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='admin/js/products.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -170,5 +170,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('catalog_static', path='store/js/product-create.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='store/js/product-create.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -364,5 +364,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('catalog_static', path='store/js/products.js') }}"></script> <script defer src="{{ static_v(request, 'catalog_static', path='store/js/products.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -701,5 +701,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='admin/js/content-page-edit.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='admin/js/content-page-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -201,5 +201,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='admin/js/content-pages.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='admin/js/content-pages.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -459,5 +459,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='admin/js/store-theme.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='admin/js/store-theme.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -125,5 +125,5 @@
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.complete.min.js"></script>
<script defer src="{{ url_for('cms_static', path='admin/js/store-themes.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='admin/js/store-themes.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -333,5 +333,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='store/js/content-page-edit.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='store/js/content-page-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -335,5 +335,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='store/js/content-pages.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='store/js/content-pages.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -452,5 +452,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('cms_static', path='store/js/media.js') }}"></script> <script defer src="{{ static_v(request, 'cms_static', path='store/js/media.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -137,5 +137,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('core_static', path='admin/js/dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'core_static', path='admin/js/dashboard.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -816,5 +816,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('core_static', path='admin/js/settings.js') }}"></script> <script defer src="{{ static_v(request, 'core_static', path='admin/js/settings.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -180,5 +180,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('core_static', path='store/js/dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'core_static', path='store/js/dashboard.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -217,5 +217,5 @@
document.head.appendChild(script); document.head.appendChild(script);
})(); })();
</script> </script>
<script defer src="{{ url_for('customers_static', path='admin/js/customers.js') }}"></script> <script defer src="{{ static_v(request, 'customers_static', path='admin/js/customers.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -174,5 +174,5 @@
customerId: {{ customer_id }} customerId: {{ customer_id }}
}; };
</script> </script>
<script defer src="{{ url_for('customers_static', path='store/js/customer-detail.js') }}"></script> <script defer src="{{ static_v(request, 'customers_static', path='store/js/customer-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -210,5 +210,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('customers_static', path='store/js/customers.js') }}"></script> <script defer src="{{ static_v(request, 'customers_static', path='store/js/customers.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -9,7 +9,7 @@
{% block alpine_data %}codeQualityDashboard(){% endblock %} {% block alpine_data %}codeQualityDashboard(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/code-quality-dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/code-quality-dashboard.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -10,7 +10,7 @@
{% block alpine_data %}codeQualityViolations(){% endblock %} {% block alpine_data %}codeQualityViolations(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/code-quality-violations.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/code-quality-violations.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -3173,5 +3173,5 @@ new Chart(document.getElementById('barChart'), barConfig);
{% block extra_scripts %} {% block extra_scripts %}
{# ✅ CRITICAL: Load JavaScript file #} {# ✅ CRITICAL: Load JavaScript file #}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/components.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/components.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -318,5 +318,5 @@
{% block extra_scripts %} {% block extra_scripts %}
{# ✅ CRITICAL: Load JavaScript file #} {# ✅ CRITICAL: Load JavaScript file #}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/icons-page.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/icons-page.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -274,5 +274,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/sql-query.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/sql-query.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -8,7 +8,7 @@
{% block alpine_data %}testingDashboard(){% endblock %} {% block alpine_data %}testingDashboard(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/testing-dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/testing-dashboard.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -218,5 +218,5 @@
{% block extra_scripts %} {% block extra_scripts %}
{# ✅ CRITICAL: Load JavaScript file #} {# ✅ CRITICAL: Load JavaScript file #}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/testing-hub.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/testing-hub.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -159,5 +159,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('dev_tools_static', path='admin/js/translation-editor.js') }}"></script> <script defer src="{{ static_v(request, 'dev_tools_static', path='admin/js/translation-editor.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -599,5 +599,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('inventory_static', path='admin/js/inventory.js') }}"></script> <script defer src="{{ static_v(request, 'inventory_static', path='admin/js/inventory.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -370,5 +370,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('inventory_static', path='store/js/inventory.js') }}"></script> <script defer src="{{ static_v(request, 'inventory_static', path='store/js/inventory.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -175,5 +175,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-analytics.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-analytics.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -21,6 +21,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-card-detail-view.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-card-detail-view.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-card-detail.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-card-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -23,6 +23,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-cards-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-cards-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-cards.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-cards.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -428,5 +428,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-detail.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -22,6 +22,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-devices-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-devices-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-devices.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-devices.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -22,6 +22,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-pins.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-pins.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -178,5 +178,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-settings.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-settings.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -21,6 +21,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-transactions-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-transactions-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-merchant-transactions.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-merchant-transactions.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -41,6 +41,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-program-edit.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-program-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -342,5 +342,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='admin/js/loyalty-programs.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='admin/js/loyalty-programs.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -63,5 +63,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-analytics.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-analytics.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -25,6 +25,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-card-detail-view.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-card-detail-view.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-card-detail.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-card-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -29,6 +29,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-cards-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-cards-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-cards.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-cards.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -31,6 +31,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-devices-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-devices-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-devices.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-devices.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -33,6 +33,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-pins.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-pins.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -39,6 +39,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-settings.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-settings.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -85,5 +85,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-merchant-settings.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-merchant-settings.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -27,6 +27,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-transactions-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-transactions-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='merchant/js/loyalty-transactions.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='merchant/js/loyalty-transactions.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -156,5 +156,5 @@
{% block extra_scripts %} {% block extra_scripts %}
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-analytics.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-analytics.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -201,5 +201,5 @@ window._cardDetailLabels = {
} }
}; };
</script> </script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-card-detail.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-card-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -167,5 +167,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-cards.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-cards.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -147,5 +147,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-enroll.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-enroll.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -44,6 +44,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-pins-list.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-pins.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-pins.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -61,6 +61,6 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='shared/js/loyalty-program-form.js') }}"></script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-settings.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-settings.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -407,5 +407,5 @@ window._txLabels = {
reward_redeemed: {{ _('loyalty.transactions.reward_redeemed')|tojson }}, reward_redeemed: {{ _('loyalty.transactions.reward_redeemed')|tojson }},
}; };
</script> </script>
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-terminal.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='store/js/loyalty-terminal.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -232,5 +232,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='storefront/js/loyalty-dashboard.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -170,5 +170,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-enroll.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='storefront/js/loyalty-enroll.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -105,5 +105,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('loyalty_static', path='storefront/js/loyalty-history.js') }}"></script> <script defer src="{{ static_v(request, 'loyalty_static', path='storefront/js/loyalty-history.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -8,7 +8,7 @@
{% block alpine_data %}backgroundTasks(){% endblock %} {% block alpine_data %}backgroundTasks(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/background-tasks.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/background-tasks.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -11,7 +11,7 @@
{% block alpine_data %}adminImports(){% endblock %} {% block alpine_data %}adminImports(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/imports.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/imports.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -426,5 +426,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/letzshop-store-directory.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/letzshop-store-directory.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -11,7 +11,7 @@
{% block alpine_data %}adminLetzshop(){% endblock %} {% block alpine_data %}adminLetzshop(){% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/letzshop.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/letzshop.js') }}"></script>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@@ -570,5 +570,5 @@
document.head.appendChild(script); document.head.appendChild(script);
})(); })();
</script> </script>
<script defer src="{{ url_for('marketplace_static', path='admin/js/marketplace-letzshop.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/marketplace-letzshop.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -388,5 +388,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/marketplace-product-detail.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/marketplace-product-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -412,5 +412,5 @@
document.head.appendChild(script); document.head.appendChild(script);
})(); })();
</script> </script>
<script defer src="{{ url_for('marketplace_static', path='admin/js/marketplace-products.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/marketplace-products.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -347,5 +347,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('marketplace_static', path='admin/js/marketplace.js') }}?v=2"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='admin/js/marketplace.js') }}?v=2"></script>
{% endblock %} {% endblock %}

View File

@@ -378,6 +378,6 @@
<script defer src="{{ url_for('static', path='shared/js/utils.js') }}"></script> <script defer src="{{ url_for('static', path='shared/js/utils.js') }}"></script>
<script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script> <script defer src="{{ url_for('static', path='shared/js/api-client.js') }}"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script> <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.0/dist/cdn.min.js"></script>
<script defer src="{{ url_for('marketplace_static', path='store/js/onboarding.js') }}"></script> <script defer src="{{ static_v(request, 'marketplace_static', path='store/js/onboarding.js') }}"></script>
</body> </body>
</html> </html>

View File

@@ -347,5 +347,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='admin/js/email-logs.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='admin/js/email-logs.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -364,5 +364,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='admin/js/email-templates.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='admin/js/email-templates.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -335,5 +335,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='admin/js/messages.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='admin/js/messages.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -361,5 +361,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='admin/js/notifications.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='admin/js/notifications.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -329,5 +329,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='store/js/email-templates.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='store/js/email-templates.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -278,5 +278,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='store/js/messages.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='store/js/messages.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -229,5 +229,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('messaging_static', path='store/js/notifications.js') }}"></script> <script defer src="{{ static_v(request, 'messaging_static', path='store/js/notifications.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -407,5 +407,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('monitoring_static', path='admin/js/logs.js') }}"></script> <script defer src="{{ static_v(request, 'monitoring_static', path='admin/js/logs.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -263,5 +263,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('monitoring_static', path='admin/js/platform-health.js') }}"></script> <script defer src="{{ static_v(request, 'monitoring_static', path='admin/js/platform-health.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -580,5 +580,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('orders_static', path='admin/js/orders.js') }}"></script> <script defer src="{{ static_v(request, 'orders_static', path='admin/js/orders.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -451,5 +451,5 @@
orderId: {{ order_id }} orderId: {{ order_id }}
}; };
</script> </script>
<script defer src="{{ url_for('orders_static', path='store/js/order-detail.js') }}"></script> <script defer src="{{ static_v(request, 'orders_static', path='store/js/order-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -330,5 +330,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('orders_static', path='store/js/orders.js') }}"></script> <script defer src="{{ static_v(request, 'orders_static', path='store/js/orders.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -156,5 +156,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/campaigns.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/campaigns.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -148,5 +148,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/capture.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/capture.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -127,5 +127,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/dashboard.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/dashboard.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -139,5 +139,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/leads.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/leads.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -429,5 +429,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/prospect-detail.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/prospect-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -241,5 +241,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/prospects.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/prospects.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -98,5 +98,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('prospecting_static', path='admin/js/scan-jobs.js') }}"></script> <script defer src="{{ static_v(request, 'prospecting_static', path='admin/js/scan-jobs.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -271,5 +271,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/admin-user-detail.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/admin-user-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -353,5 +353,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/admin-user-edit.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/admin-user-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -283,5 +283,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/admin-users.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/admin-users.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -210,6 +210,6 @@
</script> </script>
<!-- 6. Login Logic --> <!-- 6. Login Logic -->
<script defer src="{{ url_for('core_static', path='admin/js/login.js') }}"></script> <script defer src="{{ static_v(request, 'core_static', path='admin/js/login.js') }}"></script>
</body> </body>
</html> </html>

View File

@@ -483,5 +483,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/merchant-detail.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/merchant-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -490,5 +490,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/merchant-edit.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/merchant-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -281,5 +281,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/merchant-user-detail.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/merchant-user-detail.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -247,5 +247,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/merchant-user-edit.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/merchant-user-edit.js') }}"></script>
{% endblock %} {% endblock %}

View File

@@ -250,5 +250,5 @@
{% endblock %} {% endblock %}
{% block extra_scripts %} {% block extra_scripts %}
<script defer src="{{ url_for('tenancy_static', path='admin/js/merchant-users.js') }}"></script> <script defer src="{{ static_v(request, 'tenancy_static', path='admin/js/merchant-users.js') }}"></script>
{% endblock %} {% endblock %}

Some files were not shown because too many files have changed in this diff Show More