Add the dashboard settings channel, wired for theme and lock
A dashboard could only ever receive as a set of tiles. This adds the dashboard itself as a receiver: `settings` maps a name to a value plus an optional binding. Unbound, the setting is simply its value — a wall panel that is always dark costs no flow. Bound, a flow drives it live and the value is the fallback. Two settings are wired: `theme` (system/light/dark) and `locked` (read-only). There is no schedule field on purpose — a node publishing to the bound message on a cron is what a schedule is here, which is the point of a channel. - `messages_for()` now walks a dashboard's bound settings as well as its widgets' bindings. Without this a paired screen is refused its own theme message, on the one surface the setting exists for; it bounds the socket too. - `locked` is gated in `usePublish`, so every control inherits it, and each control also draws itself disabled — a dead button reads as broken otherwise. The panel surface says Read-only in the corner. - The theme is a class on the dashboard's own surface, never the root: inside the app shell it must not flip the chrome. `.light` gains the tokens `.dark` already had (mirrored in the index repo) so both directions work on a subtree. - Settings bindings are type-checked from the document alone, the rule widget bindings follow, and mirrored on the server. - A bound setting is drawn on the flow canvas as a dashboard-level endpoint. - The demo's house flow now publishes `home.panel_theme`, which the demo dashboard's theme binds to: the panel goes dark after sunset, at no tile cost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tULRZJUkZsw7rMJ3h4xvu
This commit is contained in:
@@ -146,6 +146,11 @@ def process(tick, mode="comfort", away=False, setpoint=21.0):
|
||||
"indoor": round(indoor, 2),
|
||||
"outdoor": round(outdoor, 2),
|
||||
"humidity": round(humidity, 1),
|
||||
# Not a reading: what the panel on the wall should wear. The dashboard
|
||||
# binds its `theme` setting to this, which is what "switch the panel at
|
||||
# sunset" looks like here — a flow publishing, not a rule in the
|
||||
# dashboard.
|
||||
"panel_theme": "dark" if hour < 7.0 or hour >= 20.0 else "light",
|
||||
}
|
||||
'''
|
||||
|
||||
@@ -331,6 +336,8 @@ HOUSE_NODES = [
|
||||
# downstream of it is paced by this rather than by the tick.
|
||||
{"name": "outdoor", "dtype": "float", "interval": 60},
|
||||
{"name": "humidity", "dtype": "float"},
|
||||
# Twice a day at most, so it is paced rather than sent every tick.
|
||||
{"name": "panel_theme", "dtype": "str", "interval": 60},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -1214,6 +1221,18 @@ def seed_dashboard(api: Api) -> None:
|
||||
"columns": COLUMNS,
|
||||
"canvas_width": CANVAS[0],
|
||||
"canvas_height": CANVAS[1],
|
||||
# The settings channel: the dashboard itself as a receiver. Costs
|
||||
# no tile, which is why it fits a panel with all 17 rows spoken
|
||||
# for. `system` is the fallback until the flow first says
|
||||
# otherwise; `locked` is left unset, which is the static default
|
||||
# and keeps every control on the demo usable.
|
||||
"settings": {
|
||||
"theme": {
|
||||
"value": "system",
|
||||
"message": msg(HOUSE, "panel_theme"),
|
||||
"dtype": "str",
|
||||
}
|
||||
},
|
||||
"pages": PAGES,
|
||||
},
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user