import { createContext, useContext } from "react" import type { HistoryPoint } from "@/client" import { type LiveValue, useLiveValue, useLiveValues, } from "@/components/Flow/liveStore" /** One member's curve for a bound message: a run, later a replayed day. */ export type ContextLine = { label: string /** `ts` carries whatever the x axis is — a step for a run, a moment for a * replay. Which of the two it is, is `xTime` on the context. */ points: HistoryPoint[] } /** * Where a dashboard's widgets read from, when it is not the live engine. * * A dashboard binds its widgets to message names. Normally those resolve * against what the engine is holding right now, which is what makes a wall * panel a wall panel. A data context puts something else behind the same * names, so the page someone already built to watch a training run live is * also the page that shows a finished one — no second dashboard, no widget * that knows what a run is. * * `kind` is for the bar at the top to name what is being shown. Widgets must * not switch on it: the point of this seam is that "three runs" and "last * Tuesday, replayed" are the same shape, and Labs is the second one. * * Deliberately plain data and two lookups rather than hooks, so a provider is * free to re-render with a fresh object whenever its data moves — a run * finishing, a replay ticking — without every consumer subscribing to * something of its own. * * What a consumer may not assume: that x is a time (`xTime`), that there is * exactly one member (`lines` returns as many as there are), or that the data * is settled (`pending`). */ export type DataContext = { kind: string /** What is being shown, in words: "3 runs of demo_training". */ label: string /** Nothing here can be published to; the input widgets go quiet. */ readOnly: boolean /** Whether the x values are moments. False for a run, whose x is a step. */ xTime: boolean pending: boolean /** The single value a stat or a gauge should show for this name. */ value: (name: string) => LiveValue | undefined /** One line per member for this name, or undefined if it carries none. */ lines: (name: string) => ContextLine[] | undefined } const DataCtx = createContext(null) export const DataContextProvider = DataCtx.Provider /** The context a widget is being drawn in, or null on a live dashboard. */ export const useDataContext = () => useContext(DataCtx) /** * What a widget bound to this name should show. * * The one reading every value widget goes through, so putting a dashboard in * a context is a change in one place rather than in each tile. Both hooks are * called either way — React counts them — and which answer is used is decided * afterwards. * * Not `useLiveValue` itself: a dashboard's own settings read live values too, * and opening a page against a run must not change what look or theme it is * drawn in. */ export function useBoundValue(name: string | undefined) { const context = useDataContext() const live = useLiveValue(name) return context && name ? context.value(name) : live } /** The same, for a widget that binds several names at once. */ export function useBoundValues(names: string[]) { const context = useDataContext() const live = useLiveValues(names) return context ? names.map((name) => context.value(name)) : live }