Add experimental HTML/CSS "modern" render style for weather widget
Build and push server image / test (push) Successful in 39s
Build and push server image / build-and-push (push) Failing after 2m34s
Build and push server image / deploy (push) Has been skipped

The weather widget's icons/layout are hand-drawn PIL primitives -- clean
under quantization but flat, no gradients/shadows. Adds an opt-in
render_style="modern" (current/daily modes only) that instead renders a
Jinja2 template through a persistent headless-Chromium browser
(app/html_render.py), following the approach of Tesserae, an open-source
e-ink dashboard targeting this same panel family.

Key design points:
- The Chromium dependency (Playwright) is lazily imported only when a
  weather widget actually uses "modern" style, and the background browser
  itself only launches on first use -- every other widget type, and this
  one's own classic/hourly/multi_city paths, never pay for it.
- No Frame-level dithering setting needed: html_render dithers its own
  rendered widget to exact palette colors (Bayer/ordered, not
  Floyd-Steinberg) before compositing, so the shared whole-canvas
  Floyd-Steinberg pass sees zero quantization error there and leaves it
  untouched -- same trick draw_text/hand-drawn icons already use. Floyd-
  Steinberg keeps working unchanged for photos and every other widget.
- A "Load calibrated Spectra 6 preset" button in Advanced configuration
  offers a community-measured palette (data ported from
  paperlesspaper/epdoptimize, Apache 2.0) as an alternative starting
  point to the existing idealized DEFAULT_PALETTE_RGB -- fills the
  existing palette table, doesn't save by itself.

Known open risk, not resolved here: a headless Chromium binary is far
larger than the ~100MB single-layer limit that already forced this
project's pip/npm installs into split layers, and (unlike those) is a
single ~180MB file that can't be split across layers by ordinary
Dockerfile restructuring. Flagged prominently in server/Dockerfile and
docs/widgets.md -- treat this render style as experimental/local-only
until that's resolved.
This commit is contained in:
2026-07-30 22:18:43 +00:00
parent d34eb1bf45
commit 8ea1c53ec3
22 changed files with 717 additions and 22 deletions
+7
View File
@@ -187,6 +187,12 @@
<button type="button" class="secondary" id="palette-save" style="margin-top: 16px;">Save</button>
<button type="button" class="secondary" id="palette-reset">Reset to defaults</button>
<button type="button" class="secondary" id="palette-load-calibrated">Load calibrated Spectra 6 preset</button>
<p class="sub" style="margin-top: 8px;">Experimental: a community-measured
starting point (not this specific panel) -- fills the table above,
doesn't save by itself. Real Spectra 6 ink is duller than the
idealized defaults; this may or may not match your actual unit.
Compare against the physical panel before keeping it.</p>
</details>
<section class="card">
@@ -221,6 +227,7 @@
<script>
window.FRAME_BASE_API = window.FRAME_API = {{ ("/api/frames/" ~ frame.id) | tojson }};
window.DEFAULT_PALETTE_HEX = {{ palette_to_hex(default_palette_rgb) | tojson }};
window.CALIBRATED_SPECTRA6_HEX = {{ calibrated_spectra6_hex | tojson }};
window.PHOTO_WIDGET_PREVIEW_API = {{ (("/api/frames/" ~ frame.id ~ "/widgets/" ~ photo_widget_id) | tojson) if photo_widget_id else "null" }};
</script>
<script src="/static/device_status_bar.js"></script>