Build dashboards you can actually look at and press

Widgets bind to a message name and read it live off the socket the editor
already had — lifted out of the flow editor so a dashboard route gets the
same values, which also gives the home page live data for free.

The input widgets close the loop the other way: a slider publishes into
the graph and whatever consumes that message runs. Verified end to end in
the running app — moving a slider set a flow input, and the stat bound to
what the flow computed from it followed.

View mode is plain CSS grid. A wall panel that only displays should not
download the code that lets someone drag things around, and it now does
not. Editing is a widget picker, a per-widget width control and a
settings card fed by the message catalog.

No new dependencies: the slider is a range input, the gauge is an arc,
and the markdown is a five-line subset. Charts are the one widget still
missing — they need a charting library and the chart tokens the design
guidelines reserved — so they are stored and validated but not offered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011LF61rxW1FG5YCD2J9YqjY
This commit is contained in:
2026-08-16 09:33:02 +02:00
co-authored by Claude Fable 5
parent 895bd89b2b
commit be6d4fc13c
15 changed files with 1388 additions and 7 deletions
+42
View File
@@ -19,7 +19,9 @@ import { Route as LayoutIndexRouteImport } from './routes/_layout/index'
import { Route as OauthAuthorizeRouteImport } from './routes/oauth.authorize'
import { Route as LayoutSettingsRouteImport } from './routes/_layout/settings'
import { Route as LayoutAdminRouteImport } from './routes/_layout/admin'
import { Route as LayoutDashboardsIndexRouteImport } from './routes/_layout/dashboards/index'
import { Route as CanvasFlowsIndexRouteImport } from './routes/_canvas/flows/index'
import { Route as LayoutDashboardsNameRouteImport } from './routes/_layout/dashboards/$name'
import { Route as CanvasFlowsFlowNameRouteImport } from './routes/_canvas/flows/$flowName'
const SignupRoute = SignupRouteImport.update({
@@ -70,11 +72,21 @@ const LayoutAdminRoute = LayoutAdminRouteImport.update({
path: '/admin',
getParentRoute: () => LayoutRoute,
} as any)
const LayoutDashboardsIndexRoute = LayoutDashboardsIndexRouteImport.update({
id: '/dashboards/',
path: '/dashboards/',
getParentRoute: () => LayoutRoute,
} as any)
const CanvasFlowsIndexRoute = CanvasFlowsIndexRouteImport.update({
id: '/flows/',
path: '/flows/',
getParentRoute: () => CanvasRoute,
} as any)
const LayoutDashboardsNameRoute = LayoutDashboardsNameRouteImport.update({
id: '/dashboards/$name',
path: '/dashboards/$name',
getParentRoute: () => LayoutRoute,
} as any)
const CanvasFlowsFlowNameRoute = CanvasFlowsFlowNameRouteImport.update({
id: '/flows/$flowName',
path: '/flows/$flowName',
@@ -91,7 +103,9 @@ export interface FileRoutesByFullPath {
'/settings': typeof LayoutSettingsRoute
'/oauth/authorize': typeof OauthAuthorizeRoute
'/flows/$flowName': typeof CanvasFlowsFlowNameRoute
'/dashboards/$name': typeof LayoutDashboardsNameRoute
'/flows/': typeof CanvasFlowsIndexRoute
'/dashboards/': typeof LayoutDashboardsIndexRoute
}
export interface FileRoutesByTo {
'/': typeof LayoutIndexRoute
@@ -103,7 +117,9 @@ export interface FileRoutesByTo {
'/settings': typeof LayoutSettingsRoute
'/oauth/authorize': typeof OauthAuthorizeRoute
'/flows/$flowName': typeof CanvasFlowsFlowNameRoute
'/dashboards/$name': typeof LayoutDashboardsNameRoute
'/flows': typeof CanvasFlowsIndexRoute
'/dashboards': typeof LayoutDashboardsIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
@@ -118,7 +134,9 @@ export interface FileRoutesById {
'/oauth/authorize': typeof OauthAuthorizeRoute
'/_layout/': typeof LayoutIndexRoute
'/_canvas/flows/$flowName': typeof CanvasFlowsFlowNameRoute
'/_layout/dashboards/$name': typeof LayoutDashboardsNameRoute
'/_canvas/flows/': typeof CanvasFlowsIndexRoute
'/_layout/dashboards/': typeof LayoutDashboardsIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
@@ -132,7 +150,9 @@ export interface FileRouteTypes {
| '/settings'
| '/oauth/authorize'
| '/flows/$flowName'
| '/dashboards/$name'
| '/flows/'
| '/dashboards/'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
@@ -144,7 +164,9 @@ export interface FileRouteTypes {
| '/settings'
| '/oauth/authorize'
| '/flows/$flowName'
| '/dashboards/$name'
| '/flows'
| '/dashboards'
id:
| '__root__'
| '/_canvas'
@@ -158,7 +180,9 @@ export interface FileRouteTypes {
| '/oauth/authorize'
| '/_layout/'
| '/_canvas/flows/$flowName'
| '/_layout/dashboards/$name'
| '/_canvas/flows/'
| '/_layout/dashboards/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
@@ -243,6 +267,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof LayoutAdminRouteImport
parentRoute: typeof LayoutRoute
}
'/_layout/dashboards/': {
id: '/_layout/dashboards/'
path: '/dashboards'
fullPath: '/dashboards/'
preLoaderRoute: typeof LayoutDashboardsIndexRouteImport
parentRoute: typeof LayoutRoute
}
'/_canvas/flows/': {
id: '/_canvas/flows/'
path: '/flows'
@@ -250,6 +281,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof CanvasFlowsIndexRouteImport
parentRoute: typeof CanvasRoute
}
'/_layout/dashboards/$name': {
id: '/_layout/dashboards/$name'
path: '/dashboards/$name'
fullPath: '/dashboards/$name'
preLoaderRoute: typeof LayoutDashboardsNameRouteImport
parentRoute: typeof LayoutRoute
}
'/_canvas/flows/$flowName': {
id: '/_canvas/flows/$flowName'
path: '/flows/$flowName'
@@ -277,12 +315,16 @@ interface LayoutRouteChildren {
LayoutAdminRoute: typeof LayoutAdminRoute
LayoutSettingsRoute: typeof LayoutSettingsRoute
LayoutIndexRoute: typeof LayoutIndexRoute
LayoutDashboardsNameRoute: typeof LayoutDashboardsNameRoute
LayoutDashboardsIndexRoute: typeof LayoutDashboardsIndexRoute
}
const LayoutRouteChildren: LayoutRouteChildren = {
LayoutAdminRoute: LayoutAdminRoute,
LayoutSettingsRoute: LayoutSettingsRoute,
LayoutIndexRoute: LayoutIndexRoute,
LayoutDashboardsNameRoute: LayoutDashboardsNameRoute,
LayoutDashboardsIndexRoute: LayoutDashboardsIndexRoute,
}
const LayoutRouteWithChildren =