feat(web): ES-module page lifecycle and one schema field model (stage 1) (#703)

Adds a native ES-module layer to the web UI (core/boot, registry, api, facade; window.LEDMatrix as the one global), a page lifecycle that the Cache tab is converted to as the reference, text/javascript serving and revalidation for unversioned module requests, and src/plugin_system/field_model.py with a parity test against the render_field macro. Also: the cache page toggles its grey 'Not configured' style instead of only adding it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-10-01 10:00:26 -04:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 795834811f
commit c6701ac00d
27 changed files with 3006 additions and 175 deletions
+25 -1
View File
@@ -1,6 +1,7 @@
from flask import Flask, request, redirect, url_for, jsonify, Response, send_from_directory
import json
import logging
import mimetypes
import os
import queue
import re
@@ -49,6 +50,15 @@ _VCGENCMD = shutil.which('vcgencmd')
from web_interface import display_preview
from web_interface.system_metrics import collect_system_metrics
# Static files get their Content-Type from the mimetypes table, which reads the
# host's own files (/etc/mime.types, the Windows registry). Browsers refuse to
# run a <script type="module"> served as anything but JavaScript (the
# static/v3/js/core and js/pages modules), and X-Content-Type-Options: nosniff
# below makes them strict about classic scripts too. Pin it rather than trust
# whatever the host says.
mimetypes.add_type('text/javascript', '.js')
mimetypes.add_type('text/javascript', '.mjs')
# Create Flask app
app = Flask(__name__)
app.secret_key = os.urandom(24)
@@ -618,6 +628,13 @@ def _apply_gzip(response, compressed):
return response
def _is_unversioned_static_script():
"""A /static/ .js or .mjs request with no ``v`` (content version) parameter."""
return (request.path.startswith('/static/')
and request.path.endswith(('.js', '.mjs'))
and 'v' not in request.args)
# Add security headers and caching to all responses
@app.after_request
def add_security_headers(response):
@@ -629,7 +646,14 @@ def add_security_headers(response):
response.headers['X-XSS-Protection'] = '1; mode=block'
# Add caching headers for static assets
if request.path.startswith(_VERSIONED_ASSET_PREFIXES):
if _is_unversioned_static_script():
# A script requested without the ?v= content version. ES modules
# (static/v3/js/core, js/pages) import each other by plain relative
# URL, which url_for never sees, so a year-long immutable copy would
# keep running the old module after an update. Let the browser keep
# it but revalidate (a 304 when unchanged).
response.headers['Cache-Control'] = 'no-cache'
elif request.path.startswith(_VERSIONED_ASSET_PREFIXES):
# Cache static assets for 1 year (with versioning via query params)
response.headers['Cache-Control'] = 'public, max-age=31536000, immutable'
response.headers['Expires'] = (datetime.now() + timedelta(days=365)).strftime('%a, %d %b %Y %H:%M:%S GMT')