github dmellok/tesserae v0.386.1

latest releases: v0.386.3, v0.386.2
5 hours ago

Added

  • Touch settings on the Seeed reTerminal Sticky. The Sticky's device
    settings now carry the same "Touch input" and "Touch linger" fields as the
    reTerminal E1003, so a firmware build with the Sticky's GT911 wired up can
    be switched on from the server. The digitiser's orientation is confirmed
    on hardware; the hardware entry notes touch wake as the remaining check.
  • Refreshes per day on the Lineups page (#278). Every schedule,
    rotation and timer-advancing deck card now shows how many pushes its
    timer projects for today, next to the next-fire time, and each display's
    section header sums them so the refresh load on a panel is readable at a
    glance. Counts are projections from the same walk that draws the 24h
    rail: conditions and the minimum hold can skip some, and taps, Send now
    and data-change refreshes are extra.
  • Editable calendar feed colours. The colour swatch on each saved feed in Settings → Widgets → Calendar feeds is now a colour picker; pick a new colour and it saves in place, no need to delete and re-add the feed. Useful when tuning feed colours for a specific panel's gamut. (#276)
  • Trusted networks for the auth gate. Settings → System →
    Authentication gains a "Trusted networks" list (also
    TESSERAE_TRUSTED_NETWORKS, comma-separated CIDRs). Networks listed
    there count as local alongside the fixed private ranges, so a LAN on a
    globally routable IPv6 prefix delegated by the ISP can fetch /renders/
    and, with the password disabled, reach the admin UI, instead of being
    treated as the open internet. /0 is refused.
  • Waveshare 10.85" e-Paper HAT+ (G) hardware kind. New waveshare_1085g
    kind for the 1360x480 four-colour (black/white/yellow/red) panel on an
    ESP32-S3 Zero: bwry_4 gamut, a 163200-byte 2-bpp frame from the
    esp32_bin renderer, paired with the waveshare-1085g target in
    tesserae-device-firmware. Confirmed on real hardware and added to the
    test matrix.
  • The MCP bridge's widget catalog carries a one-sentence description per
    widget
    (#257). list_widgets used to ship every widget's full description
    whether or not the agent placed it, the largest single item in a build's
    context budget. The catalog now carries the first sentence (capped at 160
    characters) and the per-widget options call returns the full text, which
    matches the list-then-read-options loop the tool docs already prescribe.
    Measured on the bundled set, the description block shrinks by about 69%.
    Bridge 0.15.0 also fixes the summarised view dropping descriptions
    entirely (it read the wrong key).
  • iCloud calendars work as private CalDAV feeds (#272). iCloud has no
    GET-able .ics export: a plain GET is answered with a challenge-less 403,
    and events are only readable through CalDAV REPORT requests. Calendar feeds
    now fall back to a REPORT calendar-query (events across the expansion
    window, todos unbounded) and stitch the collection back into the same
    cached blob shape the widgets already read, and basic credentials ride on
    the first request since there is no 401 challenge to react to. Point
    Discover at https://caldav.icloud.com/ with an Apple ID plus
    app-specific password and add calendars in one click.

Fixed

  • Resend and Send-page pushes honour a client-declared rotation (#275).
    A device that registers with rotation gets its dashboard composed for
    the turned canvas and rotated back onto its framebuffer on the way out.
    Pushing a page did this; resending it from History, or pushing a gallery
    / file / URL image from Send, did not: that path rebuilt the panel by
    hand and dropped the "declared by the device" flag the CircuitPython
    renderers key the turn on, so the client received a landscape frame for
    its portrait buffer. The Send-page path now carries the resolved panel
    through unchanged (which also restores col_offset on it).

  • iCloud calendar discovery no longer fails with HTTP 400 (#272).
    iCloud refuses a Depth 1 PROPFIND on its service root instead of listing
    it. When the listing is refused, Discover now walks root → principal →
    calendar-home-set with Depth 0 requests (the same path other CalDAV
    clients take) and enumerates the calendars on the per-account host it
    lands on. A wrong password still reports as an authentication failure.

  • Calendar feed failures now say why (#272). The event fetch path never
    recorded feed health (only the todo path did), and the Refresh button
    flashed a generic "couldn't reach the feed URL" for every failure. Both now
    carry the real reason, e.g. HTTP 403, including which of the GET / REPORT
    paths failed how.

  • IPv6 binds work end to end. Base URLs (Home Assistant discovery,
    frame URLs, broker URLs) bracket IPv6 hosts, the renderer's loopback
    rewrite targets [::1] when the server binds an IPv6 host (carried via
    TESSERAE_BIND_HOST, which now also feeds the --host default), and the
    auth gate unwraps IPv4-mapped IPv6 addresses so dual-stack binds keep the
    loopback and private-range checks working.

  • Previews behind a TLS-terminating reverse proxy no longer hang. An
    https public URL leaked its scheme into the internal compose URL, so
    renders attempted a TLS handshake against the plain-HTTP in-process
    server and timed out. Loopback compose URLs are now always plain http.

  • Calendar (day) opens its window at local midnight (#252). The widget
    draws one day, so the fetch window now starts at midnight instead of at
    the render time; "hours ahead" keeps its meaning as a forward bound from
    now, and the option label says so.

  • Calendar feeds keep separate events that reuse a UID. The recurrence
    expander folds every VEVENT with the same UID into one event, so feeds
    that reuse a UID across copied appointments lost all but one. Plain
    duplicate VEVENTs (no RRULE or RECURRENCE-ID) are keyed by their start
    before expansion; recurring series and their overrides keep the shared
    UID.

  • Calendar (month) spreads multi-day timed events across every day they
    cover
    (#267). A trip starting Friday afternoon and ending Monday morning
    showed in Friday's cell only, while the week view already spanned it.
    Each continued day renders as a dimmed pass-through row in the text
    layout (with the full title in the tooltip); the default bars mode is
    unchanged.

  • Lineup editor states the real loop length. The advance interval is
    labelled as the default dwell with the computed full-loop total in the
    hint, the navigation badge uses the loop total when per-page dwells
    differ, and the background-refresh fallbacks match the model default of
    15 minutes, with help text noting the lineup cadence supersedes a member
    dashboard's own Updates setting (discussion #266).

  • History distinguishes background warms from frames a panel showed
    (discussion #266). Deck and album warms log as "warmed" behind a History
    toggle, a cache-hit rotation advance writes the push row for the frame
    the panel actually showed, deck sources get friendly chips, and
    same-cadence warms de-phase after the first pass instead of staying in
    lockstep.

  • The per-device button map saves again. The textarea on the device
    card was not associated with the card's combined form, so edits never
    showed the save bar and the value was dropped on submit.

  • Small dashboard changes no longer strand deep-sleep panels on an old
    frame
    (#271). A small same-page change on a patch-capable REST device is
    delivered as partial-refresh patches with the frame digest held stable, but
    a deep-sleep device has no framebuffer in RAM on a timer wake and could
    never apply them, so the change silently never landed while the History tab
    showed the new render. The server now keeps the full render a patch divert
    replaced and, when the patch blob was never fetched by the device's next
    /frame poll, promotes it into the live slot so that poll serves a normal
    full frame. Devices that do apply patches (touch linger, SSE) keep the
    battery-friendly partial path unchanged; no firmware update is needed.
    /preview/<device>.png (and /mirror/<device>) now also serve the newest
    accepted composition instead of the held anchor, and a diverted render now
    writes through to the warmed deck cache the same way a normal stamp does.

Added

  • Per-device "Update delivery" setting (#271). Settings → Devices →
    General on REST instances: "Auto (partial refresh when possible)" is the
    default behaviour above; "Always full refresh" disables patch delivery for
    the device entirely, so every visible change mints a new frame and a full
    repaint. Useful where partial refresh ghosts or looks incomplete.

  • The web port is settable from Docker compose via TESSERAE_BIND_PORT.
    The env var (already used to point loopback renders at the real bind port)
    now also drives the --port default, and the image CMD no longer passes an
    explicit --port that would override it. Under host networking, moving
    Tesserae off a contested 8765 is now one environment: line instead of a
    command: override. An explicit --port flag still wins, and the env var
    is rewritten to the resolved port so loopback renders always target the
    actual bind.

  • Consolidated configuration reference in the docs. New
    install/configuration.md page listing every CLI flag and TESSERAE_*
    environment variable (serving, storage, secrets, logging, experiment
    overrides) plus the role of settings.json, so deployment knobs are no
    longer scattered across install pages and source comments. The Docker page
    gains "Change the web port" and broker-on-the-Docker-host sections.

  • Seeed XIAO ePaper EE03 (10.3" mono) hardware kind. The EE03 DIY kit puts
    the reTerminal E1003's exact 10.3" panel + controller combo (ED103TC2 behind
    an IT8951, 1872x1404, 16-level greyscale) on the XIAO ESP32-S3 Plus driver
    board, so the new seeed_ee03 kind reuses the esp32_gray_bin renderer and
    packs a byte-identical 1314144-byte frame. Registered with auto_select
    off: the two kinds are indistinguishable on the wire and a kind names an
    OTA lineage, so relay pairing keeps resolving to the E1003 and EE03 units
    declare their kind themselves at registration. Firmware target lands in
    tesserae-device-firmware as seeed-ee03 (awaiting hardware verification).

Fixed

  • Calendar widgets no longer fail with "invalid language tag: C". With the app
    locale left on "system", a host LANG/LC_ALL of C.UTF-8 (the usual default in
    Docker and Ubuntu environments) leaked through locale detection as the literal tag
    C, which the calendar Day / Week / Month widgets' date formatting rejects. C and
    POSIX values are now treated as "no locale configured" regardless of codeset
    suffix, falling back to the next source in the chain.

Added

  • Calendar widgets can read Home Assistant calendars. A calendar feed can now point
    at any calendar.* entity instead of an iCal URL: on the Calendar Feeds page, "Add
    from Home Assistant" lists the calendars HA knows about (Local Calendar, Google, waste
    collection, anything an integration exposes) and adds each as a feed in one click,
    using the connection already configured in the Home Assistant Core plugin. Events flow
    through the same load_events() path, so every calendar widget (Day / Week / Month
    and the marketplace Schedule widget) picks them up unchanged, with per-feed name and
    colour, feed-health reporting, and a 15-minute cache per requested window. Recurrence
    expansion happens inside Home Assistant, so these feeds skip the ICS expansion cache.

  • The Home Assistant Sensor and Entities widgets can hide the title row and entity
    names.
    Two new toggles per cell: "Show title row" removes the whole header (so a
    single entity renders as just its value), and "Show entity names" drops the per-row
    label in list mode, leaving icon + value. Both default to on, so existing cells are
    unchanged.

  • Synchronized wake: a fleet of devices can paint together on a wall-clock grid.
    New per-device setting on the device card's Schedule tab (REST devices, off by
    default). "On the clock" keeps the sleep interval as the period but lands each wake on
    anchor + k × interval local time (a 15-minute panel anchored at 00:05 wakes at :05 /
    :20 / :35 / :50); "at set times" wakes only at a listed set of HH:MM times. Devices
    set to the same grid wake and repaint together with no cross-device coordination; an
    "apply to all devices" switch copies the setting to every REST device in one save. The
    server recomputes the grid delta on every check-in (so error never accumulates), skips
    grid points inside quiet hours, learns each device's wake-to-paint latency from its
    telemetry and shifts the wake earlier by that lead so the paint itself lands on the
    grid. The /status response additionally carries the same instant as an absolute
    wake_at epoch for firmware that can sleep to a wall-clock target; older firmware
    ignores it and still lands close via next_poll_s.

  • The dev widget previews can be rendered in a chosen language. /_test/widgets and
    /_test/preview now carry an English / French locale picker; it adds ?locale=<tag> to the
    render so a contributor can check a widget's translated strings and its Intl date / number
    formatting without touching any install-wide or per-device setting.

  • The Calendar (week) widget is translated to French. Header, event counts, and the
    weekday / month labels follow the render locale; non-English names come from Intl
    rather than a hand-rolled table.

  • The Calendar (month) widget is translated to French. Month title, weekday header,
    and the week-start note follow the render locale, on the same Intl + strings/ path
    as the week widget. The weekday header row now upper-cases by default like the other
    calendar labels (sentence-case styles still get sentence case via --label-transform).

Fixed

  • Six widgets that fetch with raw urllib now request and decode plain responses
    (#215). Reddit News, Hacker News, Wikipedia On This Day, NASA APOD, Sunrise & Sunset,
    and the Home Assistant core plugin sent no Accept-Encoding header, which lets a
    CDN-fronted upstream answer with gzip that urllib does not decompress; the widget then
    reports a parser error instead of the real cause. Each site now asks for identity
    and decodes any Content-Encoding the server sends anyway, the same guard the RSS and
    calendar widgets already carry.
  • The Webpage widget's settle delay is now honoured in full, fixing intermittent blank
    captures.
    The wait for the embedded page previously ran under a fixed 12-second cap
    covering page load and settle together, so a slow-loading site silently ate into the
    configured settle window, and a site that took longer than the cap to load was captured
    blank. The cap is now the full settle delay plus a 10-second load grace, and the
    renderer's screenshot budget was raised to outlast the worst case.
  • Setup and sign-in errors now show inline in the card. A too-short or mismatched
    password on /setup, and a wrong password on /login, previously surfaced only as a
    corner toast that auto-dismisses after five seconds; when the field was filled by a
    password manager (which bypasses the browser's minlength check) the page could appear
    to reload with no feedback at all (#265). The error now renders as a persistent strip
    inside the form card.

[0.367.3], 2026-08-27

Fixed

  • Corrected the Xteink X4 hardware notes: no per-firmware orientation is needed.
    v0.367.2 claimed the native firmware required plain landscape; hardware testing
    showed the same registration paints upright on both CrossInk and the native firmware,
    so orientation is the ordinary mounting choice and the notes now say so.

[0.367.2], 2026-08-27

Changed

  • The Xteink X4 hardware entry now covers the native device firmware as well as
    CrossInk.
    The xteink_x4 kind's notes spell out that the two firmware paths need
    different orientation values (CrossInk keeps the portrait_flipped default; the native
    xteink-x4 firmware wants the device set to plain landscape), and that the native
    build drives the SSD1677 panel revision only, while CrossInk auto-detects the later
    UC8179/UC8279 revisions.

Don't miss a new tesserae release

NewReleases is sending notifications on new releases.