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./0is 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_4gamut, a 163200-byte 2-bpp frame from the
esp32_binrenderer, paired with thewaveshare-1085gtarget 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_widgetsused 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.icsexport: 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 athttps://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 withrotationgets 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 restorescol_offseton 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--hostdefault), 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
httpspublic 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 plainhttp. -
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
/framepoll, 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--portdefault, and the image CMD no longer passes an
explicit--portthat would override it. Under host networking, moving
Tesserae off a contested 8765 is now oneenvironment:line instead of a
command:override. An explicit--portflag 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.mdpage listing every CLI flag andTESSERAE_*
environment variable (serving, storage, secrets, logging, experiment
overrides) plus the role ofsettings.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 newseeed_ee03kind reuses theesp32_gray_binrenderer and
packs a byte-identical 1314144-byte frame. Registered withauto_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 asseeed-ee03(awaiting hardware verification).
Fixed
- Calendar widgets no longer fail with "invalid language tag: C". With the app
locale left on "system", a hostLANG/LC_ALLofC.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.Cand
POSIXvalues 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 anycalendar.*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 sameload_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 × intervallocal time (a 15-minute panel anchored at 00:05 wakes at :05 /
:20 / :35 / :50); "at set times" wakes only at a listed set ofHH:MMtimes. 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/statusresponse additionally carries the same instant as an absolute
wake_atepoch for firmware that can sleep to a wall-clock target; older firmware
ignores it and still lands close vianext_poll_s. -
The dev widget previews can be rendered in a chosen language.
/_test/widgetsand
/_test/previewnow carry an English / French locale picker; it adds?locale=<tag>to the
render so a contributor can check a widget's translated strings and itsIntldate / 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 fromIntl
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 sameIntl+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 noAccept-Encodingheader, 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 foridentity
and decodes anyContent-Encodingthe 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'sminlengthcheck) 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 plainlandscape; 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. Thexteink_x4kind's notes spell out that the two firmware paths need
different orientation values (CrossInk keeps theportrait_flippeddefault; the native
xteink-x4firmware wants the device set to plainlandscape), and that the native
build drives the SSD1677 panel revision only, while CrossInk auto-detects the later
UC8179/UC8279 revisions.