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:
@@ -435,6 +435,13 @@ export const DashboardDef_InputSchema = {
|
||||
type: 'array',
|
||||
title: 'Pages'
|
||||
},
|
||||
settings: {
|
||||
additionalProperties: {
|
||||
'$ref': '#/components/schemas/SettingDef'
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Settings'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
@@ -496,6 +503,13 @@ export const DashboardDef_OutputSchema = {
|
||||
type: 'array',
|
||||
title: 'Pages'
|
||||
},
|
||||
settings: {
|
||||
additionalProperties: {
|
||||
'$ref': '#/components/schemas/SettingDef'
|
||||
},
|
||||
type: 'object',
|
||||
title: 'Settings'
|
||||
},
|
||||
version: {
|
||||
type: 'integer',
|
||||
title: 'Version',
|
||||
@@ -2640,6 +2654,36 @@ export const SeriesPointSchema = {
|
||||
title: 'SeriesPoint'
|
||||
} as const;
|
||||
|
||||
export const SettingDefSchema = {
|
||||
properties: {
|
||||
value: {
|
||||
title: 'Value'
|
||||
},
|
||||
message: {
|
||||
type: 'string',
|
||||
title: 'Message',
|
||||
default: ''
|
||||
},
|
||||
dtype: {
|
||||
type: 'string',
|
||||
title: 'Dtype',
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
type: 'object',
|
||||
title: 'SettingDef',
|
||||
description: `One dashboard-wide setting: a value, and optionally where it comes from.
|
||||
|
||||
Unbound — no \`\`message\`\` — the setting is simply \`\`value\`\`, which is what
|
||||
makes a panel that is always dark cost no flow at all. Bound, a flow drives
|
||||
it live and \`\`value\`\` is the fallback: what the dashboard uses until
|
||||
something arrives, and whenever the message is silent.
|
||||
|
||||
A schedule is not a third case. A node publishing to the bound message on a
|
||||
cron *is* the schedule, which is the whole reason this is a channel rather
|
||||
than a switching rule per setting.`
|
||||
} as const;
|
||||
|
||||
export const ShareRequestSchema = {
|
||||
properties: {
|
||||
lib_name: {
|
||||
|
||||
@@ -117,6 +117,9 @@ export type DashboardDef_Input = {
|
||||
canvas_height?: number;
|
||||
icon?: string;
|
||||
pages?: Array<PageDef_Input>;
|
||||
settings?: {
|
||||
[key: string]: SettingDef;
|
||||
};
|
||||
version?: number;
|
||||
has_draft?: boolean;
|
||||
};
|
||||
@@ -132,6 +135,9 @@ export type DashboardDef_Output = {
|
||||
canvas_height?: number;
|
||||
icon?: string;
|
||||
pages?: Array<PageDef_Output>;
|
||||
settings?: {
|
||||
[key: string]: SettingDef;
|
||||
};
|
||||
version?: number;
|
||||
has_draft?: boolean;
|
||||
};
|
||||
@@ -919,6 +925,24 @@ export type SeriesPoint = {
|
||||
avg_lag_ms: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* One dashboard-wide setting: a value, and optionally where it comes from.
|
||||
*
|
||||
* Unbound — no ``message`` — the setting is simply ``value``, which is what
|
||||
* makes a panel that is always dark cost no flow at all. Bound, a flow drives
|
||||
* it live and ``value`` is the fallback: what the dashboard uses until
|
||||
* something arrives, and whenever the message is silent.
|
||||
*
|
||||
* A schedule is not a third case. A node publishing to the bound message on a
|
||||
* cron *is* the schedule, which is the whole reason this is a channel rather
|
||||
* than a switching rule per setting.
|
||||
*/
|
||||
export type SettingDef = {
|
||||
value?: unknown;
|
||||
message?: string;
|
||||
dtype?: string;
|
||||
};
|
||||
|
||||
export type ShareRequest = {
|
||||
lib_name: string;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user