Framework Runtime

Different devices have different, fixed amounts of screen space. The Framework Runtime fills that space when a plugin layout renders, doing the heavy, repetitive measuring and fitting for you. Expand the "Framework Runtime" panel under any example on this site to see the stats for that render.

#

What it does

At a high level, it measures the space you have and then plans columns, clamps text, formats and fits values, and adjusts gaps and index widths, so everything fits neatly without manual tweaking.

  • Normalizes screen context (size, orientation, bit depth, scale)
  • Maps legacy responsive classes to data attributes for consistency
  • Formats values and fits numbers into their containers
  • Adjusts gaps so columns/grids land on integer pixel widths
  • Plans multi-column layouts, re-clamps text per column, and can add a trailing "and N more" label when enabled
  • Applies standalone clamping where needed outside columns
  • Limits overly tall content and schedules pixel-perfect text processing
#

Runtime steps and stats

When the runtime executes, it performs these steps in the order below. Column Gaps runs twice: once before Overflow for the columns Overflow does not own, and once at the end.

Images

Waits for every image to settle, then recolors the adaptive ones for the current mode.

  • Holds the pass until images have loaded, so later steps measure real heights
  • Repaints image--adaptive sources for the screen's bit depth, dark mode, and theme

Index Widths

Ensures item index badges render at even widths to avoid artifacts.

  • Runs as one pass per terminalize, for indices outside .columns
  • Runs again inside each .columns container after its layout commits
  • Skipped on 2-bit and higher, where any width it pinned is cleared

Value Formatting

Formats numbers to fit available space and abbreviates as needed (k, M, B).

  • Accepts data-value-format="true" or data-value-type="number"
  • Respects data-value-locale
  • Works with data-fit-value for auto-sizing

Fit Value

Adjusts font size, line-height, and weight to fit numbers within their containers.

  • Minimum font size safeguard (default 8px)
  • Accepts data-fit-value or data-value-fit

Grid Gaps

Tweaks CSS gaps so grid column widths resolve to integer pixels.

  • Disable with data-adjust-grid-gaps="false"
  • Falls back to measuring child positions when gap is not explicitly set

Overflow

Plans 1..N columns with off-screen staging and commits the best fit, then re-clamps per real column width.

  • Duplicates group headers across columns when needed
  • Optional trailing "and N more" label for hidden items (enable with data-overflow-counter="true")
  • Enforces final fit by hiding trailing items if necessary

Clamp

Clamps text to N lines.

  • Word-based ellipsis
  • Preserves original text
  • Re-clamps when widths change
  • Supports responsive data attributes (size/orientation)
  • Maps legacy class utilities to data-clamp
  • Applies outside and inside columns (per-column re-clamp handled by Overflow)

Table Overflow

Trims table rows that do not fit the space the table has.

  • Opt in with data-table-limit="true" on the table
  • Runs after Clamp, so it measures rows at their final line count

Content Limiter

Caps content at an explicit height budget, or at the space it measures as available, and flags small content.

  • Set the budget with data-content-max-height, otherwise the limiter measures the view's layout
  • Adds content--small, then sets data-clamp and data-clamp-max-height-px on the block it has to trim
#

Driving the runtime from JavaScript

The runtime starts itself. It runs one pass after the page load event, then runs again whenever a screen--* class changes on a .screen element. Content injected after that needs an explicit re-run.

#

Functions

  • terminalize(): runs the full pipeline and returns a Promise that resolves once the screen has settled, including the deferred pixel-perfect pass. Call it after you inject or replace content.
  • executeTerminalize(): queues a run two animation frames out instead of starting one immediately. Repeated calls before it fires collapse into a single pass.
  • markFrameworkReady(): sets window.frameworkReady and dispatches trmnl:framework:ready on window. A host page calls it once its own setup is done.
#

Ready signals

  • window.TRMNL_PLUGINS_READY: false while a pass runs and true once it settles. A screenshot pipeline waits for true before it captures.
  • window.frameworkReady: false until markFrameworkReady() runs.
  • window.__TRMNL_BUILD__: the build stamp of the loaded plugins.js. A released bundle reports its own version (plugins.js v3.2.0) and a working checkout reports plugins.js source. Read it when an edit does not show up and you suspect a pinned or cached file.
#

The stats event

Every pass dispatches trmnl:terminalize:stats on window. Its detail carries three fields.

  • steps: every step the pass ran, each with a name, a durationMs, and its own counters.
  • engines and engineCount: the subset of steps that changed something.
  • errors: present only when an engine threw, as { engine, message } entries. The pass runs the remaining engines and readiness still flips to true.
// Re-run after injecting content into a screen.
terminalize().then(function () {
  console.log("screen settled");
});

// Inspect what each pass did.
window.addEventListener("trmnl:terminalize:stats", function (event) {
  console.log(event.detail.engineCount, "engines changed something");
});
#

Debug logging

Set window.__TRMNL_DEBUG__ = true before a pass to log what each engine decided. Renders are quiet by default. Engine failures always reach the console, debug flag or not.

plugins.js also puts TRMNLPaint and TRMNLCharts on window. Those are documented on Paint API .

#

Why this exists

Plugins need to fit source data into a static layout space that is device-defined and varies by model, orientation, and density. While this resembles responsive web design, the runtime provides purpose-built tools (overflow planning, per-column clamping, integer pixel alignment, and value fitting) tailored specifically for TRMNL devices.

#

These tokens are automatically mapped to this page by token prefix.

Token 1-bit 2-bit Density 2x 4-bit and up
--content-scale 1 - - -
--device-ui-scale 1 - - -
--full-h calc(var(--screen-h) - var(--gap) * 2) - - -
--full-w calc(var(--screen-w) - var(--gap) * 2) - - -
--gap-scale 1 - - -
--half_horizontal-h calc((var(--screen-h) - var(--gap) * 2) / 2 - var(--gap) / 2) - - -
--half_horizontal-w calc((var(--screen-w) - var(--gap) * 2)) - - -
--half_vertical-h calc((var(--screen-h) - var(--gap) * 2)) - - -
--half_vertical-w calc((var(--screen-w) - var(--gap) * 2) / 2 - var(--gap) / 2) - - -
--modifier-scale 1 - - -
--modifier-text-scale 1 - - -
--quadrant-h calc((var(--screen-h) - var(--gap) * 2) / 2 - var(--gap) / 2) - - -
--quadrant-w calc((var(--screen-w) - var(--gap) * 2) / 2 - var(--gap) / 2) - - -
--screen-h 480px - - -
--screen-h-original 480px - - -
--screen-w 800px - - -
--screen-w-original 800px - - -
--text-ui-scale 1 - - -
--ui-scale 1 - - -
#
#

The paint half of the runtime

The same plugins.js that runs these engines also ships TRMNLPaint, the framework's public paint API. It reads the live cascade and returns canonical Fill, BorderFill, and TypeSpec objects, so a plugin can resolve framework colors from JavaScript while the engines handle layout. See Paint API .