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:
@@ -8,6 +8,7 @@ from fluksio.flow.dashboards import (
|
||||
DashboardStore,
|
||||
PageDef,
|
||||
SectionDef,
|
||||
SettingDef,
|
||||
WidgetDef,
|
||||
default_dashboard,
|
||||
)
|
||||
@@ -353,3 +354,62 @@ def test_a_dashboard_is_cut_into_a_sane_number_of_columns():
|
||||
for columns in (0, 49):
|
||||
with pytest.raises(ValueError):
|
||||
DashboardDef(name="house", columns=columns)
|
||||
|
||||
|
||||
def test_a_setting_refuses_a_message_it_cannot_carry():
|
||||
"""The same rule a widget binding is held to, from the document alone."""
|
||||
DashboardDef(
|
||||
name="house",
|
||||
settings={"theme": SettingDef(value="dark", message="a.b", dtype="str")},
|
||||
)
|
||||
DashboardDef(
|
||||
name="house",
|
||||
settings={"locked": SettingDef(value=False, message="a.b", dtype="bool")},
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
DashboardDef(
|
||||
name="house",
|
||||
settings={"theme": SettingDef(value="dark", message="a.b", dtype="float")},
|
||||
)
|
||||
with pytest.raises(ValueError):
|
||||
DashboardDef(
|
||||
name="house",
|
||||
settings={"locked": SettingDef(value=False, message="a.b", dtype="str")},
|
||||
)
|
||||
|
||||
|
||||
def test_an_unbound_setting_is_just_its_value():
|
||||
"""The static case, which is what makes a dark panel cost no flow.
|
||||
|
||||
No message means nothing to type-check and nothing for a panel to be
|
||||
entitled to — and a name this build does not know is left alone rather
|
||||
than refused, so an older installation reads a newer document.
|
||||
"""
|
||||
defn = DashboardDef(
|
||||
name="house",
|
||||
settings={
|
||||
"theme": SettingDef(value="dark"),
|
||||
"someday": SettingDef(value=7, message="a.b", dtype="int"),
|
||||
},
|
||||
)
|
||||
|
||||
assert defn.settings["theme"].value == "dark"
|
||||
assert defn.setting_messages == ["a.b"]
|
||||
|
||||
|
||||
def test_a_bound_setting_is_drawn_on_the_canvas(store: DashboardStore):
|
||||
"""A dashboard consuming a message is an endpoint like a tile is."""
|
||||
store.write(
|
||||
DashboardDef(
|
||||
name="house",
|
||||
settings={"theme": SettingDef(value="system", message="home.theme")},
|
||||
)
|
||||
)
|
||||
|
||||
(binding,) = store.bindings_for("home")
|
||||
assert binding["widget"] == "settings.theme"
|
||||
assert binding["type"] == "setting"
|
||||
assert binding["requires"] == ["home.theme"]
|
||||
assert not binding["provides"]
|
||||
assert store.bindings_for("other") == []
|
||||
|
||||
Reference in New Issue
Block a user