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

@@ -9,7 +9,7 @@
{% block alpine_data %}codeQualityDashboard(){% endblock %}
{% 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 %}
{% block content %}

View File

@@ -10,7 +10,7 @@
{% block alpine_data %}codeQualityViolations(){% endblock %}
{% 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 %}
{% block content %}

View File

@@ -3173,5 +3173,5 @@ new Chart(document.getElementById('barChart'), barConfig);
{% block extra_scripts %}
{# ✅ 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 %}

View File

@@ -318,5 +318,5 @@
{% block extra_scripts %}
{# ✅ 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 %}

View File

@@ -274,5 +274,5 @@
{% endblock %}
{% 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 %}

View File

@@ -8,7 +8,7 @@
{% block alpine_data %}testingDashboard(){% endblock %}
{% 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 %}
{% block content %}

View File

@@ -218,5 +218,5 @@
{% block extra_scripts %}
{# ✅ 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 %}

View File

@@ -159,5 +159,5 @@
{% endblock %}
{% 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 %}