diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 827c1be..4bd90c8 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -65,6 +65,12 @@ whole screen, invokes Home Assistant-aware providers, renders Liquid fragments, and composes a single TRMNL document. The Renderer App contract remains `HTML + width + height -> PNG` and contains no project or HA concepts. +Theme, font family, and text scale are project-level display preferences. The +composer maps them to TRMNL Framework screen classes before sending the same +HTML to preview and Media Source. Widgets do not own or duplicate these +settings; they react to the framework variables and their physical region +container. + Backend panel registration follows the current Core Dynalite pattern: local static files, WebSocket commands, and an admin-only custom panel. Studio is not a standalone frontend, so its component model follows the Matter and ZHA diff --git a/CHANGELOG.md b/CHANGELOG.md index 95280cf..54f6728 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,17 @@ ## Unreleased +## 0.5.1 + +- Replace the integration-owned Liquid subset with `trmnl-liquid-py` 0.1.0, + matching the supported non-I18n TRMNL Liquid 0.8.2 rendering surface. +- Validate every bundled widget template against the shared TRMNL engine and + cover lax missing-data and inline-template behavior with regression tests. +- Add project-level light and dark TRMNL themes. +- Add project-level default, classic, and TRMNL font families plus four text scales. +- Add a short-wide Weather composition for dense grids such as 800×480 at 3×3. +- Verify bundled Home Assistant brand icon dimensions in CI. + ## 0.5.0 - Introduce self-contained widget packages under `opendisplay_studio/widgets`. diff --git a/README.md b/README.md index 42666e2..4eefff7 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,12 @@ media-source://opendisplay_studio/ On every resolution the integration deduplicates widget data requirements, reads current entity states, calendar events, and requested weather forecasts, -renders widget Liquid templates, composes one TRMNL Framework document, and -sends that final HTML to the Renderer. The Renderer App still knows only HTML, -width, and height. The designer preview calls this same Renderer path and shows -its PNG, so the visible preview and final Media Source do not have separate CSS -or layout implementations. +renders widget Liquid templates through +[`trmnl-liquid-py`](https://github.com/Misiu/trmnl-liquid-py), composes one +TRMNL Framework document, and sends that final HTML to the Renderer. The +Renderer App still knows only HTML, width, and height. The designer preview +calls this same Renderer path and shows its PNG, so the visible preview and +final Media Source do not have separate CSS or layout implementations. See [the widget contract](WIDGET_CONTRACT.md) for the schema/data/template boundary, native Home Assistant selectors, and the controlled provider model. diff --git a/STAGE2_COMPATIBILITY.md b/STAGE2_COMPATIBILITY.md index a8ce0c6..fc91202 100644 --- a/STAGE2_COMPATIBILITY.md +++ b/STAGE2_COMPATIBILITY.md @@ -8,41 +8,32 @@ height. The App has no Liquid dependency or template API. ## Liquid engine -The integration pins `python-liquid==2.3.1`, a Python implementation tested -against Shopify's Golden Liquid suite. It runs in strict mode, has no filesystem -loader, auto-escapes variables, and applies limits for block depth, context -depth, loop iterations, local variables, and output bytes. - -Supported in the POC: - -- variables and nested mapping/list access; -- `assign`, `capture`, `if`/`elsif`/`else`, `unless`, `case`, `for`, `break`, - `continue`, and standard Shopify filters supplied by Python Liquid; -- selected TRMNL filters ported from `trmnl-liquid` 0.8.2: - `number_with_delimiter`, `json`, `parse_json`, `map_to_i`, `group_by`, and - `find_by`. - -Not yet ported: - -- TRMNL's custom `{% template %}` tag; -- `where_exp`, QR code, Markdown, locale/Rails helpers, currency, ordinal, and - timezone-specific filters; -- arbitrary user templates or Home Assistant entity bindings. - -The official `usetrmnl/byos_node_lite` reference does not use Ruby either. It -uses LiquidJS with strict filters and variables inside its Node server. That is -useful compatibility evidence, but moving Liquid into this project's App would -violate the renderer-only boundary. A fuller Python port can become a separate -integration-side package after the POC. +The integration pins +[`trmnl-liquid-py==0.1.0`](https://github.com/Misiu/trmnl-liquid-py). The package +targets byte-for-byte compatibility with the supported non-I18n surface of Ruby +`trmnl-liquid` 0.8.2. Its differential corpus currently matches all 574 covered +Ruby outputs and maps all 73 upstream RSpec examples. + +The shared package supplies the standard Liquid language, TRMNL's inline +`{% template %}` tag, Ruby-compatible lax syntax and runtime behavior, Markdown, +QR generation, and the supported TRMNL filters. Rendering intentionally follows +TRMNL's unescaped, lax semantics; missing provider fields become empty Liquid +values instead of failing an otherwise renderable display. Each widget fragment +uses a fresh in-memory environment, so inline template definitions cannot leak +between installed widgets and Liquid has no filesystem fallback. + +Rails/ActionView behavior and full I18n implementations of `l_word` and `l_date` +remain outside the package's 0.1.0 compatibility target. Widget-owned translation +files and Home Assistant localization handle OpenDisplay Studio presentation +language independently of those Rails helpers. ## TRMNL Liquid Components The stat/item, table, and progress structures were adapted from the MIT-licensed `usetrmnl/trmnl-liquid-components` repository at commit -`77445360fb10b0d9edd2b28ffe574574ae563417`. Its custom `{% template %}` wrapper -was deliberately removed because that tag is outside the current subset. The -resulting HTML uses the same public Framework classes and renders as one DOM and -one screenshot. +`77445360fb10b0d9edd2b28ffe574574ae563417`. Installed widgets may now use the +same `{% template %}` and `{% render %}` primitives as TRMNL. The resulting HTML +uses the public Framework classes and renders as one DOM and one screenshot. ## Display profiles and palettes @@ -61,5 +52,5 @@ not final e-paper quantization or dithering by OpenDisplay. The App bundles TRMNL Framework 3.2.0 CSS/JS and fonts with upstream MIT, OFL, and CC BY notices. It contains no Highcharts distribution or plugin image -bundle. The integration's Python Liquid dependency and the adapted Liquid -Components source are MIT licensed. No render path requires Internet access. +bundle. The integration's `trmnl-liquid-py` dependency and the adapted Liquid +Components source are MIT licensed. diff --git a/WIDGET_CONTRACT.md b/WIDGET_CONTRACT.md index ba9275c..2b3050c 100644 --- a/WIDGET_CONTRACT.md +++ b/WIDGET_CONTRACT.md @@ -125,6 +125,12 @@ The panel displays the PNG returned by the same renderer used for Media Source. Its local widget component is only a temporary loading fallback; it is not a second authoritative renderer. +Display-wide presentation is part of the project contract. `theme` maps to +the TRMNL light or dark screen mode, `fontFamily` selects the default, classic, +or TRMNL family, and `textScale` selects small, regular, large, or extra-large +framework typography. These settings must remain outside widget configuration +so every package shares one predictable display environment. + `--screen-w` and `--screen-h` describe the physical device. Responsive widget decisions use the actual CSS region container dimensions, never names such as full or half. A package must work for arbitrary device and region sizes. diff --git a/custom_components/opendisplay_studio/composer.py b/custom_components/opendisplay_studio/composer.py index 29ec07b..4ad9d20 100644 --- a/custom_components/opendisplay_studio/composer.py +++ b/custom_components/opendisplay_studio/composer.py @@ -9,9 +9,10 @@ from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError +from liquid.exceptions import LiquidError +from trmnl_liquid import render as render_liquid from .const import DOMAIN -from .liquid_renderer import LIQUID from .projects import Project from .widgets import DEFAULT_REGISTRY, WidgetRegistry, definition, with_defaults @@ -32,10 +33,10 @@ class ProjectComposeError(Exception): STUDIO_STYLES = """