@devicechain/dashboards 0.19.0 → 0.20.0-rc.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/command-status.ts", "../src/types.ts", "../src/definition.ts", "../src/bindings.ts", "../src/context.ts", "../src/slots.ts", "../src/candidates.ts", "../src/entity-lister.ts", "../src/queries.ts", "../src/hub.ts", "../src/internal/alarm-doc.ts", "../src/internal/command-doc.ts", "../src/internal/location-doc.ts", "../src/internal/measurement-doc.ts", "../src/synthetic.ts", "../src/editor-model.ts", "../src/resolver.ts", "../src/history.ts"],
4
- "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The command-delivery lifecycle vocabulary, as the frontend sees it.\n//\n// \uD83D\uDD34 THIS IS THE ONE COPY. It lives in `dashboards` rather than `widgets` because\n// `widgets` depends on `dashboards` (never the reverse), so this is the lowest layer\n// both the widget package and the console can reach. It previously existed as three\n// hand-maintained sets \u2014 the console's DeviceCommandsPanel, the widgets' command-status\n// helper, and an ad-hoc expression in the synthetic preview \u2014 which is exactly how the\n// console kept offering a Cancel button on an already-cancelled command: a status was\n// added to the service and only some of the copies learned about it.\n//\n// The service declares no GraphQL enum for status, so it crosses the wire as a plain\n// string and an UNRECOGNIZED value must stay survivable: nothing here throws on one. What\n// an unknown status MEANS differs per question, though \u2014 see the two predicates at the\n// bottom, which answer for it in opposite directions on purpose.\n//\n// \uD83D\uDD34 \"IN FLIGHT\" AND \"CANCELLABLE\" ARE NOT THE SAME SET, and reading one off the other is\n// the bug this file is shaped to prevent. SENT is in flight (the device has not answered\n// yet) but NOT cancellable: the command is already at the device, and letting the platform\n// call it off would only make it discard the real answer that is on its way back.\n\n// Every lifecycle state the command-delivery service can persist. Ordered as a command\n// travels: the non-terminal states first, then the terminal ones.\nexport const COMMAND_STATUSES = [\n // \u2500\u2500 Non-terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Accepted; awaiting its first dispatch decision. Genuinely transient.\n 'QUEUED',\n // The platform is deliberately WITHHOLDING dispatch because the device is known\n // absent. This is where an offline fleet's backlog accumulates \u2014 it can sit for days,\n // and it is the honest answer to \"why hasn't my command arrived?\".\n 'HELD',\n // Dispatched toward a device believed reachable; awaiting its response.\n 'SENT',\n // Published, and it went NOWHERE: the device turned out not to be reachable, so the\n // transport had nothing to hand it to. The platform still holds the command and will\n // deliver it when the device next wakes. Distinct from HELD (where dispatch was never\n // attempted, because the device was already known absent) and from TIMEOUT (where the\n // command reached a device that then said nothing).\n 'PARKED',\n // \u2500\u2500 Terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // The device answered.\n 'SUCCESSFUL',\n 'FAILED',\n // Dispatched, never answered in time.\n 'TIMEOUT',\n // The TTL elapsed before it ever went out.\n 'EXPIRED',\n // An operator or tenant called it off. Distinct from EXPIRED because it is a\n // different ACTOR, not a different outcome. Cancellation used to write EXPIRED, so\n // BOTH values appear in real data \u2014 historical rows are not backfilled.\n 'CANCELLED',\n] as const;\n\nexport type CommandStatus = (typeof COMMAND_STATUSES)[number];\n\n// The states a command can never leave: no further transition is permitted, so nothing\n// more will happen to it. Terminal implies not cancellable; the converse does NOT hold.\nexport const TERMINAL_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'SUCCESSFUL',\n 'FAILED',\n 'TIMEOUT',\n 'EXPIRED',\n 'CANCELLED',\n]);\n\n// The states from which the platform will still accept a cancellation \u2014 a POSITIVE list,\n// mirroring the service's own gate. All three are states in which the command has not\n// reached the device: QUEUED (not yet dispatched), HELD (dispatch withheld), PARKED\n// (published into a void and waiting for the device to wake). Cancelling any of them\n// takes back something nobody else is holding.\n//\n// SENT is deliberately absent. It is in flight, so !isTerminalCommandStatus('SENT') is\n// true \u2014 which is exactly why a cancel control must not be gated on that expression.\nexport const CANCELLABLE_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'QUEUED',\n 'HELD',\n 'PARKED',\n]);\n\n// \uD83D\uDD34 THE TWO PREDICATES ANSWER FOR AN UNRECOGNIZED STATUS IN OPPOSITE DIRECTIONS, on\n// purpose. Status crosses the wire as a plain string with no GraphQL enum behind it, so a\n// value this build has never heard of is always reachable \u2014 a newer service, a hand-edited\n// row \u2014 and each question has its own safe side:\n//\n// isTerminalCommandStatus negative membership \u21D2 unknown reads NON-terminal, i.e.\n// STILL IN FLIGHT. A status the service adds is one it added\n// because something is still happening to the command, and\n// the cost of guessing wrong is only a row that lingers in an\n// \"outstanding\" list instead of settling.\n//\n// isCancellableCommandStatus positive membership \u21D2 unknown reads NOT CANCELLABLE. This\n// file used to argue the other way \u2014 that refusing to offer\n// cancel for a state we do not recognize would strand a live\n// command \u2014 and that argument is now false. The service gates\n// cancellation on the same positive list, and \uD83D\uDD34 OUTSIDE THAT\n// LIST IT DOES NOT REFUSE: it SUCCEEDS and returns the command\n// UNCHANGED. So a Cancel button offered on an unknown status is\n// a no-op that answers the click with a SUCCESS TOAST for a\n// command nobody cancelled \u2014 a worse outcome than an error,\n// because the operator has no way to tell. The command is\n// stranded either way; this way we do not also claim otherwise.\n// A host that offers the button anyway must therefore check the\n// status that CAME BACK, not the fact that the call resolved.\nexport function isTerminalCommandStatus(status: string): boolean {\n return TERMINAL_COMMAND_STATUSES.has(status);\n}\n\n// isCancellableCommandStatus reports whether a cancel request would be accepted. Gate every\n// cancel control on THIS, never on !isTerminalCommandStatus \u2014 the two differ by SENT, and\n// by every status added after this build shipped.\nexport function isCancellableCommandStatus(status: string): boolean {\n return CANCELLABLE_COMMAND_STATUSES.has(status);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The dashboard-definition contract (ADR-039). These types are the canonical\n// shape of the JSON document that dashboard-management stores opaquely \u2014 the\n// backend only validates it is well-formed JSON, so this package owns the shape.\n// Kept deliberately fluid pre-GA. See research/dashboard-phase1-design-2026-07.md.\n\n// ---- Datasource selectors ---------------------------------------------------\n//\n// A selector is a tagged union (discriminated on `kind`). The Hub resolves\n// `device`, `anchor`, and `slot` (the last via its binding manifest); the\n// remaining kinds are reserved \u2014 present so definitions stay forward-compatible,\n// but the Hub rejects them until implemented.\n\n// ---- Location series selection (ADR-078 decision 9) -------------------------\n//\n// A selector's `measurements: string[]` names NAMED SCALAR SERIES, and a position is\n// not one of those: it is an atomic triple on its own event class and its own\n// hypertable, deliberately never a metric. Naming a location through `measurements`\n// would therefore be a lie the whole way down \u2014 the hub would resolve it against the\n// measurement stream, which carries no coordinates at all.\n//\n// So a selector names a location series in its own field. The vocabulary is CLOSED\n// rather than free text: a device has exactly one position track, so there is nothing\n// to disambiguate by name, and an open string would invite an author to type a\n// measurement name here and get silence.\n//\n// 'latest' \u2014 the last-known position, read from device-state's O(1) projection.\n//\n// A second member ('track', the position history from event-management) is the shape\n// this exists to leave room for; the map widget reads 'latest' only.\nexport type LocationSeries = 'latest';\n\n// The location series a selector names. An OBJECT rather than a bare string so a\n// future member can carry its own parameters (a track's window) without changing\n// every selector again.\nexport interface LocationSelection {\n series: LocationSeries;\n}\n\n// device \u2014 one device's measurements (latest-card, gauge, single-series chart), and/or\n// its position when the selector names a location series.\nexport interface DeviceSelector {\n kind: 'device';\n deviceToken: string;\n measurements: string[];\n // Additive and OPTIONAL, deliberately: every dashboard definition stored before the\n // location channel existed carries no `location`, and must keep parsing and\n // rendering exactly as it did. Absence means \"this selector names no location\n // series\", which is the correct reading of every one of them.\n location?: LocationSelection;\n}\n\n// The dimension a dashboard aggregates over \u2014 a tracked relationship to a\n// customer / area / asset. An anchor is not an opaque alias: it is a graph edge\n// the platform already resolves events against.\nexport interface AnchorTarget {\n relationship: string;\n targetType: 'customer' | 'area' | 'asset';\n targetToken: string;\n}\n\nexport interface AnchorAggregation {\n window: string; // e.g. '1m'\n fn: 'avg' | 'min' | 'max' | 'sum' | 'count';\n}\n\n// anchor \u2014 an anchor dimension aggregated over its member devices.\nexport interface AnchorSelector {\n kind: 'anchor';\n anchor: AnchorTarget;\n measurements: string[];\n location?: LocationSelection;\n // RESERVED (Phase 2): server-side aggregation of the member devices into one\n // series. Phase 1 resolves an anchor to its member devices and streams each\n // one's raw samples; the Hub does NOT read this field yet. Present so a\n // definition authored against the final shape still round-trips.\n aggregation?: AnchorAggregation;\n}\n\n// ---- Reserved selectors (Phase 2) -------------------------------------------\n// Schema-valid and part of the union so a stored definition never fails to parse,\n// but the Hub throws \"not supported yet\" until Phase 2 wires them.\n\nexport interface DevicesSelector {\n kind: 'devices';\n deviceTokens: string[];\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport interface RelatedTraversalSelector {\n kind: 'relatedTraversal';\n from: string;\n relationship: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// slot \u2014 a NAMED reference into the dashboard's `slots` section, resolved to a\n// concrete entity at MOUNT by the host's binding manifest (ADR-039 runtime\n// binding). This makes a definition a reusable TEMPLATE: the widget names the\n// entity role (`slot`) and the measurements it wants, and two mounts of the same\n// definition can bind the slot to two different devices. The Hub resolves it via\n// the binding manifest (a slot's defaultBinding, overridable by the host); an\n// unbound slot renders as an empty placeholder.\nexport interface SlotSelector {\n kind: 'slot';\n slot: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport type DatasourceSelector =\n | DeviceSelector\n | AnchorSelector\n | DevicesSelector\n | RelatedTraversalSelector\n | SlotSelector;\n\n// ---- Canvas + widgets -------------------------------------------------------\n\n// The built-in widget types. Kept as a runtime array so parse/validation and the\n// widget registry share one source of truth (WidgetType is derived from it).\n//\n// Widgets fall into data CHANNELS (see WIDGET_CHANNEL in @devicechain/widgets): the\n// measurement widgets stream telemetry samples; the alarm widgets consume the raised-\n// alarm surface (ADR-041) via the hub's alarm channel; the control widget (command-\n// button) issues commands and tracks their delivery lifecycle via the command channel;\n// the map widget reads device positions via the location channel (ADR-078 decision 9).\n// A widget's channel decides which hook/registry the renderer binds it through.\nexport const WIDGET_TYPES = [\n 'timeseries-chart',\n 'latest-card',\n 'gauge',\n 'table',\n 'label',\n 'image',\n 'alarm-table',\n 'alarm-count',\n 'command-button',\n 'entity-selector',\n 'map',\n] as const;\n\nexport type WidgetType = (typeof WIDGET_TYPES)[number];\n\n// Grid placement + z-order \u2014 CSS-Grid-native layout (ADR-039 amendment 2026-07-08).\n// A widget is placed by SPAN on the canvas grid: `col`/`row` are its 0-based start\n// line and `colSpan`/`rowSpan` how many tracks it covers \u2014 mapping to\n// `grid-column: col+1 / span colSpan` (rows likewise). Because the columns are\n// fractional (`repeat(columns, 1fr)`), the same box fills whatever width its\n// container gives it \u2014 no pixel width baked into the definition. Layering is native\n// via `z`; `offset` is a signed-pixel escape hatch for pixel-perfect nudge/overlap\n// beyond the grid lines (Derek's \"margin and -margin\").\nexport interface WidgetBox {\n col: number;\n colSpan: number;\n row: number;\n rowSpan: number;\n z: number;\n offset?: { x: number; y: number };\n}\n\n// Per-breakpoint placement, keyed by breakpoint name. 'base' is required; a\n// widget omitting a breakpoint inherits its 'base' box.\nexport type WidgetLayout = Record<string, WidgetBox>;\n\nexport interface WidgetInstance {\n id: string;\n type: WidgetType;\n layout: WidgetLayout;\n // label/image widgets carry no datasource.\n datasource?: DatasourceSelector;\n // Widget-specific options (series colors, unit, thresholds, \u2026). Owned by the\n // widget package; opaque here.\n options?: Record<string, unknown>;\n}\n\nexport interface CanvasBackground {\n color?: string | null;\n imageUrl?: string | null;\n}\n\n// The canvas grid (ADR-039 amendment 2026-07-08). `columns` is high-resolution\n// (24\u201348) so span placement stays near-free, which is what makes the canvas-first\n// layout possible rather than a fixed grid; `gap` is the gutter, one value or a {row,col} pair;\n// `rowHeight` is the fixed pixel height of each implicit grid row. Columns are\n// fluid (`repeat(columns, 1fr)`) so the board fills its container width; rows are\n// fixed-px (the `aspect` mode \u2014 rowHeight = colWidth \u00D7 ratio \u2014 is the documented\n// fast-follow).\nexport interface CanvasGrid {\n columns: number;\n gap: number | { row: number; col: number };\n rowHeight: number;\n}\n\n// How the grid is sized into its container \u2014 a MOUNT/embed knob the host may\n// override (renderer `sizing` prop). `fill`: the grid fills the container's width\n// (the default; the \"fit into an area\" case). `{width}`: a fixed-px-wide container\n// the fluid grid adjusts within. `{height}`: a fixed-px-tall container (rows scroll).\nexport type CanvasSizing = 'fill' | { width: number } | { height: number };\n\n// Breakpoint name \u2192 min viewport width in px. 'base' is required.\nexport type Breakpoints = Record<string, number>;\n\nexport interface Canvas {\n background?: CanvasBackground;\n grid: CanvasGrid;\n sizing: CanvasSizing;\n breakpoints: Breakpoints;\n}\n\n// A concrete entity a slot resolves to \u2014 a device (by token) or an anchor target.\n// This is the ENTITY only; the measurement names stay on the widget's SlotSelector\n// (so a shared slot can feed different widgets different measurements). A slot's\n// default binding lives in its SlotDefinition; the host's mount-time manifest can\n// override it (see effectiveBindings).\nexport type SlotBinding =\n | { kind: 'device'; deviceToken: string }\n | { kind: 'anchor'; anchor: AnchorTarget };\n\n// A slot's dependency on a PARENT slot \u2014 the scoped-slot context hierarchy (ADR-039\n// selection amendment). A scoped slot resolves RELATIVE to its parent's current\n// binding: `parent` names an anchor-typed slot whose member devices are this slot's\n// candidate set, and `strategy` picks among them:\n// 'first' \u2014 auto-bind the parent's first member (ordered by token); zero members \u21D2\n// unbound. The slot follows the parent with no user input.\n// 'manual' \u2014 keep the current pick iff it is still a member of the (possibly changed)\n// parent, else reset to unbound; a picker offers only the parent's members.\n// A slot with no `scope` is a ROOT context (the top-level entity, e.g. a building). The\n// relationship used to enumerate members is the parent anchor's own `relationship` (no\n// separate field). Single parent \u21D2 the slots form a forest (no diamonds), so the cascade\n// order is well-defined. Replaces the reserved `entityFromState` selector.\nexport interface SlotScope {\n parent: string;\n strategy: 'first' | 'manual';\n}\n\n// A named entity role a dashboard declares and its widgets reference via a\n// SlotSelector. The host's binding manifest maps each slot to a concrete device or\n// anchor at mount; `defaultBinding` is the slot's own binding (set by the authoring\n// tenant) used when the host supplies no override \u2014 so a dashboard renders\n// immediately for its author AND is export-ready as a template (strip the defaults,\n// the host rebinds).\nexport interface SlotDefinition {\n type: 'device' | 'anchor';\n // Human-readable name shown in the binding UI, e.g. 'Primary thermostat'.\n label?: string;\n // The slot's default entity binding (the author's tenant); a host manifest overrides.\n // For a SCOPED slot this is a FALLBACK only \u2014 the cascade (resolveContextBindings)\n // derives the effective binding from the parent and always supersedes it (a `first`\n // slot ignores it; a `manual` slot uses it as the initial pick, kept iff a member).\n defaultBinding?: SlotBinding;\n // Optional dependency on a parent slot (the context hierarchy). Absent = a root context.\n scope?: SlotScope;\n}\n\n// The entity kinds a root context-selector can list (the anchor target types + a bare\n// device). A scoped child selector does NOT use this \u2014 its candidates are the parent\n// anchor's member devices, enumerated via the DeviceResolver, not a list query.\nexport type EntityListKind = 'device' | 'customer' | 'area' | 'asset';\n\n// EntityCandidateLister lists the tenant's entities of one kind (token + optional name),\n// backing a ROOT context-selector's candidate set. Injected by the host (createEntityLister)\n// so the widgets/dashboards packages carry no extra device-management coupling beyond the\n// one place (resolver.ts) that already owns it. A flat list \u2014 nested customer\u2192area\u2192asset\n// tree picking is deferred.\nexport type EntityCandidateLister = (\n kind: EntityListKind,\n) => Promise<Array<{ token: string; name?: string | null }>>;\n\n// One option a context/entity-selector widget offers: the binding it would set, a display\n// label, and whether it is the slot's current pick. Produced by resolveSlotCandidates.\nexport interface SelectionCandidate {\n binding: SlotBinding;\n label: string;\n selected: boolean;\n}\n\n// SelectionTarget is one view-driven selection: bind slot `slot` to `binding` (a device\n// drill from an alarm originator, a context-selector pick). The host accumulates these\n// into a selection overlay that the cascade resolves on top of the slot defaults. Named\n// its TARGET SLOT (no channels \u2014 the slot IS the channel; the cascade fans a parent\n// change out to its scoped children).\nexport interface SelectionTarget {\n slot: string;\n binding: SlotBinding;\n}\n\nexport interface DashboardDefinition {\n schemaVersion: number;\n title: string;\n canvas: Canvas;\n widgets: WidgetInstance[];\n // Named entity roles bound at mount (ADR-039 runtime binding). Optional \u2014 absent\n // on a dashboard that uses no slots.\n slots?: Record<string, SlotDefinition>;\n}\n\n// ---- Live telemetry ---------------------------------------------------------\n\n// A live measurement sample delivered to a widget. Mirrors event-management's\n// MeasurementEvent; `deviceToken` is the device token the event carries (the\n// value measurementStream is keyed on, per ADR-044).\nexport interface MeasurementSample {\n id: string;\n deviceToken: string;\n eventType: number;\n occurredTime: string | null;\n name: string;\n value: number | null;\n classifier: string | null;\n}\n\n// A raised alarm as an alarm widget sees it (ADR-041). Mirrors device-management's\n// stored Alarm row (the source of truth), NOT the transient AlarmEvent envelope \u2014 the\n// hub's alarm channel treats the live stream as a reconcile trigger and re-reads the\n// authoritative rows via the alarms query. `token` is the stable alarm token (the row\n// key + the ack/clear handle); `originatorToken` is the device token when the\n// originator is a device (null otherwise). Kept decoupled from the device-management\n// GraphQL types so the widget layer carries no service coupling.\nexport interface AlarmRow {\n token: string;\n originatorType: string;\n originatorToken: string | null;\n alarmKey: string;\n metricKey: string;\n state: string; // ACTIVE | CLEARED\n acknowledged: boolean;\n severity: string; // CRITICAL | MAJOR | MINOR | WARNING | INDETERMINATE\n raisedTime: string | null;\n clearedTime: string | null;\n acknowledgedTime: string | null;\n acknowledgedBy: string | null;\n lastValue: number | null;\n}\n\n// ---- Locations (location channel) -------------------------------------------\n\n// A device's position as a map widget sees it. Mirrors device-state's LatestLocation\n// projection row (the O(1) \"where is it now\" answer, not a scan of the history), kept\n// decoupled from the device-state GraphQL types so the widget layer carries no service\n// coupling \u2014 the same rule the alarm and command rows follow.\n//\n// \uD83D\uDD34 EVERY OPTIONAL IS NULLABLE AND ABSENT IS NOT ZERO. A device reports what its\n// receiver knows: a fix with no heading has not reported due north, one with no speed\n// has not reported stationary, one with no elevation has not reported sea level.\n// Rendering any of those as 0 would invent a fact the platform deliberately declined\n// to store, and the operator could not tell the invented value from a real one. Every\n// consumer tests `!= null` (which admits a genuine 0 and rejects an absence) and shows\n// NOTHING for an absent field.\n//\n// latitude/longitude are typed nullable because the projection's columns are \u2014 a row\n// is never written without them, but the schema does not promise it, and a consumer\n// that assumed non-null would place a marker at (0, 0), the Gulf of Guinea, rather\n// than declining to place one at all.\n//\n// Units are fixed platform-wide and never per-device: WGS84 decimal degrees;\n// elevation and accuracy in metres; speed in metres per second; heading in degrees\n// clockwise from true north, [0, 360).\nexport interface LocationSample {\n id: string;\n deviceToken: string;\n latitude: number | null;\n longitude: number | null;\n elevation: number | null;\n accuracy: number | null;\n speed: number | null;\n heading: number | null;\n occurredTime: string | null;\n}\n\n// ---- Commands (control channel) ---------------------------------------------\n\n// A device command as the command-button widget sees it (command-delivery). Mirrors\n// the stored Command row: `token` is the client-minted dispatch id (also the cancel\n// handle); `status` carries the lifecycle state as a plain string \u2014 the service\n// declares no GraphQL enum, which is also why an unrecognized value has to stay\n// survivable here. Kept decoupled from command-delivery's GraphQL types so the widget\n// layer carries no service coupling.\n//\n// The lifecycle, in full (see ./command-status for the machine-readable copy):\n//\n// non-terminal QUEUED accepted, awaiting its first dispatch decision\n// HELD dispatch deliberately withheld \u2014 the device is known absent,\n// so the command waits rather than going into the void; this is\n// where an offline fleet's backlog sits, possibly for days\n// SENT dispatched toward a reachable device, awaiting its response\n// PARKED published, but the device was not reachable, so it went\n// nowhere; the platform still holds it and delivers it when the\n// device wakes\n// terminal SUCCESSFUL / FAILED the device answered\n// TIMEOUT dispatched, never answered\n// EXPIRED TTL elapsed before it ever went out\n// CANCELLED an operator or tenant called it off\n//\n// EXPIRED and CANCELLED are separate ACTORS reaching the same dead end, not two names\n// for one thing. Cancellation used to write EXPIRED and old rows were never backfilled,\n// so both values legitimately appear in live data.\n//\n// Non-terminal is NOT the same as cancellable \u2014 SENT is the first and not the second (see\n// ./command-status). A surface offering a cancel control must ask\n// isCancellableCommandStatus rather than negating isTerminalCommandStatus.\nexport interface CommandRow {\n token: string;\n name: string;\n // QUEUED | HELD | SENT | PARKED | SUCCESSFUL | FAILED | TIMEOUT | EXPIRED | CANCELLED\n status: string;\n payload: string | null; // request JSON (as issued)\n responsePayload: string | null; // device response JSON\n error: string | null;\n queuedTime: string | null;\n sentTime: string | null;\n respondedTime: string | null;\n}\n\n// The scalar value types a command parameter can carry, reusing device-management's\n// MetricDataType vocabulary (ADR-016).\nexport type CommandParamDataType = 'DOUBLE' | 'INT' | 'BOOLEAN' | 'STRING';\n\n// One descriptor in a CommandDefinition's parameter schema (ADR-043). The console\n// bakes the selected command's parsed schema into the widget's options at author time;\n// the command-button widget renders it as a typed form. A SCALAR parameter is a single\n// typed value (dataType + optional unit/bounds/enum/default); an OBJECT parameter nests\n// a child `parameters` list. `kind` absent means SCALAR. Deliberately structural (no\n// device-management import) so the widget package stays decoupled.\nexport interface CommandParameter {\n name: string;\n description?: string;\n kind?: 'SCALAR' | 'OBJECT';\n dataType?: CommandParamDataType;\n unit?: string;\n required?: boolean;\n default?: string | null;\n minValue?: number | null;\n maxValue?: number | null;\n enum?: string[];\n parameters?: CommandParameter[];\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Load-time helpers for a stored dashboard definition.\n//\n// dashboard-management stores the definition as opaque JSON (well-formed object,\n// size-bounded \u2014 nothing more). This package owns the *shape*, so validation and\n// default-filling live here rather than on the server: parseDashboardDefinition\n// turns an untrusted parsed value into a DashboardDefinition or throws, so the\n// renderer never has to guess at a missing canvas/breakpoint/box. Kept permissive\n// where a sensible default exists (a bare `{ widgets: [] }` is valid) and strict\n// where a wrong value would silently mis-render (unknown widget type, no base box).\n\nimport {\n WIDGET_TYPES,\n type AnchorTarget,\n type Breakpoints,\n type Canvas,\n type CanvasGrid,\n type CanvasSizing,\n type DashboardDefinition,\n type LocationSelection,\n type SlotBinding,\n type SlotDefinition,\n type SlotScope,\n type WidgetBox,\n type WidgetInstance,\n type WidgetLayout,\n type WidgetType,\n} from './types';\n\n// The breakpoint every layout must define; a widget/viewport with no more specific\n// box falls back to it. Named once so parse + resolveWidgetBox agree.\nexport const BASE_BREAKPOINT = 'base';\n\nconst WIDGET_TYPE_SET: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\n// The default canvas grid: 24 fluid columns, an 8px gutter, 40px rows \u2014 a\n// high-resolution grid that fills its container width (ADR-039 amendment).\nconst DEFAULT_GRID: CanvasGrid = { columns: 24, gap: 8, rowHeight: 40 };\nconst DEFAULT_SIZING: CanvasSizing = 'fill';\n\n// Thrown when a definition cannot be coerced into a renderable shape. The message\n// names the offending path so a bad document is diagnosable, not just \"invalid\".\nexport class DashboardDefinitionError extends Error {\n constructor(message: string) {\n super(`invalid dashboard definition: ${message}`);\n this.name = 'DashboardDefinitionError';\n }\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction numberAt(rec: Record<string, unknown>, key: string, fallback: number): number {\n const v = rec[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\n// parseDashboardDefinition validates a parsed JSON value and returns a definition\n// with defaults filled, or throws DashboardDefinitionError. `raw` is the value\n// after JSON.parse (the caller owns the parse so a syntax error is theirs to\n// handle); an already-typed object round-trips unchanged.\nexport function parseDashboardDefinition(raw: unknown): DashboardDefinition {\n if (!isRecord(raw)) throw new DashboardDefinitionError('not a JSON object');\n\n const widgetsRaw = raw.widgets;\n if (!Array.isArray(widgetsRaw)) throw new DashboardDefinitionError('widgets must be an array');\n\n const def: DashboardDefinition = {\n schemaVersion: numberAt(raw, 'schemaVersion', 1),\n title: typeof raw.title === 'string' ? raw.title : '',\n canvas: parseCanvas(raw.canvas),\n widgets: widgetsRaw.map((w, i) => parseWidget(w, i)),\n };\n // slots \u2014 the runtime-binding section (ADR-039). Normalized when present so a\n // stored definition round-trips, but omitted entirely otherwise so a slot-free\n // dashboard serializes unchanged (no spurious `\"slots\":{}` diff).\n const slots = parseSlots(raw.slots);\n if (slots) def.slots = slots;\n return def;\n}\n\nfunction parseSlots(raw: unknown): Record<string, SlotDefinition> | undefined {\n if (!isRecord(raw)) return undefined;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, spec] of Object.entries(raw)) {\n // Skip a `__proto__` key: `slots['__proto__'] = \u2026` hits the prototype setter (the\n // slot is lost + the map's prototype is swapped) rather than creating an own\n // property \u2014 the same guard parseBindingManifest applies to its untrusted input.\n if (name === '__proto__') continue;\n if (!isRecord(spec)) continue;\n const type = spec.type === 'anchor' ? 'anchor' : 'device';\n const slot: SlotDefinition = { type };\n if (typeof spec.label === 'string') slot.label = spec.label;\n const binding = parseSlotBinding(spec.defaultBinding);\n if (binding) slot.defaultBinding = binding;\n // Carry a well-formed `scope` (the context hierarchy). Structural parse only here;\n // cross-slot validity (parent exists, is an anchor, no cycles) is checked once the\n // whole map is built \u2014 a scope-blind whitelist would DROP the field and silently\n // erase the hierarchy on every load.\n const scope = parseScope(spec.scope);\n if (scope) slot.scope = scope;\n slots[name] = slot;\n }\n if (Object.keys(slots).length === 0) return undefined;\n validateScopes(slots); // drops any scope with a missing/non-anchor/self/cyclic parent\n return slots;\n}\n\n// parseScope reads a candidate `{ parent, strategy }` \u2014 structural shape only. A\n// non-string/empty parent drops the scope; an unrecognized strategy defaults to 'first'.\nfunction parseScope(raw: unknown): SlotScope | undefined {\n if (!isRecord(raw)) return undefined;\n const parent = typeof raw.parent === 'string' ? raw.parent : '';\n if (!parent) return undefined;\n return { parent, strategy: raw.strategy === 'manual' ? 'manual' : 'first' };\n}\n\n// validateScopes drops (in place) any slot scope whose parent is missing, not an\n// anchor-typed slot, self-referential, or part of a cycle \u2014 degrade, don't throw, since\n// the definition is opaque JSON the backend never validated. Dropping a bad scope leaves\n// the slot as a plain (root) slot rather than failing the whole dashboard to parse.\nfunction validateScopes(slots: Record<string, SlotDefinition>): void {\n const drop: string[] = [];\n for (const [name, slot] of Object.entries(slots)) {\n if (!slot.scope) continue;\n const parentName = slot.scope.parent;\n const parent = Object.prototype.hasOwnProperty.call(slots, parentName) ? slots[parentName] : undefined;\n if (!parent || parent.type !== 'anchor' || parentName === name || inScopeCycle(slots, name)) {\n drop.push(name);\n }\n }\n for (const name of drop) delete slots[name].scope;\n}\n\n// inScopeCycle walks the parent chain from `start`; a revisited node means the chain\n// reaches a loop (a forest has none). This also drops a slot that merely POINTS INTO a\n// cycle without being on it \u2014 a deliberately conservative degrade: every affected slot\n// becomes a safe root rather than risking a dangling parent, and real (non-hand-edited)\n// definitions never contain cycles. Reads scopes via own-property lookup so a slot named\n// '__proto__'/'constructor' can't reach an inherited member.\nfunction inScopeCycle(slots: Record<string, SlotDefinition>, start: string): boolean {\n const seen = new Set<string>();\n let cur: string | undefined = start;\n while (cur) {\n if (seen.has(cur)) return true;\n seen.add(cur);\n const slot: SlotDefinition | undefined = Object.prototype.hasOwnProperty.call(slots, cur)\n ? slots[cur]\n : undefined;\n cur = slot?.scope?.parent;\n }\n return false;\n}\n\n// canScopeSlot reports whether `child` may take a scope with parent `parentName` \u2014 the\n// authoring-time equivalent of the loader's validateScopes rules, so the editor cannot write\n// a scope the loader would silently drop on reload. A scope is valid iff the parent exists,\n// is an anchor-typed slot, is not the child itself, and adding the child\u2192parent edge creates\n// no cycle (walking the parent's own ancestor chain must not reach the child). Reads scopes\n// via own-property lookup so a slot named '__proto__'/'constructor' can't reach an inherited\n// member. Pure \u2014 never mutates the slots map.\nexport function canScopeSlot(\n slots: Record<string, SlotDefinition>,\n child: string,\n parentName: string,\n): boolean {\n if (child === parentName) return false;\n const parent = Object.prototype.hasOwnProperty.call(slots, parentName) ? slots[parentName] : undefined;\n if (!parent || parent.type !== 'anchor') return false;\n // Would child\u2192parent close a loop? The parent (transitively) must not already depend on\n // the child. Guard against a pre-existing upstream cycle so this can't spin.\n const seen = new Set<string>();\n let cur: string | undefined = parentName;\n while (cur) {\n if (cur === child) return false;\n // A chain that reaches a pre-existing cycle is rejected too (not just accepted-by-break),\n // matching the loader's inScopeCycle, which drops any slot whose ancestry reaches a loop.\n if (seen.has(cur)) return false;\n seen.add(cur);\n const slot: SlotDefinition | undefined = Object.prototype.hasOwnProperty.call(slots, cur)\n ? slots[cur]\n : undefined;\n cur = slot?.scope?.parent;\n }\n return true;\n}\n\n// parseSlotBinding normalizes a slot binding (device token or anchor target), or\n// drops it (undefined) when absent/malformed. The entity only \u2014 a binding never\n// carries measurement names (those live on the widget's selector). Exported so the\n// runtime binding manifest (parseBindingManifest) validates host input the same way.\nexport function parseSlotBinding(raw: unknown): SlotBinding | undefined {\n if (!isRecord(raw)) return undefined;\n if (raw.kind === 'device') {\n const token = stringAt(raw, 'deviceToken');\n return token ? { kind: 'device', deviceToken: token } : undefined;\n }\n if (raw.kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const targetToken = stringAt(anchorRec, 'targetToken');\n // Drop an anchor binding with no target (symmetric with the empty-device-token\n // case) \u2014 it names no entity, so it can't be a default binding.\n if (!targetToken) return undefined;\n return {\n kind: 'anchor',\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken,\n },\n };\n }\n return undefined;\n}\n\n// parseGrid coerces the canvas grid, filling defaults. `columns`/`rowHeight` are\n// floored to >=1 so a zero never yields an unusable `repeat(0,1fr)` / 0-px rows.\n// `gap` accepts a single number or a {row,col} pair; anything else \u2192 the default.\nfunction parseGrid(raw: unknown): CanvasGrid {\n const rec = isRecord(raw) ? raw : {};\n const columns = Math.max(1, Math.round(numberAt(rec, 'columns', DEFAULT_GRID.columns)));\n const rowHeight = Math.max(1, numberAt(rec, 'rowHeight', DEFAULT_GRID.rowHeight));\n let gap: CanvasGrid['gap'] = DEFAULT_GRID.gap;\n if (typeof rec.gap === 'number' && Number.isFinite(rec.gap)) {\n gap = Math.max(0, rec.gap);\n } else if (isRecord(rec.gap)) {\n // A missing axis falls back to the default gutter (not 0), so a partial\n // `{gap:{row:12}}` keeps the default column gutter rather than losing it.\n gap = {\n row: Math.max(0, numberAt(rec.gap, 'row', DEFAULT_GRID.gap as number)),\n col: Math.max(0, numberAt(rec.gap, 'col', DEFAULT_GRID.gap as number)),\n };\n }\n return { columns, gap, rowHeight };\n}\n\n// parseSizing coerces the container-sizing knob. 'fill' (the default) or a\n// single-axis fixed box `{width}` / `{height}`; a malformed value falls back to fill.\nfunction parseSizing(raw: unknown): CanvasSizing {\n if (isRecord(raw)) {\n if (typeof raw.width === 'number' && Number.isFinite(raw.width)) {\n return { width: Math.max(1, raw.width) };\n }\n if (typeof raw.height === 'number' && Number.isFinite(raw.height)) {\n return { height: Math.max(1, raw.height) };\n }\n }\n return DEFAULT_SIZING;\n}\n\nfunction parseCanvas(raw: unknown): Canvas {\n const rec = isRecord(raw) ? raw : {};\n\n const grid = parseGrid(rec.grid);\n const sizing = parseSizing(rec.sizing);\n\n // Breakpoints must define 'base'; default a single base:0 so a definition that\n // omits responsive layouts still resolves.\n const bpRec = isRecord(rec.breakpoints) ? rec.breakpoints : {};\n const breakpoints: Breakpoints = {};\n for (const [name, width] of Object.entries(bpRec)) {\n if (typeof width === 'number' && Number.isFinite(width)) breakpoints[name] = width;\n }\n if (!(BASE_BREAKPOINT in breakpoints)) breakpoints[BASE_BREAKPOINT] = 0;\n\n const canvas: Canvas = { grid, sizing, breakpoints };\n if (isRecord(rec.background)) {\n const { color, imageUrl } = rec.background;\n canvas.background = {\n color: typeof color === 'string' ? color : null,\n imageUrl: typeof imageUrl === 'string' ? imageUrl : null,\n };\n }\n return canvas;\n}\n\nfunction parseWidget(raw: unknown, index: number): WidgetInstance {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}] is not an object`);\n\n const type = raw.type;\n if (typeof type !== 'string' || !WIDGET_TYPE_SET.has(type)) {\n throw new DashboardDefinitionError(`widgets[${index}] has unknown type ${JSON.stringify(type)}`);\n }\n\n const widget: WidgetInstance = {\n id: typeof raw.id === 'string' && raw.id.length > 0 ? raw.id : generateWidgetId(),\n type: type as WidgetType,\n layout: parseLayout(raw.layout, index),\n };\n // datasource is owned by the hub, but the two supported kinds are NORMALIZED here\n // so downstream (hub/widgets) never sees a partial shape (a device selector with\n // no measurements array, an anchor with a non-object target, \u2026). Reserved/other\n // kinds are carried through opaquely \u2014 the hub rejects them.\n const ds = parseDatasource(raw.datasource);\n if (ds) widget.datasource = ds;\n if (isRecord(raw.options)) widget.options = raw.options as Record<string, unknown>;\n return widget;\n}\n\nfunction stringAt(rec: Record<string, unknown>, key: string): string {\n const v = rec[key];\n return typeof v === 'string' ? v : '';\n}\n\nfunction stringArrayAt(rec: Record<string, unknown>, key: string): string[] {\n const v = rec[key];\n return Array.isArray(v) ? v.filter((m): m is string => typeof m === 'string') : [];\n}\n\n// parseLocationSelection reads a selector's OPTIONAL location-series field.\n//\n// \uD83D\uDD34 Additive, and it has to stay that way: every definition stored before the location\n// channel existed carries no `location`, and undefined here is exactly right for all of\n// them \u2014 the selector names no location series, which is true. Nothing about the\n// measurement path is touched, so the regression risk is confined to what an author\n// deliberately adds.\n//\n// The vocabulary is closed, so an unrecognized series is DROPPED rather than carried\n// through: the hub resolves a location scope only for a selector naming a series it\n// understands, and carrying an unknown one would produce a widget that looks configured\n// and reads nothing. Dropping it makes the widget's empty state honest.\nfunction parseLocationSelection(raw: unknown): LocationSelection | undefined {\n if (!isRecord(raw)) return undefined;\n return raw.series === 'latest' ? { series: 'latest' } : undefined;\n}\n\n// withLocation attaches a parsed location selection to a selector, and OMITS the key\n// entirely when there is none \u2014 so a measurement-only definition serializes byte-for-\n// byte as it did before this field existed (no spurious `\"location\":undefined` in the\n// diff, and isDirty stays quiet on a load/save round trip).\nfunction withLocation<S extends object>(selector: S, raw: Record<string, unknown>): S {\n const location = parseLocationSelection(raw.location);\n return location ? { ...selector, location } : selector;\n}\n\n// parseDatasource coerces a raw datasource into a normalized selector, or drops it\n// (returns undefined) when it is absent or its `kind` is not a non-empty string.\nfunction parseDatasource(raw: unknown): WidgetInstance['datasource'] | undefined {\n if (!isRecord(raw)) return undefined;\n const kind = raw.kind;\n if (typeof kind !== 'string' || kind.length === 0) return undefined;\n\n if (kind === 'device') {\n return withLocation(\n { kind: 'device' as const, deviceToken: stringAt(raw, 'deviceToken'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n if (kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const selector: WidgetInstance['datasource'] = withLocation(\n {\n kind: 'anchor' as const,\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n // targetType defaults to '' (the config panel constrains it to the union;\n // a hand-edited/empty value round-trips rather than being silently coerced).\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken: stringAt(anchorRec, 'targetToken'),\n },\n measurements: stringArrayAt(raw, 'measurements'),\n },\n raw,\n );\n if (isRecord(raw.aggregation)) {\n (selector as { aggregation?: unknown }).aggregation = raw.aggregation;\n }\n return selector;\n }\n\n if (kind === 'slot') {\n // Runtime-binding kind: the entity is resolved at mount via the Hub's binding\n // manifest; the widget carries only the slot name + the series it wants (the\n // measurement names, and/or the location series).\n return withLocation(\n { kind: 'slot' as const, slot: stringAt(raw, 'slot'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n\n // Reserved/other kinds: carry through opaquely (the hub rejects them).\n return raw as unknown as WidgetInstance['datasource'];\n}\n\nfunction parseLayout(raw: unknown, index: number): WidgetLayout {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}].layout is missing`);\n\n const layout: WidgetLayout = {};\n for (const [bp, box] of Object.entries(raw)) {\n if (isRecord(box)) layout[bp] = parseBox(box);\n }\n if (!(BASE_BREAKPOINT in layout)) {\n throw new DashboardDefinitionError(`widgets[${index}].layout has no '${BASE_BREAKPOINT}' box`);\n }\n return layout;\n}\n\nfunction parseBox(rec: Record<string, unknown>): WidgetBox {\n // col/row are 0-based start lines (clamped >=0); spans are >=1 so a widget can't\n // vanish. offset is an optional signed-pixel nudge \u2014 carried only when present so a\n // box without it round-trips unchanged.\n const box: WidgetBox = {\n col: Math.max(0, Math.round(numberAt(rec, 'col', 0))),\n colSpan: Math.max(1, Math.round(numberAt(rec, 'colSpan', 1))),\n row: Math.max(0, Math.round(numberAt(rec, 'row', 0))),\n rowSpan: Math.max(1, Math.round(numberAt(rec, 'rowSpan', 1))),\n // z rounds too: a fractional zIndex is invalid CSS and silently drops to auto,\n // so keep it an integer like every other box field.\n z: Math.round(numberAt(rec, 'z', 0)),\n };\n if (isRecord(rec.offset)) {\n box.offset = { x: numberAt(rec.offset, 'x', 0), y: numberAt(rec.offset, 'y', 0) };\n }\n return box;\n}\n\n// serializeDefinition is the canonical on-the-wire JSON \u2014 the inverse of\n// parseDashboardDefinition \u2014 that dashboard-management stores. Named (not an inline\n// JSON.stringify) so every consumer that persists a definition shares one format.\nexport function serializeDefinition(def: DashboardDefinition): string {\n return JSON.stringify(def);\n}\n\n// isDirty reports whether two definitions differ. A structural JSON compare is\n// enough: both sides come from parse/edit transforms that preserve key order, so\n// it only flips on a real change. Drives an editor's save/dirty state.\nexport function isDirty(a: DashboardDefinition, b: DashboardDefinition): boolean {\n return serializeDefinition(a) !== serializeDefinition(b);\n}\n\n// resolveWidgetBox returns the box for the active breakpoint, falling back to the\n// widget's 'base' box when it defines no override for that breakpoint. Parse\n// guarantees a base box exists, so this always resolves.\nexport function resolveWidgetBox(layout: WidgetLayout, breakpoint: string): WidgetBox {\n return layout[breakpoint] ?? layout[BASE_BREAKPOINT];\n}\n\n// activeBreakpoint picks the breakpoint whose min width is the largest one that\n// still fits the viewport (falling back to 'base' / the smallest). Deterministic\n// given equal widths by preferring the wider min.\nexport function activeBreakpoint(breakpoints: Breakpoints, viewportWidth: number): string {\n let best = BASE_BREAKPOINT;\n let bestWidth = -1;\n for (const [name, minWidth] of Object.entries(breakpoints)) {\n if (viewportWidth >= minWidth && minWidth > bestWidth) {\n best = name;\n bestWidth = minWidth;\n }\n }\n return best;\n}\n\n// generateWidgetId mints a unique widget-instance id. Uses crypto.randomUUID where\n// available (browsers, modern Node); the counter fallback keeps ids unique within a\n// session for the rare environment without it.\nlet idCounter = 0;\nexport function generateWidgetId(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return `w-${c.randomUUID()}`;\n idCounter += 1;\n return `w-${idCounter.toString(36)}`;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Runtime slot binding (ADR-039). A dashboard definition is a reusable TEMPLATE:\n// widgets reference named slots, and each slot declares a `defaultBinding` (the\n// authoring tenant's entity). At MOUNT the host may supply a manifest that overrides\n// any slot's binding \u2014 so one definition + two manifests renders as two live\n// dashboards on different entities. effectiveBindings computes the manifest the\n// DashboardHub actually resolves against: the slot defaults, overlaid by the host's\n// overrides.\n\nimport { parseSlotBinding } from './definition';\nimport type { DashboardDefinition, SlotBinding, SlotDefinition } from './types';\n\n// effectiveBindings merges a definition's slot default bindings with an optional\n// host manifest (manifest wins). Slots without a default and not in the manifest are\n// omitted \u2192 the Hub renders them as an empty placeholder.\n//\n// This is the BASE layer (defaults + manifest), computed synchronously. For a dashboard\n// with SCOPED slots it is NOT the final manifest: a scoped slot's default here is a\n// fallback that resolveContextBindings (the cascade) supersedes with a value derived\n// from the parent. A host with scoped slots feeds this map as `base` into the cascade\n// and hands the hub/renderer the cascade's output, not this map directly.\nexport function effectiveBindings(\n definition: DashboardDefinition,\n manifest?: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [name, slot] of Object.entries(definition.slots ?? {})) {\n if (slot.defaultBinding) out[name] = slot.defaultBinding;\n }\n if (manifest) {\n for (const [name, binding] of Object.entries(manifest)) out[name] = binding;\n }\n return out;\n}\n\n// What parseBindingManifest made of a host manifest: the bindings it accepted, and the\n// slot names it did NOT \u2014 in manifest order.\nexport interface ParsedBindingManifest {\n bindings: Record<string, SlotBinding>;\n dropped: string[];\n}\n\n// parseBindingManifest validates an untrusted host manifest (slot name \u2192 binding)\n// into a clean Record<slot, SlotBinding>, dropping malformed entries. The host of an\n// exported dashboard passes this to effectiveBindings to bind the definition's slots\n// to ITS entities: one definition + two manifests \u2192 two live dashboards.\n//\n// \uD83D\uDD34 IT REPORTS WHAT IT DROPPED, because a silent drop is this API's sharpest edge and\n// every host has to handle it. A typo'd binding does not fail \u2014 it vanishes, the slot\n// stays unbound, and the widgets on it render as empty frames with nothing anywhere\n// saying why. Returning the names makes that a thing a host can show a user. The\n// alternative every host would otherwise reach for is comparing key counts against its\n// own input, which infers the same fact less well: it cannot name the offender, and it\n// counts the `__proto__` refusal below as if it were malformed.\nexport function parseBindingManifest(raw: unknown): ParsedBindingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { bindings: {}, dropped: [] };\n }\n const bindings: Record<string, SlotBinding> = {};\n const dropped: string[] = [];\n for (const [slot, spec] of Object.entries(raw as Record<string, unknown>)) {\n // Skip a `__proto__` key: `out['__proto__'] = \u2026` would hit the prototype setter\n // (lose the binding + swap out's prototype) rather than set an own property.\n // Reported as dropped: it IS a binding the host asked for and did not get.\n if (slot === '__proto__') {\n dropped.push(slot);\n continue;\n }\n const binding = parseSlotBinding(spec);\n if (binding) bindings[slot] = binding;\n else dropped.push(slot);\n }\n return { bindings, dropped };\n}\n\n// stripDefaultBindings removes every slot's default binding, turning a concrete\n// dashboard into a TEMPLATE \u2014 the exported form a host must supply a manifest for\n// (each slot renders as a placeholder until bound). Slot names/types/labels are kept\n// so the importer knows what to bind. (Caveats: a slot's `label` is the author's\n// entity token \u2014 a naming hint, not stripped; and an anchor selector carrying a\n// Phase-2 `aggregation` stays concrete/un-slotted through migration, so it isn't\n// rebindable \u2014 neither is reachable through the console's authoring UI today.)\nexport function stripDefaultBindings(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) {\n const { defaultBinding: _drop, ...rest } = slot;\n slots[name] = rest;\n }\n return { ...def, slots };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The scoped-slot cascade (ADR-039 selection amendment). A dashboard's slots form a\n// context FOREST: a scoped slot resolves relative to its parent's binding (a device\n// within the selected building). resolveContextBindings walks that forest and produces\n// ONE settled slot\u2192binding map the host feeds to the hub + renderer.\n//\n// It is a host-level overlay, NOT in-hub machinery: the hub resolves its bindings once\n// at construction, so selection re-keys the renderer and the hub is rebuilt through the\n// shipped path \u2014 the overlay lives outside the hub and a rebuild never erases it. The\n// pass is async only because enumerating an anchor's members hits the resolver; it is\n// otherwise pure, and the host guards it with a monotonic generation so a slow members\n// response can't overwrite a newer selection.\n\nimport type { DeviceResolver } from './hub';\nimport type { DashboardDefinition, SelectionTarget, SlotBinding, SlotDefinition } from './types';\n\n// The cascade only needs to enumerate an anchor's member devices; it takes the narrow\n// slice of DeviceResolver so a caller (or a test) needn't supply the whole interface.\nexport type MemberResolver = Pick<DeviceResolver, 'devicesForAnchor'>;\n\nfunction ownGet<T>(map: Record<string, T> | undefined, key: string): T | undefined {\n return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n}\n\n// setBinding assigns an OWN property, skipping '__proto__' \u2014 a plain `map['__proto__'] =\n// v` hits the prototype setter (drops the value AND swaps the map's prototype) rather\n// than creating an own key. A slot so named names no real entity, so skipping it is safe;\n// this keeps the SCOPED path as prototype-safe as the scope-free spread path.\nfunction setBinding(map: Record<string, SlotBinding>, key: string, value: SlotBinding): void {\n if (key === '__proto__') return;\n map[key] = value;\n}\n\n// hasScopedSlots reports whether any slot declares a scope \u2014 the host's fast-path gate\n// (a scope-free dashboard needs no async cascade, just the synchronous overlay).\nexport function hasScopedSlots(definition: DashboardDefinition): boolean {\n const slots = definition.slots;\n if (!slots) return false;\n for (const name of Object.keys(slots)) if (slots[name]?.scope) return true;\n return false;\n}\n\n// bindingsWithoutScopedSlots returns `map` with every SCOPED-slot key removed \u2014 the safe\n// interim a host seeds before the async cascade first settles. At mount nothing has been\n// derived yet, so showing a scoped slot's (possibly out-of-context) default would be a\n// stale/lying binding; omitting it renders an empty placeholder until the cascade fills it\n// (\"unbound, never stale\"). Prototype-safe on write.\nexport function bindingsWithoutScopedSlots(\n definition: DashboardDefinition,\n map: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const slots = definition.slots;\n if (!slots) return map;\n const out: Record<string, SlotBinding> = {};\n for (const name of Object.keys(map)) {\n if (!ownGet(slots, name)?.scope) setBinding(out, name, map[name]);\n }\n return out;\n}\n\n// applySelection folds one selection into the overlay, returning a new map (never\n// mutating). Computed-key assignment creates an own property even for '__proto__', so\n// this is prototype-safe; the cascade reads it back via own-property lookups regardless.\nexport function applySelection(\n overlay: Record<string, SlotBinding>,\n target: SelectionTarget,\n): Record<string, SlotBinding> {\n return { ...overlay, [target.slot]: target.binding };\n}\n\n// topoOrder returns slot names parent-before-child (the forest's topological order), so\n// a child is derived only after its parent's binding is settled. DFS with a visited set;\n// parse already rejected cycles, but the guard keeps a hand-edited definition safe.\nfunction topoOrder(slots: Record<string, SlotDefinition>): string[] {\n const order: string[] = [];\n const done = new Set<string>();\n const visit = (name: string, stack: Set<string>): void => {\n if (done.has(name) || stack.has(name)) return;\n const slot = ownGet(slots, name);\n if (!slot) return;\n stack.add(name);\n const parent = slot.scope?.parent;\n if (parent) visit(parent, stack);\n stack.delete(name);\n done.add(name);\n order.push(name);\n };\n for (const name of Object.keys(slots)) visit(name, new Set());\n return order;\n}\n\n// resolveContextBindings computes the effective slot\u2192binding map: root slots take the\n// selection overlay over the slot default; a scoped slot derives from its parent's\n// resolved binding per its strategy. Unbound slots are OMITTED (the hub renders them as\n// an empty placeholder). Fail-safe: a membership error or an unbound/non-anchor parent\n// leaves the child unbound rather than throwing. `base` is the sync default+manifest\n// layer (effectiveBindings); `overlay` is the accumulated selection (wins over base).\nexport async function resolveContextBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n overlay: Record<string, SlotBinding>,\n resolver: MemberResolver,\n): Promise<Record<string, SlotBinding>> {\n const slots = definition.slots ?? {};\n const out: Record<string, SlotBinding> = {};\n\n for (const name of topoOrder(slots)) {\n const slot = ownGet(slots, name);\n const scope = slot?.scope;\n if (!scope) {\n // Root context: the selection wins over the default \u2014 but only a TYPE-COMPATIBLE\n // selection (a device pick must not re-bind an anchor slot, and vice versa). A\n // type-incompatible selection is \"not applicable\": ignore it and keep the prior\n // (default/manifest) binding rather than corrupting the context (ADR decision 7).\n const sel = ownGet(overlay, name);\n const b = sel && (!slot || bindingMatchesType(sel, slot.type)) ? sel : ownGet(base, name);\n if (b) setBinding(out, name, b);\n continue;\n }\n // Scoped: derive from the parent's ALREADY-settled binding (topo order guarantees it).\n // Every strategy binds a DEVICE, so a scoped slot must be device-typed; a hand-edited\n // anchor-typed scoped slot can't be derived (a device binding on an anchor slot mis-types\n // downstream) \u2192 leave it unbound (the authoring UI only scopes device slots).\n if (slot && slot.type !== 'device') continue;\n const parentBinding = ownGet(out, scope.parent);\n if (!parentBinding || parentBinding.kind !== 'anchor') continue; // parent unbound \u2192 child unbound\n let members: string[];\n try {\n members = [...(await resolver.devicesForAnchor(parentBinding.anchor))].sort();\n } catch {\n continue; // membership error \u2192 child unbound (fail-safe)\n }\n if (scope.strategy === 'first') {\n // Auto-follow: the parent's first member (deterministic by token). The slot's own\n // default/selection is ignored \u2014 it is fully derived.\n if (members.length > 0) setBinding(out, name, { kind: 'device', deviceToken: members[0] });\n } else {\n // Manual: the current pick (selection over default), kept iff still a member of the\n // (possibly changed) parent; otherwise unbound (a picker prompts a fresh, in-context pick).\n const pick = ownGet(overlay, name) ?? ownGet(base, name);\n if (pick && pick.kind === 'device' && members.includes(pick.deviceToken)) setBinding(out, name, pick);\n }\n }\n\n // Pass through a manifest binding for a slot the definition does not declare (a host\n // manifest may bind an undeclared slot; effectiveBindings kept it, so we must too). Only\n // BASE keys are carried \u2014 a selection targeting an undeclared slot is dropped, so a\n // mis-authored drill target (a slot that doesn't exist) can't churn a spurious rebuild.\n for (const name of Object.keys(base)) {\n if (Object.prototype.hasOwnProperty.call(slots, name) || Object.prototype.hasOwnProperty.call(out, name)) {\n continue;\n }\n const b = ownGet(overlay, name) ?? ownGet(base, name);\n if (b) setBinding(out, name, b);\n }\n\n return out;\n}\n\n// bindingMatchesType reports whether a binding's kind matches a slot's declared type \u2014 a\n// device binding fits a 'device' slot, an anchor binding an 'anchor' slot.\nfunction bindingMatchesType(binding: SlotBinding, type: SlotDefinition['type']): boolean {\n return binding.kind === type;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Slot authoring transforms (ADR-039). A dashboard is a reusable template: every\n// widget references a named slot, and each slot carries a default entity binding.\n// These pure (React/DOM-free, unit-tested) helpers convert a dashboard's concrete\n// device/anchor selectors into that model and drive the editor's rebind/prune. The\n// authoring UX stays \"pick a device/anchor\"; the storage becomes slot-based, and\n// identical bindings collapse into ONE shared slot (dedup) \u2014 so a host manifest\n// binds each real entity once, and CompositeWidget-style sharing falls out for free.\n\nimport { canScopeSlot } from './definition';\nimport type {\n AnchorTarget,\n DashboardDefinition,\n DatasourceSelector,\n LocationSelection,\n SlotBinding,\n SlotDefinition,\n SlotScope,\n WidgetInstance,\n} from './types';\n\n// slotSelector builds the slot selector these transforms write, carrying the SERIES the\n// widget reads (measurement names, and the location series when one is named) while the\n// ENTITY moves onto the slot.\n//\n// The location key is omitted when absent rather than written as undefined, so a\n// measurement-only widget serializes exactly as it did before the location channel\n// existed \u2014 a migration must not make every stored dashboard dirty.\nfunction slotSelector(\n slot: string,\n measurements: string[],\n location: LocationSelection | undefined,\n): DatasourceSelector {\n return location\n ? { kind: 'slot', slot, measurements, location }\n : { kind: 'slot', slot, measurements };\n}\n\n// sameBinding \u2014 value equality of two entity bindings (drives dedup).\nexport function sameBinding(a: SlotBinding | undefined, b: SlotBinding | undefined): boolean {\n if (!a || !b) return a === b;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') {\n return (\n a.anchor.relationship === b.anchor.relationship &&\n a.anchor.targetType === b.anchor.targetType &&\n a.anchor.targetToken === b.anchor.targetToken\n );\n }\n return false;\n}\n\n// bindingLabel \u2014 a readable slot label (the bound entity's token).\nfunction bindingLabel(b: SlotBinding): string {\n return b.kind === 'device' ? b.deviceToken : b.anchor.targetToken;\n}\n\n// nextSlotName \u2014 the lowest unused `slot-N` name in a slots map.\nfunction nextSlotName(slots: Record<string, SlotDefinition>): string {\n let n = 1;\n while (slots[`slot-${n}`]) n += 1;\n return `slot-${n}`;\n}\n\n// bindingOfSelector \u2014 the entity binding a concrete device/anchor selector names\n// (measurements are dropped \u2014 a binding is the entity only). undefined for a device\n// selector with no token, or a non-concrete selector.\nfunction bindingOfSelector(ds: DatasourceSelector | undefined): SlotBinding | undefined {\n if (ds?.kind === 'device') return ds.deviceToken ? { kind: 'device', deviceToken: ds.deviceToken } : undefined;\n // Guard an empty target token (symmetric with device): a token-less anchor names no\n // entity, so it can't be a binding \u2014 and parseSlotBinding would drop it, so slotting it\n // would silently lose the relationship/targetType on reload.\n if (ds?.kind === 'anchor') {\n return ds.anchor.targetToken ? { kind: 'anchor', anchor: ds.anchor } : undefined;\n }\n return undefined;\n}\n\n// findOrAddSlot returns the name of the slot bound to `binding`, creating one (in\n// the passed, freshly-copied map) when none exists. Dedup lives here. It only reuses a\n// PLAIN (unscoped) slot: a scoped slot follows a parent, so folding a plain widget onto\n// one that merely shares the same default binding would silently give the widget cascade\n// behavior it never asked for. The migration/authoring path only ever mints plain slots.\nfunction findOrAddSlot(slots: Record<string, SlotDefinition>, binding: SlotBinding): string {\n const existing = Object.keys(slots).find((k) => !slots[k].scope && sameBinding(slots[k].defaultBinding, binding));\n if (existing) return existing;\n const name = nextSlotName(slots);\n slots[name] = { type: binding.kind, label: bindingLabel(binding), defaultBinding: binding };\n return name;\n}\n\n// migrateToSlots rewrites every widget's concrete device/anchor selector into a slot\n// selector (default-bound to that entity), deduping identical bindings into one\n// shared slot. Idempotent: slot selectors (and slot-free widgets) pass through\n// untouched, so re-running is a no-op. This is the decisive pre-GA cutover applied\n// when the console loads a dashboard.\nexport function migrateToSlots(def: DashboardDefinition): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const ds = w.datasource;\n // Leave an anchor that carries a Phase-2 `aggregation` as a concrete selector: the\n // slot model has no aggregation field, so slotting it would drop that (reserved)\n // config. It coexists fine \u2014 the Hub resolves concrete anchors too. (Editing such a\n // widget in the panel still drops aggregation, but that's an explicit action.)\n if (ds?.kind === 'anchor' && ds.aggregation) return w;\n const binding = bindingOfSelector(ds);\n if (!binding || !ds) return w;\n changed = true;\n const slot = findOrAddSlot(slots, binding);\n // The location series moves across with the measurement names: it says WHICH SERIES\n // the widget reads, and slotting changes only WHICH ENTITY it reads them from.\n // Dropping it here would silently blank every map on the board at load time.\n return { ...w, datasource: slotSelector(slot, ds.measurements, ds.location) };\n });\n // Return the SAME reference (no spurious `slots:{}`) when nothing migrated, so the\n // early-out is a true no-op and the result round-trips byte-identically.\n if (!changed) return def;\n return { ...def, widgets, slots };\n}\n\n// bindWidgetSlot points a widget at the slot for `binding` (creating/reusing it,\n// deduped) with the given measurement names \u2014 the editor's rebind + measurements\n// operation. Picking a NEW entity forks a fresh slot, leaving other widgets' slots\n// alone. A measurements-only edit (binding unchanged) KEEPS the widget's own slot even\n// when another slot shares the binding \u2014 so a distinct slot the host may override\n// separately (I-3 templates) isn't silently collapsed away.\n// `location` names the location series the widget reads, when it reads one. It is a\n// parameter rather than something preserved from the widget's current selector for the\n// same reason `measurements` is: the caller (the config panel) is the one that knows\n// which series the widget wants, and a map widget's FIRST datasource has no previous\n// selector to preserve it from.\nexport function bindWidgetSlot(\n def: DashboardDefinition,\n widgetId: string,\n binding: SlotBinding,\n measurements: string[],\n location?: LocationSelection,\n): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n const current = def.widgets.find((w) => w.id === widgetId)?.datasource;\n const currentSlot = current?.kind === 'slot' ? current.slot : undefined;\n const currentDef = currentSlot ? slots[currentSlot] : undefined;\n // Keep the widget's own slot when it is SCOPED (a context slot the cascade drives \u2014\n // never silently rehome it onto a plain slot) or when the binding is unchanged;\n // otherwise fork/reuse a plain slot for the new entity.\n const slot =\n currentSlot && currentDef && (currentDef.scope || sameBinding(currentDef.defaultBinding, binding))\n ? currentSlot\n : findOrAddSlot(slots, binding);\n const widgets = def.widgets.map((w) =>\n w.id === widgetId ? { ...w, datasource: slotSelector(slot, measurements, location) } : w,\n );\n return { ...def, widgets, slots };\n}\n\n// clearWidgetDatasource drops a widget's datasource (the \"None\" data source). Prune\n// afterwards to reclaim a now-orphaned slot.\nexport function clearWidgetDatasource(def: DashboardDefinition, widgetId: string): DashboardDefinition {\n const widgets = def.widgets.map((w) => {\n if (w.id !== widgetId) return w;\n const { datasource: _drop, ...rest } = w;\n return rest;\n });\n return { ...def, widgets };\n}\n\n// pruneSlots removes slots no widget references, and omits the `slots` key entirely\n// when none remain (so a dashboard that loses its last slot serializes clean).\nexport function pruneSlots(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const defSlots = def.slots;\n const used = new Set<string>();\n for (const w of def.widgets) {\n if (w.datasource?.kind === 'slot') used.add(w.datasource.slot);\n // A widget can also REFERENCE a slot without binding it: a context/entity-selector's\n // (and an alarm-table drill's) `options.selectionTarget` names the slot it re-points.\n // Miss it and deleting the data widget that binds that slot would prune it out from\n // under the selector \u2014 a silent \"No options\". Count it as a use.\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target.length > 0) used.add(target);\n }\n // Close `used` over scope.parent: a context-only parent slot (e.g. a root 'building'\n // that no widget binds directly but a scoped child depends on) must be kept, or the\n // child dangles and the cascade loses its top-level context. Walk each used slot's\n // ancestor chain (guarded against a hand-edited cycle) adding every parent.\n for (const start of [...used]) {\n let cur: string | undefined = start;\n const guard = new Set<string>();\n while (cur && Object.prototype.hasOwnProperty.call(defSlots, cur) && !guard.has(cur)) {\n guard.add(cur);\n const parent: string | undefined = defSlots[cur].scope?.parent;\n if (parent) used.add(parent);\n cur = parent;\n }\n }\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) if (used.has(name)) slots[name] = slot;\n if (Object.keys(slots).length === 0) {\n const { slots: _drop, ...rest } = def;\n return rest;\n }\n return { ...def, slots };\n}\n\n// anchorSlotNames lists the dashboard's anchor-typed slots \u2014 the candidate PARENTS a\n// scoped slot may attach to (an editor's parent picker), and the slots a root context-\n// selector can target. Empty when the dashboard has no slots.\nexport function anchorSlotNames(def: DashboardDefinition): string[] {\n const slots = def.slots ?? {};\n return Object.keys(slots).filter((name) => slots[name].type === 'anchor');\n}\n\n// setSlotScope makes `slotName` scoped to a parent (the context hierarchy) or, with\n// `scope` undefined, clears its scope back to a root context \u2014 the editor's scope-authoring\n// transform. It is a no-op when the slot doesn't exist, or when the proposed scope is\n// invalid (canScopeSlot: parent missing/non-anchor/self/cycle) \u2014 degrade, never write a\n// scope the loader would drop on reload. Prototype-free (operates on the parsed slots map).\nexport function setSlotScope(\n def: DashboardDefinition,\n slotName: string,\n scope: SlotScope | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const slots: Record<string, SlotDefinition> = { ...def.slots };\n const current = slots[slotName];\n if (!scope) {\n if (!current.scope) return def; // already root \u2014 no-op (keeps the reference stable)\n const { scope: _drop, ...rest } = current;\n slots[slotName] = rest;\n return { ...def, slots };\n }\n if (!canScopeSlot(slots, slotName, scope.parent)) return def;\n slots[slotName] = { ...current, scope: { parent: scope.parent, strategy: scope.strategy } };\n return { ...def, slots };\n}\n\n// widgetBinding resolves a widget's effective entity binding for the editor: a slot\n// widget's default binding, or a (pre-migration) concrete selector's entity.\nexport function widgetBinding(def: DashboardDefinition, widget: WidgetInstance): SlotBinding | undefined {\n const ds = widget.datasource;\n if (ds?.kind === 'slot') return def.slots?.[ds.slot]?.defaultBinding;\n return bindingOfSelector(ds);\n}\n\n// widgetSlotName is the slot a widget references, if any (for an editor hint).\nexport function widgetSlotName(widget: WidgetInstance): string | undefined {\n return widget.datasource?.kind === 'slot' ? widget.datasource.slot : undefined;\n}\n\n// A concrete device/anchor selector \u2014 what the config panel edits (slot-agnostic).\n// Rebuilt into slot storage by the workspace via bindWidgetSlot.\nexport type ConcreteSelector =\n | { kind: 'device'; deviceToken: string; measurements: string[]; location?: LocationSelection }\n | { kind: 'anchor'; anchor: AnchorTarget; measurements: string[]; location?: LocationSelection };\n\n// resolveConcrete gives the config panel a slot-free view of a widget's data source:\n// the bound entity + the series the widget reads (measurement names, and the location\n// series when it names one), or undefined when unbound.\nexport function resolveConcrete(\n def: DashboardDefinition,\n widget: WidgetInstance,\n): ConcreteSelector | undefined {\n const binding = widgetBinding(def, widget);\n if (!binding) return undefined;\n const measurements = widget.datasource?.measurements ?? [];\n const location = widget.datasource?.location;\n const base = location ? { measurements, location } : { measurements };\n return binding.kind === 'device'\n ? { kind: 'device', deviceToken: binding.deviceToken, ...base }\n : { kind: 'anchor', anchor: binding.anchor, ...base };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// resolveSlotCandidates \u2014 the option set a context/entity-selector widget offers for one\n// target slot (ADR-039 selection amendment). It is the picker's half of the cascade: the\n// cascade (resolveContextBindings) DERIVES a scoped slot's binding, and this enumerates the\n// bindings a viewer may pick FROM \u2014 sharing the same member source (DeviceResolver) so the\n// picker's list and the strategies can never disagree.\n//\n// scoped child slot \u2192 the parent anchor's member devices (devicesForAnchor), one option\n// per member; empty / unbound / non-anchor parent \u21D2 no options.\n// root device slot \u2192 the tenant's devices (the injected lister).\n// root anchor slot \u2192 the tenant's entities of the slot's target type; the anchor\n// relationship + targetType come from the slot's CURRENT binding (or\n// its default) as a template, only the targetToken varies. A root\n// anchor slot with no bound/default anchor has no template \u21D2 no options.\n//\n// Pure and fail-safe: a membership/lister error yields [] (an empty picker), never a throw \u2014\n// matching resolveContextBindings' degrade-don't-throw contract.\n\nimport type { MemberResolver } from './context';\nimport type {\n DashboardDefinition,\n EntityCandidateLister,\n SelectionCandidate,\n SlotBinding,\n} from './types';\n\nfunction ownGet<T>(map: Record<string, T> | undefined, key: string): T | undefined {\n return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n}\n\n// sameBinding \u2014 value equality of two bindings, so a candidate can flag the current pick.\nfunction sameBinding(a: SlotBinding | undefined, b: SlotBinding): boolean {\n if (!a) return false;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') return a.anchor.targetToken === b.anchor.targetToken;\n return false;\n}\n\nexport async function resolveSlotCandidates(\n definition: DashboardDefinition,\n slot: string,\n bindings: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister: EntityCandidateLister,\n): Promise<SelectionCandidate[]> {\n const slots = definition.slots ?? {};\n const def = ownGet(slots, slot);\n if (!def) return [];\n const current = ownGet(bindings, slot);\n const mark = (binding: SlotBinding, label: string): SelectionCandidate => ({\n binding,\n label,\n selected: sameBinding(current, binding),\n });\n\n // Scoped child: the parent anchor's members (the same source the cascade's strategies use).\n if (def.scope) {\n // A 'first' slot is fully auto-derived (the cascade always binds the parent's first\n // member and IGNORES any selection), so a picker over it would snap back on every pick.\n // Only a 'manual' slot is user-pickable; offer no options for 'first'.\n if (def.scope.strategy === 'first') return [];\n const parentBinding = ownGet(bindings, def.scope.parent);\n if (!parentBinding || parentBinding.kind !== 'anchor') return [];\n let members: string[];\n try {\n members = [...(await resolver.devicesForAnchor(parentBinding.anchor))].sort();\n } catch {\n return [];\n }\n return members.map((token) => mark({ kind: 'device', deviceToken: token }, token));\n }\n\n // Root device slot: list the tenant's devices.\n if (def.type === 'device') {\n let rows: Array<{ token: string; name?: string | null }>;\n try {\n rows = await lister('device');\n } catch {\n return [];\n }\n return rows.map((r) => mark({ kind: 'device', deviceToken: r.token }, r.name || r.token));\n }\n\n // Root anchor slot: list the tenant's entities of the target type, reusing the current/\n // default binding's relationship+targetType as the template (only targetToken varies).\n const template =\n current?.kind === 'anchor'\n ? current.anchor\n : def.defaultBinding?.kind === 'anchor'\n ? def.defaultBinding.anchor\n : undefined;\n if (!template) return [];\n let rows: Array<{ token: string; name?: string | null }>;\n try {\n rows = await lister(template.targetType);\n } catch {\n return [];\n }\n return rows.map((r) =>\n mark(\n {\n kind: 'anchor',\n anchor: {\n relationship: template.relationship,\n targetType: template.targetType,\n targetToken: r.token,\n },\n },\n r.name || r.token,\n ),\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// createEntityLister \u2014 the device-management-backed EntityCandidateLister a host injects to\n// back a ROOT context-selector's candidate set (ADR-039 selection amendment). It lives here,\n// beside createDeviceResolver, because this is the package's one seam onto device-management's\n// schema; the widget/renderer layers stay presentational and both apps (console + the /dash\n// reference viewer) share ONE implementation rather than hand-authoring the same query twice.\n//\n// Results are memoized per kind for the lister's lifetime: a context-selector re-opens over a\n// viewing session, and the tenant's customers/areas are stable enough that a per-mount refetch\n// buys nothing. A failed load drops the cache entry so the next open retries.\n\nimport { gql } from '@devicechain/client';\n\nimport { LIST_AREAS, LIST_ASSETS, LIST_CUSTOMERS, LIST_DEVICES } from './queries';\nimport type { EntityCandidateLister, EntityListKind } from './types';\n\n// A generous single page \u2014 a flat picker over tens of entities, filtered client-side.\nconst LIST_PAGE_SIZE = 500;\n\nexport function createEntityLister(): EntityCandidateLister {\n const cache = new Map<EntityListKind, Promise<Array<{ token: string; name: string | null }>>>();\n const criteria = { pageNumber: 1, pageSize: LIST_PAGE_SIZE };\n\n return (kind: EntityListKind) => {\n let pending = cache.get(kind);\n if (!pending) {\n pending = fetchKind(kind, criteria).catch((err) => {\n cache.delete(kind);\n throw err;\n });\n cache.set(kind, pending);\n }\n return pending;\n };\n}\n\nfunction fetchKind(\n kind: EntityListKind,\n criteria: { pageNumber: number; pageSize: number },\n): Promise<Array<{ token: string; name: string | null }>> {\n switch (kind) {\n case 'device':\n return gql('device-management', LIST_DEVICES, { criteria }).then((r) => r.devices.results);\n case 'customer':\n return gql('device-management', LIST_CUSTOMERS, { criteria }).then((r) => r.customers.results);\n case 'area':\n return gql('device-management', LIST_AREAS, { criteria }).then((r) => r.areas.results);\n case 'asset':\n return gql('device-management', LIST_ASSETS, { criteria }).then((r) => r.assets.results);\n default:\n // An out-of-union kind (a hand-edited definition can carry an empty/other targetType)\n // yields no candidates rather than throwing synchronously inside the lister.\n return Promise.resolve([]);\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Hand-authored typed GraphQL documents the dashboard runtime issues to resolve\n// datasources and seed history. Like the rest of the package there is no codegen \u2014\n// the SDK runs in documentMode 'string', so a raw query string cast to\n// TypedDocument<Result, Vars> is exactly what a generated document is at runtime.\n// Each doc targets one service area (see the `gql(area, ...)` call sites in\n// resolver.ts / history.ts).\n\nimport type { TypedDocument } from '@devicechain/client';\n\n// \u2500\u2500 device-management: the devices anchored to a target (area/customer/asset) \u2500\n// Filters relationships whose source is a device and whose target is the anchor\n// entity; `source { token }` yields each member device's token (measurementStream\n// is keyed by token, per ADR-044).\n\nexport interface EntityRelationshipsResult {\n entityRelationships: {\n results: Array<{ source: { token: string } }>;\n };\n}\nexport interface EntityRelationshipsVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n sourceType: string;\n targetType: string;\n target: string;\n relationshipType?: string | null;\n };\n}\n\nexport const DEVICES_FOR_ANCHOR = `\n query DevicesForAnchor($criteria: EntityRelationshipSearchCriteria!) {\n entityRelationships(criteria: $criteria) {\n results {\n source {\n token\n }\n }\n }\n }\n` as unknown as TypedDocument<EntityRelationshipsResult, EntityRelationshipsVariables>;\n\n// \u2500\u2500 device-management: which of the given device tokens still exist \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A dashboard references a device by a STABLE token (ADR-044); the token re-key\n// dropped the token\u2192id hop that used to fail on a deleted device, so a widget bound to\n// a since-deleted device streamed nothing and rendered blank. This existence check\n// restores the \"unavailable\" signal \u2014 devicesByToken returns only the tokens that\n// resolve, so a missing token is absent from the result.\n\nexport interface DevicesByTokenResult {\n devicesByToken: Array<{ token: string }>;\n}\nexport interface DevicesByTokenVariables {\n tokens: string[];\n}\n\nexport const DEVICES_BY_TOKEN = `\n query DashboardDevicesByToken($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n }\n }\n` as unknown as TypedDocument<DevicesByTokenResult, DevicesByTokenVariables>;\n\n// \u2500\u2500 device-management: list entities of one kind (root context-selector candidates) \u2500\n// Each anchor target type (customer/area/asset) plus bare devices exposes a\n// `<kind>(criteria){results{token name}}` query. A root context-selector lists these so a\n// viewer can re-point the dashboard's top-level context (which building/customer). One doc\n// per kind since each is a distinct root field; a generous single page (dashboards pick\n// among tens, not thousands \u2014 nested tree picking is deferred).\n\nexport interface EntityListResult {\n results: Array<{ token: string; name: string | null }>;\n}\nexport interface EntityListVariables {\n criteria: { pageNumber: number; pageSize: number };\n}\n\nexport const LIST_DEVICES = `\n query DashboardListDevices($criteria: DeviceSearchCriteria!) {\n devices(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ devices: EntityListResult }, EntityListVariables>;\n\nexport const LIST_CUSTOMERS = `\n query DashboardListCustomers($criteria: CustomerSearchCriteria!) {\n customers(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ customers: EntityListResult }, EntityListVariables>;\n\nexport const LIST_AREAS = `\n query DashboardListAreas($criteria: AreaSearchCriteria!) {\n areas(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ areas: EntityListResult }, EntityListVariables>;\n\nexport const LIST_ASSETS = `\n query DashboardListAssets($criteria: AssetSearchCriteria!) {\n assets(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ assets: EntityListResult }, EntityListVariables>;\n\n// \u2500\u2500 event-management: bucketed history for chart seeding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MeasurementBucket {\n bucketStart: string;\n name: string;\n avg: number | null;\n}\nexport interface BucketedMeasurementsResult {\n bucketedMeasurements: MeasurementBucket[];\n}\nexport interface BucketedMeasurementsVariables {\n criteria: {\n deviceToken: string;\n name?: string | null;\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n };\n}\n\nexport const BUCKETED_MEASUREMENTS = `\n query BucketedMeasurements($criteria: MeasurementAggregationCriteria!) {\n bucketedMeasurements(criteria: $criteria) {\n bucketStart\n name\n avg\n }\n }\n` as unknown as TypedDocument<BucketedMeasurementsResult, BucketedMeasurementsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// DashboardHub \u2014 the dashboard runtime (ADR-039).\n//\n// It multiplexes every widget's live telemetry over the SDK's single per-area\n// graphql-ws connection: many widgets bound to the same device share ONE upstream\n// measurementStream subscription (ref-counted), so a crowded dashboard opens one\n// stream per distinct device token, not one per widget. A per-widget subscription\n// model fans out badly on a crowded board. The Hub owns the subscription\n// lifecycle; widgets just hand it a datasource selector and a sink.\n\nimport { gql, isForbiddenError, subscribe, type Area, type SubscriptionSink } from '@devicechain/client';\n\nimport {\n ACKNOWLEDGE_ALARM,\n ALARM_STREAM,\n ALARMS_QUERY,\n CLEAR_ALARM,\n type AlarmSearchCriteriaInput,\n type AlarmStreamResult,\n} from './internal/alarm-doc';\nimport {\n COMMANDS_QUERY,\n CREATE_COMMAND,\n} from './internal/command-doc';\nimport { LATEST_LOCATIONS_QUERY } from './internal/location-doc';\nimport {\n MEASUREMENT_STREAM,\n type MeasurementStreamResult,\n type MeasurementStreamVariables,\n} from './internal/measurement-doc';\nimport type {\n AlarmRow,\n AnchorTarget,\n CommandRow,\n DatasourceSelector,\n LocationSample,\n MeasurementSample,\n SlotBinding,\n} from './types';\n\nconst EVENT_AREA: Area = 'event-management';\nconst DEVICE_AREA: Area = 'device-management';\nconst COMMAND_AREA: Area = 'command-delivery';\nconst STATE_AREA: Area = 'device-state';\n\n// randomToken mints a command dispatch token (its idempotency key + cancel handle).\n// crypto.randomUUID is only defined in a secure context, so fall back to a random-hex\n// token for a plain-HTTP on-prem host \u2014 matching the guarded pattern generateWidgetId\n// uses rather than throwing at send time.\nfunction randomToken(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n return `cmd-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\n// Alarm channel cadence. The live stream is a best-effort trigger, so the poll is\n// the correctness backstop (an alarm cleared while the socket was down still\n// converges within one poll); the debounce coalesces a burst of events into one\n// re-query.\nconst ALARM_RECONCILE_DEBOUNCE_MS = 800;\nconst ALARM_POLL_MS = 30_000;\n\n// Command channel cadence. command-delivery exposes NO subscription, so the control\n// channel is poll-only \u2014 but a command's lifecycle (QUEUED\u2192SENT\u2192SUCCESSFUL)\n// resolves in seconds, so it polls far faster than the alarm channel. An issued command\n// reconciles immediately (not on the next tick) so the operator sees it appear at once.\nconst COMMAND_POLL_MS = 4_000;\n\n// Location channel cadence. device-state exposes NO location subscription either, so\n// like the control channel this is poll-only \u2014 but the two poll for opposite reasons,\n// and the cadence follows from that rather than from copying a number:\n//\n// \u2022 the control channel polls FAST (4s) for a SHORT time: a command's lifecycle is a\n// burst that reaches a terminal status in seconds and then stops changing.\n// \u2022 the alarm channel polls SLOWLY (30s) because it is only a backstop \u2014 the live\n// ALARM_STREAM does the real work and the poll exists for what the socket missed.\n//\n// A fleet's positions have neither property: they change continuously, for as long as\n// the board is open, with nothing else watching them. So this poll is not a backstop,\n// it IS the channel, and its cost is a standing one. What makes a middle cadence\n// affordable is the BATCH query: one `latestLocations` round trip per tick covers\n// every device on the widget, so the cost is per-poll, not per-marker, and a 200-device\n// map costs what a 2-device map costs. Anything materially faster than this wants a\n// subscription rather than a tighter poll \u2014 a tighter poll would multiply a whole\n// board's queries against a projection that is only written when a device moves.\nconst LOCATION_POLL_MS = 15_000;\n\n// DeviceResolver turns the graph references in a dashboard definition into the\n// device tokens event-management keys on (measurementStream(deviceToken:), per\n// ADR-044). It is injected so this package carries no device-management coupling\n// and stays unit-testable; a host backs it with device-management queries.\nexport interface DeviceResolver {\n // The device tokens currently anchored to the given target. This is where\n // \"the Hub expands an anchor to its current membership\" lives (Phase 1);\n // server-side expansion is a Phase-2 optimization.\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]>;\n // Whether a device with this token currently exists (device-management). Backs the\n // widget availability check: a dashboard references a device by a stable token\n // (ADR-044), and a since-deleted device's token no longer resolves.\n deviceExists(deviceToken: string): Promise<boolean>;\n}\n\n// WidgetStreamSink receives live samples for one widget, across every device its\n// datasource resolves to. next() fires per sample; error() once if selector\n// resolution or the socket fails.\nexport interface WidgetStreamSink {\n next: (sample: MeasurementSample) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Alarm channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Alarm widgets consume a different surface than telemetry: the raised-alarm rows\n// (ADR-041), read query-then-reconcile. An alarm subscription describes the SCOPE\n// (which entity's alarms) plus the server-side filters, and receives whole snapshots\n// (not incremental events), because the authoritative rows come from the query.\n\n// AlarmSubscription is one alarm widget's interest: its scope selector (undefined =\n// tenant-wide \u2014 every alarm the viewer can see) plus filters. `pageSize` bounds the\n// rows returned (an alarm table shows the newest N); the total count reported in a\n// snapshot is independent of it (server totalRecords), so an alarm-count reflects the\n// true match count even past the page.\nexport interface AlarmSubscription {\n datasource?: DatasourceSelector;\n state?: string;\n severity?: string;\n acknowledged?: boolean;\n pageSize: number;\n}\n\n// A full alarm snapshot: the current rows (newest first, capped to pageSize) and the\n// total number of alarms matching the filter (past the page). One replaces the last.\nexport interface AlarmSnapshot {\n alarms: AlarmRow[];\n total: number;\n}\n\nexport interface AlarmStreamSink {\n next: (snapshot: AlarmSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Control channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The command-button widget issues commands to a device and watches their delivery\n// lifecycle. Unlike telemetry/alarms there is no live subscription (command-delivery\n// exposes none), so the channel is poll-only: a device-scoped `commands` query re-read\n// on a short interval (and immediately after an issue). A command targets ONE device,\n// so a subscription resolves its scope to a single device token.\n\n// CommandSubscription is one command widget's interest: its scope (which device) plus\n// the page size bounding the recent-command history it shows. A command button binds a\n// single device; an unscoped (or unresolved) widget has no target and renders empty.\nexport interface CommandSubscription {\n datasource?: DatasourceSelector;\n pageSize: number;\n}\n\n// A command-history snapshot: the resolved target device (null when unbound \u2014 the\n// widget then can't issue), the recent commands (newest first, capped to pageSize) and\n// the total matching count. One replaces the last (poll-then-emit).\nexport interface CommandSnapshot {\n deviceToken: string | null;\n commands: CommandRow[];\n total: number;\n}\n\nexport interface CommandStreamSink {\n next: (snapshot: CommandSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// The result of issuing a command. It is a DISCRIMINATED UNION rather than a token,\n// because the enqueue has two possible answers and a widget must not show them the same\n// way:\n//\n// - 'sent' \u2014 the command was created; `token` is its freshly-minted dispatch\n// token, so the widget can highlight the command it just issued as it\n// moves through the lifecycle.\n// - 'rejected' \u2014 the server REFUSED the request and said why: `code` is the stable\n// classification to branch on (DEVICE_NOT_FOUND,\n// COMMAND_NOT_IN_VOCABULARY, PAYLOAD_SCHEMA_VIOLATION,\n// PAYLOAD_NOT_JSON, METADATA_NOT_JSON, EXPIRES_AT_INVALID,\n// HELD_CEILING_EXCEEDED, \u2026 \u2014 an OPEN set), `reason` is client-safe\n// prose to show a person.\n//\n// A THROWN error is neither: it means the platform could not decide the enqueue at all\n// (the service unreachable, a database error), which says nothing about the command and\n// so deserves a generic failure message rather than a reason.\n//\n// The union is deliberately not \"token, plus an optional rejection\". A caller reading\n// `.token` off a refusal would compile, highlight nothing, and report no failure \u2014 the\n// send would look successful. The discriminant makes the refusal impossible to ignore\n// at the type level.\nexport type CommandDispatch =\n | { status: 'sent'; token: string }\n | { status: 'rejected'; code: string; reason: string };\n\n// \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The map widget consumes a fifth surface: where the bound devices ARE. Like the\n// control channel there is no live subscription to ride (device-state exposes none for\n// position), so it is query-then-poll \u2014 the alarm and command channels are the\n// precedent here, not the measurement channel.\n//\n// What is different from all three is the REFUSAL. Position is gated on its own\n// `location:read` authority, which is deliberately absent from the read-only viewer\n// baseline, so an ordinary member with full telemetry access is routinely refused. The\n// channel therefore turns a refusal into a VALUE rather than an error: it is not a\n// fault, and it is not emptiness either.\n\n// LocationSubscription is one map widget's interest: the selector naming both the\n// devices and the location series to read. There is no page size \u2014 a map shows every\n// device it is bound to, and the bound set is the selector's own resolution.\nexport interface LocationSubscription {\n datasource?: DatasourceSelector;\n}\n\n// A location snapshot, discriminated so the refusal cannot be mistaken for emptiness.\n//\n// \uD83D\uDD34 `positions` with an empty `locations` and `forbidden` are OPPOSITE facts, and only\n// one of them is actionable. \"No device here has ever reported a position\" is a claim\n// about the DEVICES; \"you may not view location\" is a claim about the CALLER. Folding\n// them together (a bare `locations: []`) would tell an operator their fleet is\n// unlocated when the truth is that they need a role \u2014 which is the exact mistake the\n// device-detail position panel was built to avoid, held here in the type.\n//\n// `deviceTokens` is the set the selector resolved to, carried alongside the positions\n// because a never-located device is ABSENT from the query result: without it, \"bound to\n// nothing\" and \"bound to devices that have never moved\" are indistinguishable.\nexport type LocationSnapshot =\n | { kind: 'positions'; deviceTokens: string[]; locations: LocationSample[] }\n | { kind: 'forbidden' };\n\nexport interface LocationStreamSink {\n next: (snapshot: LocationSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Action seam (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Read widgets are pure `(widget, data)`; a widget that ACTS (acknowledge/clear an\n// alarm, send a command) never touches the SDK either \u2014 it calls this seam, which the\n// renderer threads in from the runtime. So preview stays offline (SyntheticDataSource\n// implements a no-op/echo WidgetActions) and the \"widget never reaches the backend\"\n// invariant holds. `can` gates the UI: a widget hides an action the viewer isn't\n// authorized for (the server enforces it regardless).\n//\n// Growing this interface is a breaking change for any external host implementing it.\n// The alarm actions (acknowledgeAlarm/clearAlarm) are the required baseline; capabilities\n// added later \u2014 like sendCommand below \u2014 are declared OPTIONAL so a host predating them\n// still satisfies the type, and consumers feature-detect (typeof actions?.sendCommand).\nexport interface WidgetActions {\n // Acknowledge / clear a raised alarm by token (requires alarm:write). Resolves when\n // the mutation succeeds; the runtime reconciles the affected alarm widgets so the\n // change shows immediately rather than waiting for the next poll.\n acknowledgeAlarm(alarmToken: string): Promise<void>;\n clearAlarm(alarmToken: string): Promise<void>;\n // Issue a command to a device (requires command:write). The runtime mints the dispatch\n // token and returns it; it also reconciles the command widgets so the new command\n // appears at once. It RESOLVES on a refusal too \u2014 with a 'rejected' dispatch carrying\n // the server's code + reason (see CommandDispatch) \u2014 and rejects only when the enqueue\n // could not be decided at all. OPTIONAL: added after this interface shipped, so a host\n // predating it (or a strictly read-only one) may omit it \u2014 a command widget then\n // renders its Send control disabled. `payload` is the request body sent to the device\n // verbatim (the widget serializes its typed parameter form to JSON).\n sendCommand?(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch>;\n // Whether the current viewer holds an authority (e.g. 'alarm:write'). Drives whether\n // an action control renders at all.\n can(authority: string): boolean;\n}\n\nexport interface DashboardHubConfig {\n resolver: DeviceResolver;\n // The effective slot\u2192entity manifest (slot defaults merged with any host override;\n // see effectiveBindings). A widget's `slot` selector resolves through this. Absent\n // slots render as an empty placeholder. Can be replaced later via setBindings.\n bindings?: Record<string, SlotBinding>;\n // The current viewer's authorities (access-token claims). Drives `can()` \u2014 which\n // gates whether a widget's action controls render. Omitted/empty = no write actions\n // (the read-only default); '*' grants all. The server enforces authority regardless.\n authorities?: string[];\n}\n\n// WidgetDataSource is the minimal contract a widget renderer needs from a data\n// source: bind a datasource selector to a sink, get a disposer back. DashboardHub\n// is the live implementation (multiplexed backend telemetry); SyntheticDataSource\n// is the offline preview implementation. The widget layer depends on THIS interface,\n// not the concrete class, so a host can feed either without widgets knowing which.\nexport interface WidgetDataSource {\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void;\n // Bind an alarm widget's scope+filters to a sink; returns a disposer. Delivers\n // whole snapshots (query-then-reconcile), not incremental events. Implemented by\n // both the live hub and the synthetic preview source so alarm widgets render\n // identically from either.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void;\n // Bind a command widget's scope to a sink; returns a disposer. Delivers whole\n // command-history snapshots on a poll (command-delivery has no subscription).\n // Implemented by both the live hub and the synthetic preview source.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;\n // Bind a map widget's selector to a sink; returns a disposer. Delivers whole\n // position snapshots on a poll (device-state has no location subscription), and\n // reports a `location:read` refusal as a snapshot state rather than an error.\n // Implemented by both the live hub and the synthetic preview source.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;\n // Whether a widget's bound entity still exists. Optimistic + async: a widget renders\n // from its stream immediately, and this resolves separately \u2014 only a device selector\n // (or a slot bound to a device) whose token no longer resolves reports false, so the\n // widget shows \"unavailable\" instead of a blank pane. Anchor / unbound-slot / no-\n // datasource report true (their empty state is legitimate, not \"unavailable\"). Fails\n // OPEN (true) on an inconclusive check, so a device-management blip never falsely marks\n // a live device unavailable.\n isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;\n}\n\n// One widget's interest in a device stream: the measurement names it wants (an\n// empty set means every measurement) and where to deliver them.\ninterface Subscriber {\n names: Set<string>;\n sink: WidgetStreamSink;\n}\n\n// The shared upstream for one distinct device token.\ninterface DeviceStream {\n subscribers: Set<Subscriber>;\n unsubscribe: () => void;\n}\n\nexport class DashboardHub implements WidgetDataSource, WidgetActions {\n private readonly resolver: DeviceResolver;\n // The viewer's authorities (for can()); '*' is the superuser wildcard.\n private readonly authorities: ReadonlySet<string>;\n // Per alarm-subscription reconcile triggers \u2014 invoked after an ack/clear so the\n // affected alarm widgets refresh immediately instead of waiting for the poll/stream.\n private readonly alarmReconcilers = new Set<() => void>();\n // Per command-subscription reconcile triggers \u2014 invoked after an issue so the command\n // widgets show the new command immediately instead of waiting for the next poll tick.\n private readonly commandReconcilers = new Set<() => void>();\n // One entry per distinct device token that has at least one subscriber.\n private readonly streams = new Map<string, DeviceStream>();\n // Live alarm-subscription disposers. The alarm channel isn't ref-counted through\n // `streams` (it holds a poll/debounce/trigger per subscription, not a shared device\n // stream), so its disposers are tracked here for disposeAll() to reach \u2014 otherwise an\n // imperative host closing the dashboard would leak every alarm widget's poll + socket.\n private readonly alarmDisposers = new Set<() => void>();\n // Live command-subscription disposers (same rationale as alarmDisposers \u2014 the control\n // channel holds a poll per subscription, not a shared device stream), so disposeAll()\n // can tear down every command widget's poll.\n private readonly commandDisposers = new Set<() => void>();\n // Live location-subscription disposers (same rationale as alarmDisposers/\n // commandDisposers \u2014 the location channel holds a poll per subscription, not a shared\n // device stream), so disposeAll() can tear down every map widget's poll.\n private readonly locationDisposers = new Set<() => void>();\n // slot name \u2192 concrete entity binding. Consulted when a widget's selector is a\n // `slot`. Mutable so the authoring host can rebind live (setBindings).\n private bindings: Record<string, SlotBinding>;\n\n constructor(config: DashboardHubConfig) {\n this.resolver = config.resolver;\n this.bindings = config.bindings ?? {};\n this.authorities = new Set(config.authorities ?? []);\n }\n\n // setBindings replaces the slot manifest. New subscriptions resolve through it;\n // callers that need already-open slot streams to re-resolve should re-subscribe\n // (the console keys the renderer on the manifest to do exactly that).\n setBindings(bindings: Record<string, SlotBinding>): void {\n this.bindings = bindings;\n }\n\n // subscribeWidget binds a widget's datasource to a sink and returns a disposer.\n // Selector resolution is async (anchor\u2192devices); the disposer is returned\n // synchronously and cancels a still-pending resolution, so tearing a widget down\n // before its streams open never attaches a leaked subscriber.\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n let disposed = false;\n const detachers: Array<() => void> = [];\n const dispose = (): void => {\n disposed = true;\n for (const detach of detachers.splice(0)) detach();\n };\n\n this.resolveDevices(datasource)\n .then((groups) => {\n if (disposed) return;\n for (const group of groups) {\n detachers.push(this.attach(group.deviceToken, group.names, sink));\n }\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // subscribeAlarms binds an alarm widget's scope+filters to a sink and returns a\n // disposer. Unlike the measurement channel it is NOT multiplexed \u2014 alarm widgets are\n // few, and each carries its own filter \u2014 so every subscription opens its own trigger\n // stream + reconcile poll (sharing one tenant-wide trigger stream across widgets is a\n // deferred optimization). Query-then-reconcile: an initial query, then the live\n // ALARM_STREAM debounced into re-queries, plus a poll backstop and a reconnect\n // re-query. Scope resolution is async (slot/anchor \u2192 devices); the disposer is\n // returned synchronously and cancels a still-pending resolution.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n let disposed = false;\n let debounce: ReturnType<typeof setTimeout> | undefined;\n let poll: ReturnType<typeof setInterval> | undefined;\n let unsubscribe: (() => void) | undefined;\n let reconciler: (() => void) | undefined;\n // Monotonic generation: only the newest reconcile's result may be emitted, so a\n // slow query that resolves after a newer one can't overwrite fresher rows.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (debounce) clearTimeout(debounce);\n if (poll) clearInterval(poll);\n unsubscribe?.();\n if (reconciler) this.alarmReconcilers.delete(reconciler);\n this.alarmDisposers.delete(dispose);\n };\n this.alarmDisposers.add(dispose);\n\n const reconcile = (tokens: string[], tenantWide: boolean): void => {\n const gen = ++generation;\n this.queryAlarms(subscription, tokens, tenantWide)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveAlarmScope(subscription.datasource)\n .then((scope) => {\n if (disposed) return;\n\n // A scoped widget that resolves to no device (an unbound slot, an empty anchor)\n // shows an empty state \u2014 NOT tenant-wide. Only a widget with no datasource at\n // all is tenant-wide. Nothing to stream/poll here. Scope is resolved once (like\n // the measurement channel): a slot rebind rebuilds the hub and re-resolves, but\n // organic anchor-membership change isn't picked up until the hub is rebuilt \u2014\n // a deferred enhancement shared with the measurement channel.\n if (!scope.tenantWide && scope.tokens.length === 0) {\n sink.next({ alarms: [], total: 0 });\n return;\n }\n\n const trigger = (): void => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => reconcile(scope.tokens, scope.tenantWide), ALARM_RECONCILE_DEBOUNCE_MS);\n };\n // Subscribe unfiltered (server filters resolve once at subscribe time and a\n // widget's scope may span devices) and treat every event as a reconcile\n // trigger \u2014 the query re-applies the scope+filters. On reconnect, re-query to\n // catch transitions missed while the socket was down.\n const adapter: SubscriptionSink<AlarmStreamResult> = {\n next: () => trigger(),\n connected: (wasRetry) => {\n if (wasRetry) reconcile(scope.tokens, scope.tenantWide);\n },\n };\n unsubscribe = subscribe(DEVICE_AREA, ALARM_STREAM, {}, adapter);\n poll = setInterval(() => reconcile(scope.tokens, scope.tenantWide), ALARM_POLL_MS);\n // Register a reconcile trigger so an ack/clear (via the action seam) refreshes\n // this widget immediately, not on the next poll tick.\n reconciler = () => reconcile(scope.tokens, scope.tenantWide);\n this.alarmReconcilers.add(reconciler);\n reconcile(scope.tokens, scope.tenantWide); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveAlarmScope turns an alarm widget's scope selector into the originator device\n // tokens to filter on, or tenant-wide when it carries no datasource. Reuses the same\n // device/anchor/slot resolution the measurement channel does.\n private async resolveAlarmScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<{ tenantWide: boolean; tokens: string[] }> {\n if (!datasource) return { tenantWide: true, tokens: [] };\n const groups = await this.resolveDevices(datasource);\n return { tenantWide: false, tokens: groups.map((g) => g.deviceToken) };\n }\n\n // queryAlarms reads the authoritative rows. Tenant-wide is one query; a scoped widget\n // runs one query per originator device (the alarms query filters a single originator)\n // and merges \u2014 deduped by token, newest first, capped to pageSize; total is the sum of\n // per-originator match counts.\n private async queryAlarms(\n sub: AlarmSubscription,\n tokens: string[],\n tenantWide: boolean,\n ): Promise<AlarmSnapshot> {\n const base = {\n pageNumber: 1, // the alarms query paginates 1-based\n pageSize: sub.pageSize,\n state: sub.state ?? null,\n severity: sub.severity ?? null,\n acknowledged: sub.acknowledged ?? null,\n } satisfies Partial<AlarmSearchCriteriaInput>;\n\n if (tenantWide) {\n const data = await gql(DEVICE_AREA, ALARMS_QUERY, {\n criteria: { ...base, originatorType: null, originator: null },\n });\n return { alarms: data.alarms.results, total: data.alarms.pagination.totalRecords };\n }\n\n const pages = await Promise.all(\n tokens.map((token) =>\n gql(DEVICE_AREA, ALARMS_QUERY, {\n criteria: { ...base, originatorType: 'device', originator: token },\n }),\n ),\n );\n const byToken = new Map<string, AlarmRow>();\n let total = 0;\n for (const page of pages) {\n total += page.alarms.pagination.totalRecords;\n for (const row of page.alarms.results) byToken.set(row.token, row);\n }\n const alarms = [...byToken.values()]\n .sort((a, b) => (b.raisedTime ?? '').localeCompare(a.raisedTime ?? ''))\n .slice(0, sub.pageSize);\n return { alarms, total };\n }\n\n // \u2500\u2500 Control channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // subscribeCommands binds a command widget's scope to a sink and returns a disposer.\n // Poll-only (command-delivery has no subscription): resolve the target device once,\n // then re-read its recent commands on an interval (and immediately after an issue via\n // the reconciler). Scope resolution is async; the disposer is returned synchronously\n // and cancels a still-pending resolution.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let reconciler: (() => void) | undefined;\n let deviceToken: string | null = null;\n // Monotonic generation: a slow poll that resolves after a newer one can't overwrite\n // fresher rows.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n if (reconciler) this.commandReconcilers.delete(reconciler);\n this.commandDisposers.delete(dispose);\n };\n this.commandDisposers.add(dispose);\n\n const reconcile = (): void => {\n const gen = ++generation;\n this.queryCommands(subscription, deviceToken)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveCommandScope(subscription.datasource)\n .then((token) => {\n if (disposed) return;\n deviceToken = token;\n // A command button needs a single target device. Unscoped/unresolved (an unbound\n // slot, no device) \u2192 an empty state, NOT tenant-wide: a command can't be issued\n // to \"all devices\". Nothing to poll.\n if (!deviceToken) {\n sink.next({ deviceToken: null, commands: [], total: 0 });\n return;\n }\n poll = setInterval(reconcile, COMMAND_POLL_MS);\n reconciler = reconcile;\n this.commandReconcilers.add(reconciler);\n reconcile(); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveCommandScope turns a command widget's scope selector into its single target\n // device token (a command targets one device), or null when it carries no datasource\n // or resolves to no device. When a selector expands to several devices (an anchor), the\n // first is the target \u2014 the console restricts command widgets to a device scope, so\n // this is a defensive fallback, not the authoring path.\n private async resolveCommandScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<string | null> {\n if (!datasource) return null;\n const groups = await this.resolveDevices(datasource);\n return groups[0]?.deviceToken ?? null;\n }\n\n // queryCommands reads the recent commands for the target device (newest first, capped\n // to pageSize) with their live delivery status.\n private async queryCommands(\n sub: CommandSubscription,\n deviceToken: string | null,\n ): Promise<CommandSnapshot> {\n if (!deviceToken) return { deviceToken: null, commands: [], total: 0 };\n const data = await gql(COMMAND_AREA, COMMANDS_QUERY, {\n criteria: { pageNumber: 1, pageSize: sub.pageSize, deviceToken, status: null },\n });\n return {\n deviceToken,\n commands: data.commands.results,\n total: data.commands.pagination.totalRecords,\n };\n }\n\n // \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // subscribeLocations binds a map widget's selector to a sink and returns a disposer.\n // Poll-only (device-state has no location subscription): resolve the bound devices\n // once, then re-read their last-known positions on an interval. Scope resolution is\n // async; the disposer is returned synchronously and cancels a still-pending\n // resolution, matching every other channel.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let deviceTokens: string[] = [];\n // Monotonic generation: a slow poll that resolves after a newer one can't overwrite\n // fresher positions.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n this.locationDisposers.delete(dispose);\n };\n this.locationDisposers.add(dispose);\n\n const reconcile = (): void => {\n const gen = ++generation;\n this.queryLocations(deviceTokens)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveLocationScope(subscription.datasource)\n .then((tokens) => {\n if (disposed) return;\n deviceTokens = tokens;\n // A widget that names no location series, carries no datasource, or resolves to\n // no device has nothing to place on a map. Emit the empty POSITIONS snapshot \u2014\n // never `forbidden`, which would claim a permission problem that does not\n // exist \u2014 and open no poll.\n if (deviceTokens.length === 0) {\n sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return;\n }\n poll = setInterval(reconcile, LOCATION_POLL_MS);\n reconcile(); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveLocationScope turns a map widget's selector into the device tokens whose\n // positions to read.\n //\n // \uD83D\uDD34 It resolves NOTHING unless the selector NAMES A LOCATION SERIES. That is the\n // point of the separate field: a device selector carrying only `measurements` is a\n // telemetry binding, and quietly reading its device's position because a map widget\n // happens to hold it would make the location field decorative \u2014 authored or not, the\n // behaviour would be identical, so nothing would ever hold it. A map bound to a\n // measurement-only selector shows its empty state, which is the honest answer.\n private async resolveLocationScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<string[]> {\n if (!datasource?.location) return [];\n const groups = await this.resolveDevices(datasource);\n return groups.map((g) => g.deviceToken);\n }\n\n // queryLocations reads the last-known position of each bound device in ONE batch\n // round trip. A device that has never been located is absent from the result (the\n // service's contract), so the caller reads \"how many are located\" from the returned\n // rows and \"how many are bound\" from the tokens.\n private async queryLocations(deviceTokens: string[]): Promise<LocationSnapshot> {\n if (deviceTokens.length === 0) return { kind: 'positions', deviceTokens, locations: [] };\n try {\n const data = await gql(STATE_AREA, LATEST_LOCATIONS_QUERY, { deviceTokens });\n return { kind: 'positions', deviceTokens, locations: data.latestLocations };\n } catch (err) {\n // Only a REFUSAL becomes a value; every other failure stays a failure, so a broken\n // device-state is never dressed up as a permission boundary (and vice versa \u2014 a\n // permission boundary is never dressed up as an outage the operator should page\n // someone about).\n if (isForbiddenError(err)) return { kind: 'forbidden' };\n throw err;\n }\n }\n\n // \u2500\u2500 WidgetActions (the write seam) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // can reports whether the viewer holds an authority ('*' grants all). Drives whether\n // a widget renders an action control; the server enforces authority regardless.\n can(authority: string): boolean {\n return this.authorities.has('*') || this.authorities.has(authority);\n }\n\n // acknowledgeAlarm / clearAlarm mutate the alarm by token, then nudge every open alarm\n // widget to reconcile so the change shows at once. The mutation reaches device-management\n // (the acknowledging identity is taken server-side from the token).\n async acknowledgeAlarm(alarmToken: string): Promise<void> {\n await gql(DEVICE_AREA, ACKNOWLEDGE_ALARM, { token: alarmToken });\n this.reconcileAlarms();\n }\n\n async clearAlarm(alarmToken: string): Promise<void> {\n await gql(DEVICE_AREA, CLEAR_ALARM, { token: alarmToken });\n this.reconcileAlarms();\n }\n\n // sendCommand issues a command to a device, minting the dispatch token here (the\n // idempotency key + cancel handle), then nudges every open command widget to reconcile\n // so the new command shows at once. The mutation reaches command-delivery (requires\n // command:write, enforced server-side regardless of can()).\n //\n // A REFUSAL COMES BACK AS A VALUE, not an exception (see CommandDispatch): the server\n // decided the request and named the reason, so the widget can show it. Nothing was\n // created in that case, so the open command widgets are NOT reconciled \u2014 a re-poll\n // would only re-render the same history and make a refused send look like it did\n // something.\n async sendCommand(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch> {\n const token = randomToken();\n const result = await gql(COMMAND_AREA, CREATE_COMMAND, {\n request: { token, deviceToken, name, payload: payload ?? null },\n });\n const rejection = result.createCommand?.rejection;\n if (rejection) {\n return { status: 'rejected', code: rejection.code, reason: rejection.reason };\n }\n // \uD83D\uDD34 A response carrying NEITHER arm is a contract violation, and it must not read\n // as success. Exactly one of command/rejection is populated; neither means the\n // answer never arrived intact \u2014 a resolver returning nothing, a host GraphQL layer\n // dropping a field, a mangled response.\n //\n // The trap is that the dispatch token is minted HERE, so nothing forces this code\n // to look at the server's answer at all: without this guard the fall-through\n // returns { status: 'sent' } with a token that names no command, and reconciles\n // every open command widget as though something had been created. The operator is\n // told their command went out and has a handle that cancels nothing.\n //\n // The two siblings written alongside this both guard it \u2014 the REACT sink returns an\n // explicit error, the console throws \u2014 and this is the surface most likely to be\n // embedded outside the console, so it is the last one that should be lenient.\n if (!result.createCommand?.command) {\n throw new Error('The command could not be issued: the platform returned no answer for it.');\n }\n this.reconcileCommands();\n return { status: 'sent', token };\n }\n\n // reconcileAlarms re-queries every open alarm subscription hub-wide (after a mutation).\n // Hub-wide is deliberate: one alarm can appear in several widgets (different scopes),\n // and the acked/cleared row must refresh in all of them; scoping the nudge would need\n // per-reconciler token knowledge for no real saving. Iterate a copy for safety.\n private reconcileAlarms(): void {\n for (const reconcile of [...this.alarmReconcilers]) reconcile();\n }\n\n // reconcileCommands re-polls every open command subscription (after an issue). Iterate\n // a copy for safety.\n private reconcileCommands(): void {\n for (const reconcile of [...this.commandReconcilers]) reconcile();\n }\n\n // disposeAll tears down every upstream stream (e.g. on dashboard close): the\n // ref-counted measurement device streams AND every alarm/command/location\n // subscription's poll + trigger. Iterate a copy of the disposer sets since each\n // removes itself as it runs.\n disposeAll(): void {\n for (const stream of this.streams.values()) stream.unsubscribe();\n this.streams.clear();\n for (const dispose of [...this.alarmDisposers]) dispose();\n this.alarmDisposers.clear();\n for (const dispose of [...this.commandDisposers]) dispose();\n this.commandDisposers.clear();\n for (const dispose of [...this.locationDisposers]) dispose();\n this.locationDisposers.clear();\n }\n\n // The number of distinct upstream device streams currently open (observability\n // + test hook \u2014 proves multiplexing collapses shared devices to one stream).\n get openStreamCount(): number {\n return this.streams.size;\n }\n\n // isDatasourceAvailable reports whether a widget's bound device still exists. Only a\n // device selector (or a slot bound to a device) is validated \u2014 an anchor, an unbound\n // slot, or no datasource has a legitimate empty state and is always \"available\". Fails\n // open: an existence-check outage returns true (never falsely mark a live device gone).\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n const deviceToken = this.availabilityToken(datasource);\n if (deviceToken === undefined) return true;\n try {\n return await this.resolver.deviceExists(deviceToken);\n } catch {\n return true;\n }\n }\n\n // availabilityToken returns the single device token whose existence gates a widget's\n // availability, or undefined when there is nothing device-specific to validate (an\n // anchor's membership is self-validating; an unbound slot is a placeholder; a reserved\n // kind isn't resolved yet).\n private availabilityToken(datasource: DatasourceSelector | undefined): string | undefined {\n if (!datasource) return undefined;\n // An empty token (a half-authored or hand-edited definition) has nothing to\n // validate \u2014 treat it like an unbound slot (available/empty), not a device that\n // \"no longer exists\", and skip the guaranteed-empty query.\n if (datasource.kind === 'device') return datasource.deviceToken || undefined;\n if (datasource.kind === 'slot') {\n const binding = Object.prototype.hasOwnProperty.call(this.bindings, datasource.slot)\n ? this.bindings[datasource.slot]\n : undefined;\n return binding && binding.kind === 'device' ? binding.deviceToken || undefined : undefined;\n }\n return undefined;\n }\n\n // resolveDevices turns a selector into the devices to stream, each with the\n // measurement names the widget wants (empty = all). Reserved selector kinds are\n // rejected here, mirroring the backend (Phase 1 ships device + anchor).\n private async resolveDevices(\n datasource: DatasourceSelector,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n switch (datasource.kind) {\n case 'device':\n return this.resolveBinding(\n { kind: 'device', deviceToken: datasource.deviceToken },\n new Set(datasource.measurements),\n );\n case 'anchor':\n return this.resolveBinding(\n { kind: 'anchor', anchor: datasource.anchor },\n new Set(datasource.measurements),\n );\n case 'slot': {\n // Own-property lookup: a slot named 'constructor'/'__proto__'/'toString' must\n // NOT resolve to an inherited Object.prototype member (which is truthy and\n // would bypass the unbound-placeholder guard, then crash on binding.kind).\n // An unbound slot is a valid placeholder (a template the host hasn't bound),\n // not an error \u2014 resolve to zero devices, a silent empty state (like an anchor\n // with no members), so the widget shows an empty pane, not an error.\n const binding = Object.prototype.hasOwnProperty.call(this.bindings, datasource.slot)\n ? this.bindings[datasource.slot]\n : undefined;\n if (!binding) return [];\n return this.resolveBinding(binding, new Set(datasource.measurements));\n }\n default:\n throw new Error(\n `dashboard selector kind '${datasource.kind}' is not supported yet`,\n );\n }\n }\n\n // resolveBinding turns a concrete entity binding (device or anchor) into the\n // device streams to open, each carrying the given measurement names. Shared by the\n // device/anchor selectors and by slot resolution (whose binding is either kind).\n // A device binding streams its token directly (measurementStream is keyed by token,\n // per ADR-044); an anchor expands to its member device tokens.\n private async resolveBinding(\n binding: SlotBinding,\n names: Set<string>,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n if (binding.kind === 'device') {\n return [{ deviceToken: binding.deviceToken, names }];\n }\n const tokens = await this.resolver.devicesForAnchor(binding.anchor);\n return tokens.map((deviceToken) => ({ deviceToken, names }));\n }\n\n // attach registers a subscriber on a device's stream (opening the upstream on\n // the first subscriber) and returns a detacher that drops it and closes the\n // upstream once the last subscriber leaves.\n private attach(deviceToken: string, names: Set<string>, sink: WidgetStreamSink): () => void {\n const stream = this.ensureStream(deviceToken);\n const subscriber: Subscriber = { names, sink };\n stream.subscribers.add(subscriber);\n\n return () => {\n if (!stream.subscribers.delete(subscriber)) return;\n // Only tear down and forget the stream if it is STILL the registered stream\n // for this device \u2014 an upstream error may have evicted and replaced it, and a\n // lingering old subscriber's detach must not delete the replacement.\n if (stream.subscribers.size === 0 && this.streams.get(deviceToken) === stream) {\n stream.unsubscribe();\n this.streams.delete(deviceToken);\n }\n };\n }\n\n private ensureStream(deviceToken: string): DeviceStream {\n const existing = this.streams.get(deviceToken);\n if (existing) return existing;\n\n const stream: DeviceStream = { subscribers: new Set(), unsubscribe: () => {} };\n // Register before subscribing so that even a synchronously-delivered first\n // sample resolves through fanout (unsubscribe stays the no-op placeholder only\n // for the brief window until subscribe() returns the real disposer).\n this.streams.set(deviceToken, stream);\n\n // Subscribe unfiltered by name (name: null) so a device's every reading rides\n // ONE upstream and each subscriber filters to the names it wants \u2014 a chart and\n // a card on the same device share the stream instead of opening two.\n const adapter: SubscriptionSink<MeasurementStreamResult> = {\n next: (data) => this.fanout(deviceToken, data.measurementStream),\n error: (err) => {\n // The upstream is dead. Evict it (and drop the socket-level subscription)\n // so the NEXT subscriber for this device opens a fresh stream instead of\n // attaching to this corpse and freezing silently \u2014 the reconnect path.\n // Guard the delete so a stream that has already been replaced is left be.\n if (this.streams.get(deviceToken) === stream) this.streams.delete(deviceToken);\n stream.unsubscribe();\n for (const subscriber of stream.subscribers) subscriber.sink.error?.(err);\n },\n };\n const variables: MeasurementStreamVariables = { deviceToken, name: null };\n stream.unsubscribe = subscribe(EVENT_AREA, MEASUREMENT_STREAM, variables, adapter);\n\n return stream;\n }\n\n private fanout(deviceToken: string, sample: MeasurementSample): void {\n const stream = this.streams.get(deviceToken);\n if (!stream) return;\n for (const subscriber of stream.subscribers) {\n if (subscriber.names.size === 0 || subscriber.names.has(sample.name)) {\n subscriber.sink.next(sample);\n }\n }\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed alarm documents the hub's alarm channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc \u2014 these\n// are written by hand and cast to TypedDocument. The alarm channel is query-then-\n// reconcile (ADR-041): ALARMS_QUERY reads the authoritative raised-alarm rows\n// (device-management, requires device:read), and ALARM_STREAM is a best-effort\n// (at-most-once) trigger the hub debounces into a re-query \u2014 never the row source of\n// truth. The trigger selects only what proves an event arrived; the rows come from the\n// query.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { AlarmRow } from '../types';\n\n// \u2500\u2500 Query (source of truth) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface AlarmSearchCriteriaInput {\n pageNumber: number;\n pageSize: number;\n originatorType?: string | null;\n originator?: string | null;\n state?: string | null;\n severity?: string | null;\n acknowledged?: boolean | null;\n alarmKey?: string | null;\n}\n\nexport interface AlarmsQueryResult {\n alarms: {\n results: AlarmRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface AlarmsQueryVariables {\n criteria: AlarmSearchCriteriaInput;\n}\n\nexport const ALARMS_QUERY = `\n query DashboardAlarms($criteria: AlarmSearchCriteria!) {\n alarms(criteria: $criteria) {\n results {\n token\n originatorType\n originatorToken\n alarmKey\n metricKey\n state\n acknowledged\n severity\n raisedTime\n clearedTime\n acknowledgedTime\n acknowledgedBy\n lastValue\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<AlarmsQueryResult, AlarmsQueryVariables>;\n\n// \u2500\u2500 Live trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface AlarmStreamResult {\n alarmStream: { alarmToken: string; eventType: string };\n}\n\nexport interface AlarmStreamVariables {\n originatorType?: string | null;\n originator?: string | null;\n state?: string | null;\n severity?: string | null;\n alarmKey?: string | null;\n}\n\nexport const ALARM_STREAM = `\n subscription DashboardAlarmStream(\n $originatorType: String\n $originator: String\n $state: String\n $severity: String\n $alarmKey: String\n ) {\n alarmStream(\n originatorType: $originatorType\n originator: $originator\n state: $state\n severity: $severity\n alarmKey: $alarmKey\n ) {\n alarmToken\n eventType\n }\n }\n` as unknown as TypedDocument<AlarmStreamResult, AlarmStreamVariables>;\n\n// \u2500\u2500 Operator actions (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// acknowledge/clear an alarm by token (device-management, require alarm:write). The\n// acknowledging identity is taken server-side from the authenticated subject; the\n// caller supplies only the token. Only the token is selected back \u2014 the hub reconciles\n// the rows via the query afterward.\n\nexport interface AlarmMutationVariables {\n token: string;\n}\n\nexport interface AlarmMutationResult {\n [key: string]: { token: string };\n}\n\nexport const ACKNOWLEDGE_ALARM = `\n mutation DashboardAcknowledgeAlarm($token: String!) {\n acknowledgeAlarm(token: $token) {\n token\n }\n }\n` as unknown as TypedDocument<AlarmMutationResult, AlarmMutationVariables>;\n\nexport const CLEAR_ALARM = `\n mutation DashboardClearAlarm($token: String!) {\n clearAlarm(token: $token) {\n token\n }\n }\n` as unknown as TypedDocument<AlarmMutationResult, AlarmMutationVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed command documents the hub's control channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc and\n// alarm-doc \u2014 these are written by hand and cast to TypedDocument. The control channel\n// is poll-then-emit: command-delivery exposes NO subscription, so COMMANDS_QUERY is\n// polled (device-scoped) for the live delivery lifecycle, and CREATE_COMMAND issues a\n// new command (requires command:write). The command-button widget bakes its parameter\n// schema from the console at author time; there is no definition query here.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { CommandRow } from '../types';\n\n// \u2500\u2500 Query (lifecycle poll) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CommandSearchCriteriaInput {\n pageNumber: number;\n pageSize: number;\n deviceToken?: string | null;\n // Match a single lifecycle state exactly.\n status?: string | null;\n // Match ANY of several lifecycle states. ANDed with `status` when both are given; an\n // empty list is ignored rather than matching nothing. The states a caller cares about\n // are usually a set \u2014 \"still outstanding\" is QUEUED \u222A HELD \u222A SENT \u222A PARKED, not one\n // value, and \"still cancellable\" is a different set again (SENT is outstanding but\n // cannot be called back).\n statuses?: string[] | null;\n}\n\nexport interface CommandsQueryResult {\n commands: {\n results: CommandRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface CommandsQueryVariables {\n criteria: CommandSearchCriteriaInput;\n}\n\nexport const COMMANDS_QUERY = `\n query DashboardCommands($criteria: CommandSearchCriteria!) {\n commands(criteria: $criteria) {\n results {\n token\n name\n status\n payload\n responsePayload\n error\n queuedTime\n sentTime\n respondedTime\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<CommandsQueryResult, CommandsQueryVariables>;\n\n// \u2500\u2500 Mutation (issue a command) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// createCommand persists a new command to a device (command-delivery, requires\n// command:write). The caller mints a fresh unique `token` per dispatch (the\n// idempotency key + cancel handle); `payload` is the request body the device receives\n// verbatim (the widget serializes its typed form to JSON). Only the token + status are\n// selected back \u2014 the hub re-reads the lifecycle via the poll query afterward.\n//\n// \uD83D\uDD34 THE RESULT HAS TWO ARMS, and they mean opposite things. `command` is the command\n// that was created; `rejection` is a decided REFUSAL of the request (unknown device,\n// command not in the device's published vocabulary, payload violating its parameter\n// schema, malformed JSON/timestamp, the tenant's held-command ceiling full), carrying a\n// stable `code` to branch on and a client-safe `reason` for a person. Exactly one is\n// non-null. A GraphQL error instead of either means the enqueue could not be DECIDED \u2014\n// an availability failure that says nothing about the command \u2014 which is why a\n// rejection is a value here rather than something thrown.\n//\n// The code set is OPEN (the enqueue gate that owns the vocabulary relays its own codes\n// through), so an unrecognized code is still a rejection, never a success.\n\nexport interface CreateCommandRequestInput {\n token: string;\n deviceToken: string;\n name: string;\n payload?: string | null;\n}\n\nexport interface CreateCommandResult {\n createCommand: {\n command: { token: string; status: string } | null;\n rejection: { code: string; reason: string } | null;\n };\n}\n\nexport interface CreateCommandVariables {\n request: CreateCommandRequestInput;\n}\n\nexport const CREATE_COMMAND = `\n mutation DashboardCreateCommand($request: CommandCreateRequest!) {\n createCommand(request: $request) {\n command {\n token\n status\n }\n rejection {\n code\n reason\n }\n }\n }\n` as unknown as TypedDocument<CreateCommandResult, CreateCommandVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed location document the hub's location channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc,\n// alarm-doc and command-doc \u2014 this is written by hand and cast to TypedDocument.\n//\n// The channel is poll-only: device-state exposes NO location subscription, so this is\n// the whole wire. It reads the BATCH form (`latestLocations`, plural), which the\n// service documents as the fleet-map query: one round trip for a whole board's worth\n// of devices instead of one per marker, which is what keeps a 200-device map's poll\n// the same cost as a 2-device map's.\n//\n// \uD83D\uDD34 A device that has never been located is ABSENT from the result rather than\n// present with null coordinates. That is the service's contract, and the channel\n// preserves it: the snapshot reports the devices the selector resolved to alongside\n// the positions, so a widget can tell \"no devices bound\" from \"devices bound, none\n// has ever reported a position\" \u2014 which are different things to tell an operator.\n//\n// Requires location:read, which is deliberately NOT in the read-only viewer baseline,\n// so a refusal here is an ordinary state (see the hub's forbidden handling) rather\n// than a fault.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { LocationSample } from '../types';\n\nexport interface LatestLocationsResult {\n latestLocations: LocationSample[];\n}\n\nexport interface LatestLocationsVariables {\n deviceTokens: string[];\n}\n\nexport const LATEST_LOCATIONS_QUERY = `\n query DashboardLatestLocations($deviceTokens: [String!]!) {\n latestLocations(deviceTokens: $deviceTokens) {\n id\n deviceToken\n latitude\n longitude\n elevation\n accuracy\n speed\n heading\n occurredTime\n }\n }\n` as unknown as TypedDocument<LatestLocationsResult, LatestLocationsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed measurementStream subscription document, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so unlike the console this\n// document is written by hand. The SDK uses documentMode:'string' \u2014 it only ever\n// calls document.toString() and sends the text over graphql-ws \u2014 so a raw\n// GraphQL string carrying phantom result/variable types IS exactly what a\n// generated TypedDocumentString would be at runtime, minus the class wrapper.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { MeasurementSample } from '../types';\n\nexport interface MeasurementStreamResult {\n measurementStream: MeasurementSample;\n}\n\nexport interface MeasurementStreamVariables {\n deviceToken?: string | null;\n name?: string | null;\n}\n\nexport const MEASUREMENT_STREAM = `\n subscription MeasurementStream($deviceToken: String, $name: String) {\n measurementStream(deviceToken: $deviceToken, name: $name) {\n id\n deviceToken\n eventType\n occurredTime\n name\n value\n classifier\n }\n }\n` as unknown as TypedDocument<MeasurementStreamResult, MeasurementStreamVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// SyntheticDataSource \u2014 an offline, client-side data source for the dashboard\n// PREVIEW mode (ADR-039). It implements the same WidgetDataSource contract the live\n// DashboardHub does, so the renderer and widgets consume it unchanged \u2014 but instead\n// of subscribing to backend telemetry it generates values from a chosen waveform.\n// This lets an author validate layout, scales, and thresholds before any device has\n// reported (and it works for ANY selector, including a slot with no binding \u2014 which\n// the live hub renders empty \u2014 because it only reads the datasource's measurement\n// names, never resolves\n// a device).\n\nimport type {\n AlarmStreamSink,\n AlarmSubscription,\n CommandDispatch,\n CommandStreamSink,\n CommandSubscription,\n LocationStreamSink,\n LocationSubscription,\n WidgetActions,\n WidgetDataSource,\n WidgetStreamSink,\n} from './hub';\nimport type {\n AlarmRow,\n CommandRow,\n DatasourceSelector,\n LocationSample,\n MeasurementSample,\n} from './types';\n\n// The waveforms an author can preview with. Sine is the default (smooth, obviously\n// synthetic); ramp is a sawtooth; random-walk drifts within range.\nexport type SyntheticGenerator = 'sine' | 'ramp' | 'random-walk';\n\n// Presentation list for a generator picker (value + human label).\nexport const SYNTHETIC_GENERATORS: ReadonlyArray<{ value: SyntheticGenerator; label: string }> = [\n { value: 'sine', label: 'Sine wave' },\n { value: 'ramp', label: 'Ramp' },\n { value: 'random-walk', label: 'Random walk' },\n];\n\nexport interface SyntheticDataSourceConfig {\n generator?: SyntheticGenerator;\n // Emit cadence in ms (also the backfill spacing). Default 1s.\n intervalMs?: number;\n // How many past points to backfill on subscribe so a chart shows a full waveform\n // immediately instead of drawing in one tick at a time. Default 60.\n backfill?: number;\n // The value range the waveforms span. Default 0..100.\n min?: number;\n // Default 100.\n max?: number;\n // Period of one sine/ramp cycle in ms. Default 60s.\n periodMs?: number;\n}\n\n// A measurement name for a selector that lists none (empty = \"all\" on the live hub);\n// gives cards/gauges/charts something to render in preview.\nconst DEFAULT_NAME = 'value';\n\n// A canonical spread of synthetic alarms (one per severity, mixed state/ack) so an\n// author previewing an alarm table/count sees populated, representative rows before any\n// real alarm has raised. The filter on the subscription is applied so the preview\n// reflects the widget's configured scope-of-interest.\nconst SYNTHETIC_ALARMS: ReadonlyArray<\n Pick<\n AlarmRow,\n 'severity' | 'state' | 'acknowledged' | 'alarmKey' | 'metricKey' | 'lastValue' | 'originatorToken'\n >\n> = [\n { severity: 'CRITICAL', state: 'ACTIVE', acknowledged: false, alarmKey: 'over-temperature', metricKey: 'temperature', lastValue: 87.4, originatorToken: 'thermostat-01' },\n { severity: 'MAJOR', state: 'ACTIVE', acknowledged: true, alarmKey: 'low-battery', metricKey: 'battery', lastValue: 12, originatorToken: 'sensor-14' },\n { severity: 'MINOR', state: 'ACTIVE', acknowledged: false, alarmKey: 'humidity-high', metricKey: 'humidity', lastValue: 78, originatorToken: 'sensor-03' },\n { severity: 'WARNING', state: 'CLEARED', acknowledged: true, alarmKey: 'signal-weak', metricKey: 'rssi', lastValue: -89, originatorToken: 'gateway-02' },\n { severity: 'INDETERMINATE', state: 'ACTIVE', acknowledged: false, alarmKey: 'self-test', metricKey: 'status', lastValue: null, originatorToken: 'device-99' },\n];\n\n// A canonical spread of synthetic commands (one per lifecycle stage) so an author\n// previewing a command-button sees a populated, representative history \u2014 an in-flight\n// command, one withheld for an absent device, one parked after finding nobody home, a\n// completed one, a failure, a cancellation \u2014 before any real command has been issued.\n//\n// `dispatched` says whether this command was ever actually put on the wire, which is\n// what decides sentTime below. It is a per-fixture fact, not something derivable from\n// the status: a CANCELLED command may have been called off while still held (never\n// sent) or after dispatch, and the preview picks the former.\nconst SYNTHETIC_COMMANDS: ReadonlyArray<\n Pick<CommandRow, 'name' | 'status' | 'payload' | 'responsePayload' | 'error'> & { dispatched: boolean }\n> = [\n { name: 'reboot', status: 'SENT', payload: '{\"delaySeconds\":5}', responsePayload: null, error: null, dispatched: true },\n // Early in the list on purpose: an author can cap the widget's row count, and a\n // withheld command is the state a preview most needs to show.\n { name: 'self-test', status: 'HELD', payload: null, responsePayload: null, error: null, dispatched: false },\n // The other way a command ends up waiting on an absent device: this one was published\n // and found nobody there, so it is parked until the device wakes. Included next to HELD\n // because the two look alike in a list and an author laying out a command-button needs\n // to see that they are distinguishable. `dispatched` is false, matching the service: it\n // CLEARS sent_time when it parks a command, because a dispatch that reached nobody sent\n // nothing. That is also why a parked command is still cancellable \u2014 nothing has taken\n // delivery of it.\n { name: 'sync-clock', status: 'PARKED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'set-interval', status: 'SUCCESSFUL', payload: '{\"seconds\":30}', responsePayload: '{\"ok\":true}', error: null, dispatched: true },\n { name: 'calibrate', status: 'QUEUED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'firmware-update', status: 'FAILED', payload: '{\"version\":\"2.1.0\"}', responsePayload: null, error: 'device offline', dispatched: true },\n { name: 'open-valve', status: 'CANCELLED', payload: '{\"percent\":100}', responsePayload: null, error: null, dispatched: false },\n];\n\n// The device a synthetic command-button reports as its target, so the Send control\n// renders (a real widget needs a bound device to issue against).\nconst SYNTHETIC_COMMAND_DEVICE = 'synthetic-device';\n\n// A canonical spread of synthetic positions so an author previewing a map sees markers\n// laid out the way a real fleet would be \u2014 several devices a short distance apart, at a\n// zoom a map actually renders at, rather than one dot in the middle.\n//\n// \uD83D\uDD34 THE OPTIONALS ARE DELIBERATELY MIXED, INCLUDING NULLS AND A GENUINE ZERO. Preview\n// is where an author decides what a marker looks like, so it must show the real range:\n// `sp-loader-03` reports no heading and no speed at all (a receiver that does not supply\n// them), while `sp-dozer-01` reports speed 0 \u2014 parked, which is a reading, not an\n// absence. A preview that filled every optional in would teach an author that a marker\n// always has a heading, and the first real fleet would prove otherwise.\nconst SYNTHETIC_LOCATIONS: ReadonlyArray<\n Pick<LocationSample, 'deviceToken' | 'latitude' | 'longitude' | 'elevation' | 'accuracy' | 'speed' | 'heading'>\n> = [\n { deviceToken: 'sp-dozer-01', latitude: 33.749, longitude: -84.388, elevation: 320.5, accuracy: 4.2, speed: 0, heading: 271.5 },\n { deviceToken: 'sp-excavator-02', latitude: 33.7512, longitude: -84.3858, elevation: 318.1, accuracy: 3.1, speed: 1.4, heading: 88 },\n { deviceToken: 'sp-loader-03', latitude: 33.7468, longitude: -84.3903, elevation: null, accuracy: 9.8, speed: null, heading: null },\n { deviceToken: 'sp-truck-04', latitude: 33.7481, longitude: -84.3841, elevation: 315.9, accuracy: 2.5, speed: 8.3, heading: 12.25 },\n];\n\n// Deterministic small hash of a name \u2192 a stable phase offset, so multiple series on\n// one dashboard are visibly out of phase rather than overlapping identically.\nfunction hashName(name: string): number {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (Math.imul(h, 31) + name.charCodeAt(i)) | 0;\n return h >>> 0;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nexport class SyntheticDataSource implements WidgetDataSource, WidgetActions {\n private readonly generator: SyntheticGenerator;\n private readonly intervalMs: number;\n private readonly backfill: number;\n private readonly min: number;\n private readonly max: number;\n private readonly periodMs: number;\n // Live timers, tracked so disposeAll() can stop every widget's stream at once.\n private readonly timers = new Set<ReturnType<typeof setInterval>>();\n\n constructor(config: SyntheticDataSourceConfig = {}) {\n this.generator = config.generator ?? 'sine';\n // Guard the divisors/counts so a misconfigured host can't produce NaN values\n // (periodMs:0 \u2192 tMs/0) or a zero-delay flood: intervalMs/periodMs floor at 1ms,\n // backfill can't go negative.\n this.intervalMs = Math.max(1, config.intervalMs ?? 1000);\n this.backfill = Math.max(0, config.backfill ?? 60);\n this.min = config.min ?? 0;\n this.max = config.max ?? 100;\n this.periodMs = Math.max(1, config.periodMs ?? 60_000);\n }\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n // Per-name random-walk state, private to this subscription so two widgets don't\n // share (and corrupt) each other's walk.\n const walk = new Map<string, number>();\n let seq = 0;\n\n const emit = (name: string, tMs: number): void => {\n const value = this.valueFor(name, tMs, walk);\n const s: MeasurementSample = {\n id: `syn-${seq++}`,\n deviceToken: 'synthetic',\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value,\n classifier: null,\n };\n sink.next(s);\n };\n\n // Backfill oldest \u2192 newest so the widget window is chronological (and the\n // random-walk advances forward through the backfilled points).\n const now = Date.now();\n for (let i = this.backfill - 1; i >= 0; i--) {\n const tMs = now - i * this.intervalMs;\n for (const name of names) emit(name, tMs);\n }\n\n const timer = setInterval(() => {\n const tMs = Date.now();\n for (const name of names) emit(name, tMs);\n }, this.intervalMs);\n this.timers.add(timer);\n\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeAlarms emits a synthetic alarm snapshot for preview. The canonical set is\n // filtered by the subscription (state/severity/ack) so the preview reflects what the\n // widget is configured to show; scope (datasource) is ignored \u2014 preview never resolves\n // a device. Re-emits on the same cadence with advancing raised times so the table\n // looks live. Returns whole snapshots, matching the live hub's contract.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n const matches = SYNTHETIC_ALARMS.filter(\n (a) =>\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.severity === subscription.severity) &&\n (subscription.acknowledged == null || a.acknowledged === subscription.acknowledged),\n );\n\n const emit = (): void => {\n const now = Date.now();\n const rows: AlarmRow[] = matches.map((a, i) => {\n const raised = new Date(now - i * 45_000).toISOString();\n return {\n token: `syn-alarm-${i}`,\n originatorType: 'device',\n alarmKey: a.alarmKey,\n metricKey: a.metricKey,\n state: a.state,\n acknowledged: a.acknowledged,\n severity: a.severity,\n originatorToken: a.originatorToken,\n lastValue: a.lastValue,\n raisedTime: raised,\n clearedTime: a.state === 'CLEARED' ? raised : null,\n acknowledgedTime: a.acknowledged ? raised : null,\n acknowledgedBy: a.acknowledged ? 'preview@devicechain' : null,\n };\n });\n sink.next({ alarms: rows.slice(0, subscription.pageSize), total: rows.length });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeCommands emits a synthetic command history for preview so a command-button\n // shows a populated, lifecycle-varied list (and a bound target device, so its Send\n // control renders). Re-emits on the same cadence with advancing queued times. Scope\n // (datasource) is ignored \u2014 preview never resolves a device. Returns whole snapshots,\n // matching the live hub's contract.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n const emit = (): void => {\n const now = Date.now();\n const commands: CommandRow[] = SYNTHETIC_COMMANDS.map((c, i) => {\n const queued = new Date(now - i * 20_000).toISOString();\n // respondedTime is NOT \"terminal\" \u2014 it is \"the DEVICE answered\", which is a\n // narrower thing. TIMEOUT, EXPIRED and CANCELLED are all terminal with nothing\n // ever coming back from the device, so stamping them with a response time would\n // teach an author's layout to expect a value the live hub never supplies.\n const answered = c.status === 'SUCCESSFUL' || c.status === 'FAILED';\n return {\n token: `syn-command-${i}`,\n name: c.name,\n status: c.status,\n payload: c.payload,\n responsePayload: c.responsePayload,\n error: c.error,\n queuedTime: queued,\n // Likewise: only a command that actually reached a device has a sentTime. QUEUED,\n // HELD and PARKED never did, and neither did the cancelled one (called off while\n // held).\n sentTime: c.dispatched ? queued : null,\n respondedTime: answered ? queued : null,\n };\n });\n sink.next({\n deviceToken: SYNTHETIC_COMMAND_DEVICE,\n commands: commands.slice(0, subscription.pageSize),\n total: commands.length,\n });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeLocations emits a synthetic position snapshot for preview so a map shows a\n // populated, representative fleet before any device has reported one. Scope\n // (datasource) is ignored \u2014 preview never resolves a device \u2014 but the LOCATION SERIES\n // is honored: a selector that names none gets the empty snapshot, exactly as the live\n // hub gives it, so an author who has not bound the map sees preview agree with\n // production rather than paper over the omission with fake markers.\n //\n // Preview NEVER reports `forbidden`: it reaches no backend, so there is no authority\n // to be refused, and inventing a permission state would show an author a wall their\n // viewers may not actually hit.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n if (!subscription.datasource?.location) {\n sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return () => {};\n }\n\n const emit = (): void => {\n const now = Date.now();\n const locations: LocationSample[] = SYNTHETIC_LOCATIONS.map((l, i) => ({\n id: `syn-location-${i}`,\n deviceToken: l.deviceToken,\n latitude: l.latitude,\n longitude: l.longitude,\n elevation: l.elevation,\n accuracy: l.accuracy,\n speed: l.speed,\n heading: l.heading,\n occurredTime: new Date(now - i * 30_000).toISOString(),\n }));\n sink.next({\n kind: 'positions',\n deviceTokens: locations.map((l) => l.deviceToken),\n locations,\n });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // isDatasourceAvailable \u2014 preview always resolves data (it generates it), so every\n // datasource is \"available\"; an author previewing a template never sees the\n // deleted-device state.\n async isDatasourceAvailable(): Promise<boolean> {\n return true;\n }\n\n // \u2500\u2500 WidgetActions (preview stubs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Preview shows action controls (so an author sees the real layout), so can() is\n // always true; the actions themselves are no-ops \u2014 preview never mutates the backend.\n can(): boolean {\n return true;\n }\n\n async acknowledgeAlarm(): Promise<void> {\n // no-op in preview\n }\n\n async clearAlarm(): Promise<void> {\n // no-op in preview\n }\n\n async sendCommand(): Promise<CommandDispatch> {\n // no-op in preview \u2014 return a stub dispatch token so the widget's optimistic UI works.\n // Preview never refuses: there is no enqueue gate behind it to decide a rejection,\n // and inventing one would be a verdict nothing actually reached.\n return { status: 'sent', token: 'syn-dispatch' };\n }\n\n // disposeAll stops every live stream (e.g. when preview is turned off). Individual\n // widget disposers already clear their own timer; this is the belt-and-braces\n // teardown for the whole source.\n disposeAll(): void {\n for (const timer of this.timers) clearInterval(timer);\n this.timers.clear();\n }\n\n private valueFor(name: string, tMs: number, walk: Map<string, number>): number {\n const span = this.max - this.min;\n const phase = (hashName(name) % 1000) / 1000; // 0..1 of a cycle\n switch (this.generator) {\n case 'ramp': {\n // Sawtooth: fraction of the period (offset per name), rising min\u2192max.\n const frac = ((tMs / this.periodMs + phase) % 1 + 1) % 1;\n return this.min + span * frac;\n }\n case 'random-walk': {\n const prev = walk.get(name) ?? this.min + span / 2;\n const next = clamp(prev + (Math.random() - 0.5) * span * 0.1, this.min, this.max);\n walk.set(name, next);\n return next;\n }\n case 'sine':\n default: {\n const angle = 2 * Math.PI * (tMs / this.periodMs + phase);\n return this.min + span * (0.5 + 0.5 * Math.sin(angle));\n }\n }\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Pure state transforms for the canvas editor. Kept free of React/DOM so the\n// editing logic (move, resize, delete, reorder, retitle, add) is unit-testable;\n// the editor UI (console + the reference /dash app) wires these to react-rnd and\n// the save mutation. Lives here \u2014 next to parse/serialize \u2014 because it is the\n// definition's edit lifecycle, not app-specific glue (ADR-039 authoring in the\n// console).\n//\n// The editor edits the 'base' breakpoint boxes only; per-breakpoint responsive\n// editing is deferred. Boxes are stored as CSS-Grid span placements (col/colSpan/\n// row/rowSpan); the canvas measures its rendered column width and maps boxes to/from\n// pixels (gridBoxToPx / pxToGridBox) so react-rnd drag+resize snap to real grid lines.\n\nimport { BASE_BREAKPOINT, generateWidgetId } from './definition';\nimport type {\n CanvasGrid,\n CanvasSizing,\n DashboardDefinition,\n WidgetBox,\n WidgetInstance,\n WidgetType,\n} from './types';\n\n// The base box a widget is placed by in the editor.\nexport function baseBox(widget: WidgetInstance): WidgetBox {\n return widget.layout[BASE_BREAKPOINT];\n}\n\n// setWidgetBox replaces a widget's base box, returning a new definition.\nexport function setWidgetBox(def: DashboardDefinition, id: string, box: WidgetBox): DashboardDefinition {\n return {\n ...def,\n widgets: def.widgets.map((w) =>\n w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: box } } : w,\n ),\n };\n}\n\n// deleteWidget removes a widget by id.\nexport function deleteWidget(def: DashboardDefinition, id: string): DashboardDefinition {\n return { ...def, widgets: def.widgets.filter((w) => w.id !== id) };\n}\n\n// bringToFront raises a widget above the others (z = current max + 1).\nexport function bringToFront(def: DashboardDefinition, id: string): DashboardDefinition {\n const widget = def.widgets.find((w) => w.id === id);\n if (!widget) return def;\n const box = baseBox(widget);\n // The highest z among the OTHER widgets. Strictly above them all \u2192 already on\n // top (no bump \u2014 this also covers a lone widget at z=0). On a tie, bump past it.\n const maxOther = Math.max(-Infinity, ...def.widgets.filter((w) => w.id !== id).map((w) => baseBox(w).z));\n if (box.z > maxOther) return def;\n return setWidgetBox(def, id, { ...box, z: maxOther + 1 });\n}\n\n// setTitle updates the dashboard title.\nexport function setTitle(def: DashboardDefinition, title: string): DashboardDefinition {\n return { ...def, title };\n}\n\n// setCanvasGrid merges a partial grid change (columns / gap / rowHeight) into the\n// canvas, returning a new definition. It owns the grid invariants (parse-time\n// clamping is asymmetric \u2014 a live edit never round-trips through parse): columns and\n// rowHeight floor to >=1 integers so the renderer never emits `repeat(0,\u2026)` or a\n// fractional/`repeat(12.5,\u2026)` template (which CSS drops wholesale). When columns\n// SHRINKS, existing widgets that now overrun the grid are clamped back inside it \u2014\n// otherwise they'd land in implicit `auto` tracks past the fluid columns (0-width /\n// horizontal overflow in the viewer).\nexport function setCanvasGrid(def: DashboardDefinition, patch: Partial<CanvasGrid>): DashboardDefinition {\n const grid = { ...def.canvas.grid, ...patch };\n if (patch.columns !== undefined) grid.columns = Math.max(1, Math.round(patch.columns));\n if (patch.rowHeight !== undefined) grid.rowHeight = Math.max(1, Math.round(patch.rowHeight));\n const widgets =\n grid.columns < def.canvas.grid.columns ? def.widgets.map((w) => clampWidgetColumns(w, grid.columns)) : def.widgets;\n return { ...def, widgets, canvas: { ...def.canvas, grid } };\n}\n\n// clampWidgetColumns pulls every breakpoint box of a widget back inside `columns`\n// (col <= columns-1, colSpan <= columns-col), leaving rows untouched.\nfunction clampWidgetColumns(widget: WidgetInstance, columns: number): WidgetInstance {\n const layout: WidgetInstance['layout'] = {};\n for (const [bp, box] of Object.entries(widget.layout)) {\n const col = Math.min(box.col, columns - 1);\n layout[bp] = { ...box, col, colSpan: Math.min(box.colSpan, columns - col) };\n }\n return { ...widget, layout };\n}\n\n// setCanvasSizing replaces the container-sizing knob (fill / fixed width / fixed height).\nexport function setCanvasSizing(def: DashboardDefinition, sizing: CanvasSizing): DashboardDefinition {\n return { ...def, canvas: { ...def.canvas, sizing } };\n}\n\n// updateWidget replaces the widget with the matching id, returning a new definition.\nexport function updateWidget(def: DashboardDefinition, id: string, next: WidgetInstance): DashboardDefinition {\n return { ...def, widgets: def.widgets.map((w) => (w.id === id ? next : w)) };\n}\n\n// humanizeType turns a widget type slug into a readable default title\n// ('timeseries-chart' \u2192 'Timeseries chart').\nfunction humanizeType(type: WidgetType): string {\n const words = type.replace(/-/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\n// defaultOptions is the starter options bag for a freshly added widget: labels\n// carry placeholder text, alarm widgets default to the active alarms (the useful\n// operations default), everything else just a title.\nfunction defaultOptions(type: WidgetType): Record<string, unknown> {\n if (type === 'label') return { text: 'New label' };\n if (type === 'alarm-table' || type === 'alarm-count') {\n return { title: humanizeType(type), state: 'ACTIVE' };\n }\n return { title: humanizeType(type) };\n}\n\n// addWidget appends a new default widget of the given type, placed on top\n// (z = max existing z + 1) at a sensible default base box, datasource left\n// undefined. Returns the new definition and the new widget's id so the caller\n// can select it.\nexport function addWidget(\n def: DashboardDefinition,\n type: WidgetType,\n): { definition: DashboardDefinition; id: string } {\n const maxZ = def.widgets.reduce((m, w) => Math.max(m, baseBox(w).z), 0);\n const id = generateWidgetId();\n // A sensible starter tile on a high-res grid: a third of a 24-col canvas, 4 rows.\n const box: WidgetBox = { col: 0, colSpan: 8, row: 0, rowSpan: 4, z: maxZ + 1 };\n const widget: WidgetInstance = {\n id,\n type,\n layout: { [BASE_BREAKPOINT]: box },\n options: defaultOptions(type),\n };\n return { definition: { ...def, widgets: [...def.widgets, widget] }, id };\n}\n\n// A pixel rectangle react-rnd reports after a drag/resize.\nexport interface PixelRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n// The rendered geometry of one grid, in pixels \u2014 what the canvas measures so it can\n// map span boxes to/from the pixel rects react-rnd works in. `colWidth` is the width\n// of one fractional column at the current container width; the gaps are the gutters.\nexport interface GridGeometry {\n colWidth: number;\n colGap: number;\n rowHeight: number;\n rowGap: number;\n}\n\n// gridBoxToPx renders a span box to a pixel rect at the current grid geometry. The\n// stride between track starts is (track + gap); a span of N covers N tracks and the\n// N-1 gaps between them. The signed `offset` (overlap escape hatch) is added last.\nexport function gridBoxToPx(box: WidgetBox, geom: GridGeometry): PixelRect {\n const colStride = geom.colWidth + geom.colGap;\n const rowStride = geom.rowHeight + geom.rowGap;\n const dx = box.offset?.x ?? 0;\n const dy = box.offset?.y ?? 0;\n return {\n x: box.col * colStride + dx,\n y: box.row * rowStride + dy,\n w: box.colSpan * geom.colWidth + (box.colSpan - 1) * geom.colGap,\n h: box.rowSpan * geom.rowHeight + (box.rowSpan - 1) * geom.rowGap,\n };\n}\n\n// pxToGridBox snaps a pixel rect back to a span box, preserving z and any existing\n// offset (drag/resize move on the grid; offset is a hand-edited fine nudge the editor\n// doesn't clobber). Clamped so a widget can't leave the canvas (col,row >= 0) or\n// vanish (spans >= 1); when `columns` is given, also clamped to the RIGHT edge\n// (col <= columns-1, colSpan <= columns-col) so a boundary/offset drag can't commit a\n// box that overruns the grid into implicit tracks. The pixel rect is un-offset first\n// so the snap is grid-relative.\nexport function pxToGridBox(\n px: PixelRect,\n geom: GridGeometry,\n z: number,\n offset?: { x: number; y: number },\n columns?: number,\n): WidgetBox {\n const colStride = Math.max(1, geom.colWidth + geom.colGap);\n const rowStride = Math.max(1, geom.rowHeight + geom.rowGap);\n const x = px.x - (offset?.x ?? 0);\n const y = px.y - (offset?.y ?? 0);\n let col = Math.max(0, Math.round(x / colStride));\n let colSpan = Math.max(1, Math.round((px.w + geom.colGap) / colStride));\n if (columns !== undefined) {\n col = Math.min(col, columns - 1);\n colSpan = Math.min(colSpan, columns - col);\n }\n const box: WidgetBox = {\n col,\n colSpan,\n row: Math.max(0, Math.round(y / rowStride)),\n rowSpan: Math.max(1, Math.round((px.h + geom.rowGap) / rowStride)),\n z,\n };\n if (offset) box.offset = offset;\n return box;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The concrete DeviceResolver \u2014 the device-management-backed implementation of the\n// interface DashboardHub injects. It is the one place the dashboard runtime couples\n// to device-management's schema (the Hub itself couples to event-management's\n// measurementStream), so the widget/renderer layers above stay purely presentational.\n//\n// devicesForAnchor \u2014 entityRelationships filtered to (source=device, target=anchor)\n// \u2192 each relationship's source token is a member device (the Hub\n// opens one measurementStream per token, per ADR-044).\n//\n// Results are memoized per-anchor for the resolver's lifetime: a dashboard resolves\n// the same handful of anchors repeatedly (re-mounts, re-renders), and the membership\n// is stable for a viewing session.\n\nimport { gql } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport {\n DEVICES_BY_TOKEN,\n DEVICES_FOR_ANCHOR,\n type DevicesByTokenResult,\n type EntityRelationshipsResult,\n} from './queries';\nimport type { AnchorTarget } from './types';\n\n// A generous page size for anchor membership \u2014 Phase 1 dashboards anchor to areas\n// with tens of devices, not thousands; server-side aggregation is Phase 2.\nconst ANCHOR_PAGE_SIZE = 500;\n\nexport function createDeviceResolver(): DeviceResolver {\n const anchorCache = new Map<string, Promise<string[]>>();\n // Memoize existence per token for the resolver's lifetime: many widgets on a\n // dashboard bind the same device, and a device's existence is stable for a viewing\n // session (a delete mid-session is rare and the next mount re-checks).\n const existsCache = new Map<string, Promise<boolean>>();\n\n return {\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]> {\n const key = `${anchor.relationship}|${anchor.targetType}|${anchor.targetToken}`;\n let pending = anchorCache.get(key);\n if (!pending) {\n pending = gql('device-management', DEVICES_FOR_ANCHOR, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n sourceType: 'device',\n targetType: anchor.targetType,\n target: anchor.targetToken,\n relationshipType: anchor.relationship,\n },\n })\n .then((r: EntityRelationshipsResult) =>\n r.entityRelationships.results.map((rel) => rel.source.token),\n )\n .catch((err) => {\n anchorCache.delete(key);\n throw err;\n });\n anchorCache.set(key, pending);\n }\n return pending;\n },\n\n // deviceExists caches only a POSITIVE result (a live device is stable for the\n // session); a negative and an error both drop the entry so a later check re-queries.\n // ADR-042 frees a token on delete, so a deleted-then-recreated device must be able to\n // recover on a long-lived viewer (the availability hook re-checks on a timer while a\n // widget shows unavailable) rather than staying stuck \"gone\" for the whole session.\n // The in-flight promise is still shared, so concurrent checks for one token coalesce.\n // (Batching distinct tokens into one devicesByToken call is a deferred optimization \u2014\n // Phase-1 dashboards bind a handful of devices.)\n deviceExists(deviceToken: string): Promise<boolean> {\n let pending = existsCache.get(deviceToken);\n if (!pending) {\n pending = gql('device-management', DEVICES_BY_TOKEN, { tokens: [deviceToken] })\n .then((r: DevicesByTokenResult) => {\n const exists = r.devicesByToken.some((d) => d.token === deviceToken);\n if (!exists) existsCache.delete(deviceToken);\n return exists;\n })\n .catch((err) => {\n // A failure is inconclusive, not \"gone\": drop the entry so the next check\n // retries, and rethrow so the caller fails open (renders available).\n existsCache.delete(deviceToken);\n throw err;\n });\n existsCache.set(deviceToken, pending);\n }\n return pending;\n },\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// History seeding \u2014 turns event-management's bucketedMeasurements aggregates into\n// MeasurementSamples so a chart shows recent history immediately instead of\n// filling in only from live data. Each bucket becomes one synthetic sample at the\n// bucket start carrying its average; the live tail then extends it.\n//\n// Seeds `device` selectors only (single numeric id). Anchor (multi-device) history\n// seeding is deferred \u2014 the live stream still populates anchor charts.\n\nimport { gql } from '@devicechain/client';\n\nimport { BUCKETED_MEASUREMENTS } from './queries';\nimport type { DatasourceSelector, MeasurementSample, SlotBinding, WidgetInstance } from './types';\n\nexport interface HistoryWindow {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n}\n\n// The default backfill: the last hour bucketed per minute (~60 points) \u2014 enough to\n// give a chart shape on load without a heavy query.\nexport function defaultHistoryWindow(): HistoryWindow {\n const now = Date.now();\n return {\n startTime: new Date(now - 60 * 60 * 1000).toISOString(),\n endTime: new Date(now).toISOString(),\n intervalSeconds: 60,\n };\n}\n\n// fetchWidgetHistory returns seed samples for one widget, or [] when it has no\n// device datasource (label/image, or an anchor selector). Never rejects into the\n// render path \u2014 a failed backfill just yields an empty seed and the live stream\n// still fills the widget.\nexport async function fetchWidgetHistory(\n widget: WidgetInstance,\n window: HistoryWindow,\n bindings?: Record<string, SlotBinding>,\n): Promise<MeasurementSample[]> {\n // Resolve a slot selector to its bound entity so a migrated (slot-based) dashboard\n // still backfills \u2014 otherwise every device-turned-slot would lose its history seed.\n // A device binding seeds like a device selector; an anchor binding (or unbound\n // slot) seeds nothing, matching the anchor path below.\n const ds = resolveHistorySelector(widget.datasource, bindings);\n if (!ds || ds.kind !== 'device') return [];\n\n try {\n // measurementStream and bucketedMeasurements are keyed by device token (ADR-044),\n // so the token goes straight into the criteria \u2014 no token\u2192id hop.\n const deviceToken = ds.deviceToken;\n\n // Seed each requested measurement (or all, when the widget lists none).\n const names: Array<string | undefined> = ds.measurements.length ? ds.measurements : [undefined];\n const pages = await Promise.all(\n names.map((name) =>\n gql('event-management', BUCKETED_MEASUREMENTS, {\n criteria: {\n deviceToken,\n name,\n startTime: window.startTime,\n endTime: window.endTime,\n intervalSeconds: window.intervalSeconds,\n },\n }).then((r) => r.bucketedMeasurements),\n ),\n );\n\n return pages\n .flat()\n .filter((b) => b.avg != null)\n .map((b) => ({\n id: `${deviceToken}-${b.name}-${b.bucketStart}`,\n deviceToken,\n eventType: 0,\n occurredTime: b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n }))\n .sort((a, b) => (a.occurredTime! < b.occurredTime! ? -1 : 1));\n } catch {\n return [];\n }\n}\n\n// resolveHistorySelector maps a slot selector to the concrete device/anchor selector\n// its binding points at (carrying the widget's measurement names), so history seeding\n// can treat a bound slot exactly like a direct selector. Non-slot selectors pass\n// through unchanged; an unbound slot or an anchor binding yields a non-device selector\n// that the caller then seeds as empty.\nfunction resolveHistorySelector(\n ds: DatasourceSelector | undefined,\n bindings?: Record<string, SlotBinding>,\n): DatasourceSelector | undefined {\n if (!ds || ds.kind !== 'slot') return ds;\n // Own-property lookup (a slot could be named 'constructor' etc.); an unbound slot\n // resolves to undefined \u2192 empty seed.\n const binding =\n bindings && Object.prototype.hasOwnProperty.call(bindings, ds.slot) ? bindings[ds.slot] : undefined;\n if (binding?.kind === 'device') {\n return { kind: 'device', deviceToken: binding.deviceToken, measurements: ds.measurements };\n }\n if (binding?.kind === 'anchor') {\n return { kind: 'anchor', anchor: binding.anchor, measurements: ds.measurements };\n }\n return undefined; // unbound slot \u2192 no seed\n}\n"],
5
- "mappings": "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",
6
- "names": ["ownGet", "sameBinding", "rows", "gql", "gql", "gql", "gql", "gql", "gql"]
3
+ "sources": ["../src/command-status.ts", "../src/types.ts", "../src/widget-compat.ts", "../src/definition.ts", "../src/bindings.ts", "../src/context.ts", "../src/slots.ts", "../src/candidates.ts", "../src/entity-lister.ts", "../src/queries.ts", "../src/hub.ts", "../src/internal/alarm-doc.ts", "../src/internal/command-doc.ts", "../src/internal/location-doc.ts", "../src/internal/measurement-doc.ts", "../src/synthetic.ts", "../src/recording.ts", "../src/recorded.ts", "../src/editor-model.ts", "../src/resolver.ts", "../src/history.ts", "../src/option-schema.ts", "../src/widget-descriptor.ts", "../src/widget-catalog.ts"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The command-delivery lifecycle vocabulary, as the frontend sees it.\n//\n// \uD83D\uDD34 THIS IS THE ONE COPY. It lives in `dashboards` rather than `widgets` because\n// `widgets` depends on `dashboards` (never the reverse), so this is the lowest layer\n// both the widget package and the console can reach. It previously existed as three\n// hand-maintained sets \u2014 the console's DeviceCommandsPanel, the widgets' command-status\n// helper, and an ad-hoc expression in the synthetic preview \u2014 which is exactly how the\n// console kept offering a Cancel button on an already-cancelled command: a status was\n// added to the service and only some of the copies learned about it.\n//\n// The service declares no GraphQL enum for status, so it crosses the wire as a plain\n// string and an UNRECOGNIZED value must stay survivable: nothing here throws on one. What\n// an unknown status MEANS differs per question, though \u2014 see the two predicates at the\n// bottom, which answer for it in opposite directions on purpose.\n//\n// \uD83D\uDD34 \"IN FLIGHT\" AND \"CANCELLABLE\" ARE NOT THE SAME SET, and reading one off the other is\n// the bug this file is shaped to prevent. SENT is in flight (the device has not answered\n// yet) but NOT cancellable: the command is already at the device, and letting the platform\n// call it off would only make it discard the real answer that is on its way back.\n\n// Every lifecycle state the command-delivery service can persist. Ordered as a command\n// travels: the non-terminal states first, then the terminal ones.\nexport const COMMAND_STATUSES = [\n // \u2500\u2500 Non-terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Accepted; awaiting its first dispatch decision. Genuinely transient.\n 'QUEUED',\n // The platform is deliberately WITHHOLDING dispatch because the device is known\n // absent. This is where an offline fleet's backlog accumulates \u2014 it can sit for days,\n // and it is the honest answer to \"why hasn't my command arrived?\".\n 'HELD',\n // Dispatched toward a device believed reachable; awaiting its response.\n 'SENT',\n // Published, and it went NOWHERE: the device turned out not to be reachable, so the\n // transport had nothing to hand it to. The platform still holds the command and will\n // deliver it when the device next wakes. Distinct from HELD (where dispatch was never\n // attempted, because the device was already known absent) and from TIMEOUT (where the\n // command reached a device that then said nothing).\n 'PARKED',\n // \u2500\u2500 Terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // The device answered.\n 'SUCCESSFUL',\n 'FAILED',\n // Dispatched, never answered in time.\n 'TIMEOUT',\n // The TTL elapsed before it ever went out.\n 'EXPIRED',\n // An operator or tenant called it off. Distinct from EXPIRED because it is a\n // different ACTOR, not a different outcome. Cancellation used to write EXPIRED, so\n // BOTH values appear in real data \u2014 historical rows are not backfilled.\n 'CANCELLED',\n] as const;\n\nexport type CommandStatus = (typeof COMMAND_STATUSES)[number];\n\n// The states a command can never leave: no further transition is permitted, so nothing\n// more will happen to it. Terminal implies not cancellable; the converse does NOT hold.\nexport const TERMINAL_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'SUCCESSFUL',\n 'FAILED',\n 'TIMEOUT',\n 'EXPIRED',\n 'CANCELLED',\n]);\n\n// The states from which the platform will still accept a cancellation \u2014 a POSITIVE list,\n// mirroring the service's own gate. All three are states in which the command has not\n// reached the device: QUEUED (not yet dispatched), HELD (dispatch withheld), PARKED\n// (published into a void and waiting for the device to wake). Cancelling any of them\n// takes back something nobody else is holding.\n//\n// SENT is deliberately absent. It is in flight, so !isTerminalCommandStatus('SENT') is\n// true \u2014 which is exactly why a cancel control must not be gated on that expression.\nexport const CANCELLABLE_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'QUEUED',\n 'HELD',\n 'PARKED',\n]);\n\n// \uD83D\uDD34 THE TWO PREDICATES ANSWER FOR AN UNRECOGNIZED STATUS IN OPPOSITE DIRECTIONS, on\n// purpose. Status crosses the wire as a plain string with no GraphQL enum behind it, so a\n// value this build has never heard of is always reachable \u2014 a newer service, a hand-edited\n// row \u2014 and each question has its own safe side:\n//\n// isTerminalCommandStatus negative membership \u21D2 unknown reads NON-terminal, i.e.\n// STILL IN FLIGHT. A status the service adds is one it added\n// because something is still happening to the command, and\n// the cost of guessing wrong is only a row that lingers in an\n// \"outstanding\" list instead of settling.\n//\n// isCancellableCommandStatus positive membership \u21D2 unknown reads NOT CANCELLABLE. This\n// file used to argue the other way \u2014 that refusing to offer\n// cancel for a state we do not recognize would strand a live\n// command \u2014 and that argument is now false. The service gates\n// cancellation on the same positive list, and \uD83D\uDD34 OUTSIDE THAT\n// LIST IT DOES NOT REFUSE: it SUCCEEDS and returns the command\n// UNCHANGED. So a Cancel button offered on an unknown status is\n// a no-op that answers the click with a SUCCESS TOAST for a\n// command nobody cancelled \u2014 a worse outcome than an error,\n// because the operator has no way to tell. The command is\n// stranded either way; this way we do not also claim otherwise.\n// A host that offers the button anyway must therefore check the\n// status that CAME BACK, not the fact that the call resolved.\nexport function isTerminalCommandStatus(status: string): boolean {\n return TERMINAL_COMMAND_STATUSES.has(status);\n}\n\n// isCancellableCommandStatus reports whether a cancel request would be accepted. Gate every\n// cancel control on THIS, never on !isTerminalCommandStatus \u2014 the two differ by SENT, and\n// by every status added after this build shipped.\nexport function isCancellableCommandStatus(status: string): boolean {\n return CANCELLABLE_COMMAND_STATUSES.has(status);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The dashboard-definition contract (ADR-039). These types are the canonical\n// shape of the JSON document that dashboard-management stores opaquely \u2014 the\n// backend only validates it is well-formed JSON, so this package owns the shape.\n// Kept deliberately fluid pre-GA. See research/dashboard-phase1-design-2026-07.md.\n\n// ---- Datasource selectors ---------------------------------------------------\n//\n// A selector is a tagged union (discriminated on `kind`). The Hub resolves\n// `device` and `anchor`, and treats `unbound` as zero devices. A `slot` never reaches\n// it: the renderer resolves each slot through the settled bindings first (to a device,\n// an anchor, or `unbound`). The remaining kinds are reserved \u2014 present so definitions\n// stay forward-compatible, but the Hub rejects them until implemented.\n\n// ---- Location series selection (ADR-078 decision 9) -------------------------\n//\n// A selector's `measurements: string[]` names NAMED SCALAR SERIES, and a position is\n// not one of those: it is an atomic triple on its own event class and its own\n// hypertable, deliberately never a metric. Naming a location through `measurements`\n// would therefore be a lie the whole way down \u2014 the hub would resolve it against the\n// measurement stream, which carries no coordinates at all.\n//\n// So a selector names a location series in its own field. The vocabulary is CLOSED\n// rather than free text: a device has exactly one position track, so there is nothing\n// to disambiguate by name, and an open string would invite an author to type a\n// measurement name here and get silence.\n//\n// 'latest' \u2014 the last-known position, read from device-state's O(1) projection.\n//\n// A second member ('track', the position history from event-management) is the shape\n// this exists to leave room for; the map widget reads 'latest' only.\nexport type LocationSeries = 'latest';\n\n// The location series a selector names. An OBJECT rather than a bare string so a\n// future member can carry its own parameters (a track's window) without changing\n// every selector again.\nexport interface LocationSelection {\n series: LocationSeries;\n}\n\n// device \u2014 one device's measurements (latest-card, gauge, single-series chart), and/or\n// its position when the selector names a location series.\nexport interface DeviceSelector {\n kind: 'device';\n deviceToken: string;\n measurements: string[];\n // Additive and OPTIONAL, deliberately: every dashboard definition stored before the\n // location channel existed carries no `location`, and must keep parsing and\n // rendering exactly as it did. Absence means \"this selector names no location\n // series\", which is the correct reading of every one of them.\n location?: LocationSelection;\n}\n\n// The dimension a dashboard aggregates over \u2014 a tracked relationship to a\n// customer / area / asset. An anchor is not an opaque alias: it is a graph edge\n// the platform already resolves events against.\nexport interface AnchorTarget {\n relationship: string;\n targetType: 'customer' | 'area' | 'asset';\n targetToken: string;\n}\n\nexport interface AnchorAggregation {\n window: string; // e.g. '1m'\n fn: 'avg' | 'min' | 'max' | 'sum' | 'count';\n}\n\n// anchor \u2014 an anchor dimension aggregated over its member devices.\nexport interface AnchorSelector {\n kind: 'anchor';\n anchor: AnchorTarget;\n measurements: string[];\n location?: LocationSelection;\n // RESERVED (Phase 2): server-side aggregation of the member devices into one\n // series. Phase 1 resolves an anchor to its member devices and streams each\n // one's raw samples; the Hub does NOT read this field yet. Present so a\n // definition authored against the final shape still round-trips.\n aggregation?: AnchorAggregation;\n}\n\n// ---- Reserved selectors (Phase 2) -------------------------------------------\n// Schema-valid and part of the union so a stored definition never fails to parse,\n// but the Hub throws \"not supported yet\" until Phase 2 wires them.\n\nexport interface DevicesSelector {\n kind: 'devices';\n deviceTokens: string[];\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport interface RelatedTraversalSelector {\n kind: 'relatedTraversal';\n from: string;\n relationship: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// slot \u2014 a NAMED reference into the dashboard's `slots` section, resolved to a\n// concrete entity at MOUNT by the host's binding manifest (ADR-039 runtime\n// binding). This makes a definition a reusable TEMPLATE: the widget names the\n// entity role (`slot`) and the measurements it wants, and two mounts of the same\n// definition can bind the slot to two different devices. The RENDERER resolves it\n// through the settled bindings (a slot's defaultBinding, overridable by the host and\n// by a selection) before the data source sees it, so re-pointing a slot re-subscribes\n// only the widgets bound to it; an unbound slot becomes an `unbound` selector and\n// renders as an empty placeholder.\nexport interface SlotSelector {\n kind: 'slot';\n slot: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// unbound \u2014 a slot that has NO binding, as the renderer hands it to a data source.\n//\n// It is never authored: the renderer produces it when it resolves a widget's `slot`\n// selector through the settled bindings and finds nothing there (see\n// resolveWidgetDatasource). Every live channel resolves it to ZERO devices \u2014 no stream,\n// an empty alarm snapshot, no command target, no positions, and no availability check.\n//\n// \uD83D\uDD34 It is the OPPOSITE of an absent datasource, and the difference is not cosmetic. A\n// widget with no datasource at all is tenant-wide on the alarm channel, so an unbound slot\n// that collapsed into `undefined` would show every alarm in the tenant under a subtitle\n// that implies one machine. Unbound is therefore a resolved state of its own, never a\n// missing one.\n//\n// It carries the widget's series (measurement names, and the location series when one is\n// named) so an offline preview source, which never resolves a device, can still fabricate\n// the values a widget asks for. The live hub reads none of it.\nexport interface UnboundSelector {\n kind: 'unbound';\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport type DatasourceSelector =\n | DeviceSelector\n | AnchorSelector\n | DevicesSelector\n | RelatedTraversalSelector\n | SlotSelector\n | UnboundSelector;\n\n// ---- Canvas + widgets -------------------------------------------------------\n\n// The built-in widget types. Kept as a runtime array so parse/validation and the\n// widget registry share one source of truth (WidgetType is derived from it).\n//\n// Widgets fall into data CHANNELS (see WIDGET_CHANNEL in @devicechain/widgets): the\n// measurement widgets stream telemetry samples; the alarm widgets consume the raised-\n// alarm surface (ADR-041) via the hub's alarm channel; the control widget (command-\n// button) issues commands and tracks their delivery lifecycle via the command channel;\n// the map widget reads device positions via the location channel (ADR-078 decision 9).\n// A widget's channel decides which hook/registry the renderer binds it through.\nexport const WIDGET_TYPES = [\n 'timeseries-chart',\n 'latest-card',\n 'gauge',\n 'table',\n 'label',\n 'image',\n 'alarm-table',\n 'alarm-count',\n 'command-button',\n 'entity-selector',\n 'map',\n] as const;\n\nexport type WidgetType = (typeof WIDGET_TYPES)[number];\n\n// Grid placement + z-order \u2014 CSS-Grid-native layout (ADR-039 amendment 2026-07-08).\n// A widget is placed by SPAN on the canvas grid: `col`/`row` are its 0-based start\n// line and `colSpan`/`rowSpan` how many tracks it covers \u2014 mapping to\n// `grid-column: col+1 / span colSpan` (rows likewise). Because the columns are\n// fractional (`repeat(columns, 1fr)`), the same box fills whatever width its\n// container gives it \u2014 no pixel width baked into the definition. Layering is native\n// via `z`; `offset` is a signed-pixel escape hatch for pixel-perfect nudge/overlap\n// beyond the grid lines (Derek's \"margin and -margin\").\nexport interface WidgetBox {\n col: number;\n colSpan: number;\n row: number;\n rowSpan: number;\n z: number;\n offset?: { x: number; y: number };\n}\n\n// Per-breakpoint placement, keyed by breakpoint name. 'base' is required; a\n// widget omitting a breakpoint inherits its 'base' box.\nexport type WidgetLayout = Record<string, WidgetBox>;\n\n// The type a widget is given when its stored type is not one this build knows \u2014 one a\n// newer release added. Deliberately NOT a member of WIDGET_TYPES: it is never authored,\n// never offered in an add menu, and has no row in any per-type table. See\n// UnknownWidgetInstance.\nexport const UNKNOWN_WIDGET_TYPE = 'unknown-widget';\nexport type UnknownWidgetType = typeof UNKNOWN_WIDGET_TYPE;\n\n// The implementation a widget was authored against, stored beside its type as `widget`.\n// `version` is provenance (the widget release the author used); `optionsVersion` and\n// `sdkApiVersion` are the compatibility key together with the id \u2014 see checkWidgetCompatibility.\n// A definition predating this field is a legacy built-in and carries none.\nexport interface WidgetImplementationRef {\n version: string;\n optionsVersion: number;\n sdkApiVersion?: number;\n}\n\n// A namespaced id (`scope/name`) a registered extension package owns. Kept apart from\n// WidgetType so the built-in union stays exhaustive for the compiler.\nexport type ExtensionWidgetType = `${string}/${string}`;\n\n// A widget of a type this build knows how to render.\nexport interface KnownWidgetInstance {\n id: string;\n type: WidgetType;\n // Absent on a legacy (unversioned) built-in.\n widget?: WidgetImplementationRef;\n layout: WidgetLayout;\n // label/image widgets carry no datasource.\n datasource?: DatasourceSelector;\n // Widget-specific options (series colors, unit, thresholds, \u2026). Owned by the\n // widget package; opaque here.\n options?: Record<string, unknown>;\n raw?: undefined;\n reason?: undefined;\n}\n\n// A widget from an installed extension package, recognized through the catalog the\n// definition was parsed with: registered, versioned, and compatible (optionsVersion and\n// SDK generation). Same envelope as a built-in; the id is a namespaced one.\nexport interface ExtensionWidgetInstance {\n id: string;\n type: ExtensionWidgetType;\n widget: WidgetImplementationRef;\n layout: WidgetLayout;\n datasource?: DatasourceSelector;\n options?: Record<string, unknown>;\n raw?: undefined;\n reason?: undefined;\n}\n\n// Why a stored widget could not be resolved to an installed implementation.\n// unregistered - no registration for the id in the catalog (or the type is unknown)\n// unversioned - a registered extension stored without version metadata\n// invalid-metadata - `widget` is present but malformed, or an extension widget uses a\n// top-level field name the format reserves (`raw`, `reason`)\n// options-version - the stored settings shape differs from the installed one\n// sdk-incompatible - authored against a different SDK contract generation\nexport type UnresolvedWidgetCode =\n | 'unregistered'\n | 'unversioned'\n | 'invalid-metadata'\n | 'options-version'\n | 'sdk-incompatible';\n\nexport interface UnresolvedWidgetReason {\n code: UnresolvedWidgetCode;\n // A human-readable, specific explanation (names the id and the versions involved).\n message: string;\n}\n\n// A widget that cannot be resolved to an installed implementation. The parser keeps the\n// stored object VERBATIM in `raw`, and the serializer writes `raw` back, so a host on an\n// older release or without the package that loads a board and saves it does not delete or\n// rewrite the widget it cannot read.\n//\n// `id` and `layout` are the only parsed fields, because every widget has them and the\n// canvas needs them to place, select, move and delete the placeholder. An edit to\n// either is written back into `raw` on save; nothing else about the widget is ever\n// read or changed. It carries no datasource and no options: whatever the widget\n// binds lives in `raw`, where no hub channel can mistake it for a selector this build\n// understands. `reason` says why it is unresolved (derived at parse time, never stored).\nexport interface UnknownWidgetInstance {\n id: string;\n type: UnknownWidgetType;\n layout: WidgetLayout;\n raw: Record<string, unknown>;\n reason?: UnresolvedWidgetReason;\n widget?: undefined;\n datasource?: undefined;\n options?: undefined;\n}\n\nexport type WidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance | UnknownWidgetInstance;\n\n// A widget some installed implementation resolved: a built-in or a registered extension, i.e.\n// everything but a placeholder. What an editor panel is typed against.\nexport type ResolvedWidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance;\n\nexport interface CanvasBackground {\n color?: string | null;\n imageUrl?: string | null;\n}\n\n// The canvas grid (ADR-039 amendment 2026-07-08). `columns` is high-resolution\n// (24\u201348) so span placement stays near-free, which is what makes the canvas-first\n// layout possible rather than a fixed grid; `gap` is the gutter, one value or a {row,col} pair;\n// `rowHeight` is the fixed pixel height of each implicit grid row. Columns are\n// fluid (`repeat(columns, 1fr)`) so the board fills its container width; rows are\n// fixed-px (the `aspect` mode \u2014 rowHeight = colWidth \u00D7 ratio \u2014 is the documented\n// fast-follow).\nexport interface CanvasGrid {\n columns: number;\n gap: number | { row: number; col: number };\n rowHeight: number;\n}\n\n// How the grid is sized into its container \u2014 a MOUNT/embed knob the host may\n// override (renderer `sizing` prop). `fill`: the grid fills the container's width\n// (the default; the \"fit into an area\" case). `{width}`: a fixed-px-wide container\n// the fluid grid adjusts within. `{height}`: a fixed-px-tall container (rows scroll).\nexport type CanvasSizing = 'fill' | { width: number } | { height: number };\n\n// Breakpoint name \u2192 min viewport width in px. 'base' is required.\nexport type Breakpoints = Record<string, number>;\n\nexport interface Canvas {\n background?: CanvasBackground;\n grid: CanvasGrid;\n sizing: CanvasSizing;\n breakpoints: Breakpoints;\n}\n\n// A concrete entity a slot resolves to \u2014 a device (by token) or an anchor target.\n// This is the ENTITY only; the measurement names stay on the widget's SlotSelector\n// (so a shared slot can feed different widgets different measurements). A slot's\n// default binding lives in its SlotDefinition; the host's mount-time manifest can\n// override it (see effectiveBindings).\nexport type SlotBinding =\n | { kind: 'device'; deviceToken: string }\n | { kind: 'anchor'; anchor: AnchorTarget };\n\n// A slot's dependency on a PARENT slot \u2014 the scoped-slot context hierarchy (ADR-039\n// selection amendment). A scoped slot resolves RELATIVE to its parent's current\n// binding: `parent` names an anchor-typed slot whose member devices are this slot's\n// candidate set, and `strategy` picks among them:\n// 'first' \u2014 auto-bind the parent's first member (ordered by token); zero members \u21D2\n// unbound. The slot follows the parent with no user input.\n// 'manual' \u2014 keep the current pick iff it is still a member of the (possibly changed)\n// parent, else reset to unbound; a picker offers only the parent's members.\n// A slot with no `scope` is a ROOT context (the top-level entity, e.g. a building). The\n// relationship used to enumerate members is the parent anchor's own `relationship` (no\n// separate field). Single parent \u21D2 the slots form a forest (no diamonds), so the cascade\n// order is well-defined. Replaces the reserved `entityFromState` selector.\nexport interface SlotScope {\n parent: string;\n strategy: 'first' | 'manual';\n}\n\n// A named entity role a dashboard declares and its widgets reference via a\n// SlotSelector. The host's binding manifest maps each slot to a concrete device or\n// anchor at mount; `defaultBinding` is the slot's own binding (set by the authoring\n// tenant) used when the host supplies no override \u2014 so a dashboard renders\n// immediately for its author AND is export-ready as a template (strip the defaults,\n// the host rebinds).\nexport interface SlotDefinition {\n type: 'device' | 'anchor';\n // Human-readable name shown in the binding UI, e.g. 'Primary thermostat'.\n label?: string;\n // The slot's default entity binding (the author's tenant); a host manifest overrides.\n // For a SCOPED slot this is a FALLBACK only \u2014 the cascade (resolveContextBindings)\n // derives the effective binding from the parent and always supersedes it (a `first`\n // slot ignores it; a `manual` slot uses it as the initial pick, kept iff a member).\n defaultBinding?: SlotBinding;\n // Optional dependency on a parent slot (the context hierarchy). Absent = a root context.\n scope?: SlotScope;\n}\n\n// The entity kinds a root context-selector can list (the anchor target types + a bare\n// device). A scoped child selector does NOT use this \u2014 its candidates are the parent\n// anchor's member devices, enumerated via the DeviceResolver, not a list query.\nexport type EntityListKind = 'device' | 'customer' | 'area' | 'asset';\n\n// EntityCandidateLister lists the tenant's entities of one kind (token + optional name),\n// backing a ROOT context-selector's candidate set. Injected by the host (createEntityLister)\n// so the widgets/dashboards packages carry no extra device-management coupling beyond the\n// one place (resolver.ts) that already owns it. A flat list \u2014 nested customer\u2192area\u2192asset\n// tree picking is deferred.\nexport type EntityCandidateLister = (\n kind: EntityListKind,\n) => Promise<Array<{ token: string; name?: string | null }>>;\n\n// One option a context/entity-selector widget offers: the binding it would set, a display\n// label, and whether it is the slot's current pick. Produced by resolveSlotCandidates.\nexport interface SelectionCandidate {\n binding: SlotBinding;\n label: string;\n selected: boolean;\n}\n\n// SelectionTarget is one view-driven selection: bind slot `slot` to `binding` (a device\n// drill from an alarm originator, a context-selector pick). The host accumulates these\n// into a selection overlay that the cascade resolves on top of the slot defaults. Named\n// its TARGET SLOT (no channels \u2014 the slot IS the channel; the cascade fans a parent\n// change out to its scoped children).\nexport interface SelectionTarget {\n slot: string;\n binding: SlotBinding;\n}\n\nexport interface DashboardDefinition {\n schemaVersion: number;\n title: string;\n canvas: Canvas;\n widgets: WidgetInstance[];\n // Named entity roles bound at mount (ADR-039 runtime binding). Optional \u2014 absent\n // on a dashboard that uses no slots.\n slots?: Record<string, SlotDefinition>;\n}\n\n// ---- Live telemetry ---------------------------------------------------------\n\n// A live measurement sample delivered to a widget. Mirrors event-management's\n// MeasurementEvent; `deviceToken` is the device token the event carries (the\n// value measurementStream is keyed on, per ADR-044).\nexport interface MeasurementSample {\n id: string;\n deviceToken: string;\n eventType: number;\n occurredTime: string | null;\n name: string;\n value: number | null;\n classifier: string | null;\n}\n\n// A raised alarm as an alarm widget sees it (ADR-041). Mirrors device-management's\n// stored Alarm row (the source of truth), NOT the transient AlarmEvent envelope \u2014 the\n// hub's alarm channel treats the live stream as a reconcile trigger and re-reads the\n// authoritative rows via the alarms query. `token` is the stable alarm token (the row\n// key + the ack/clear handle); `originatorToken` is the device token when the\n// originator is a device (null otherwise). Kept decoupled from the device-management\n// GraphQL types so the widget layer carries no service coupling.\nexport interface AlarmRow {\n token: string;\n originatorType: string;\n originatorToken: string | null;\n alarmKey: string;\n metricKey: string;\n state: string; // ACTIVE | CLEARED\n acknowledged: boolean;\n severity: string; // CRITICAL | MAJOR | MINOR | WARNING | INDETERMINATE\n raisedTime: string | null;\n clearedTime: string | null;\n acknowledgedTime: string | null;\n acknowledgedBy: string | null;\n lastValue: number | null;\n}\n\n// ---- Locations (location channel) -------------------------------------------\n\n// A device's position as a map widget sees it. Mirrors device-state's LatestLocation\n// projection row (the O(1) \"where is it now\" answer, not a scan of the history), kept\n// decoupled from the device-state GraphQL types so the widget layer carries no service\n// coupling \u2014 the same rule the alarm and command rows follow.\n//\n// \uD83D\uDD34 EVERY OPTIONAL IS NULLABLE AND ABSENT IS NOT ZERO. A device reports what its\n// receiver knows: a fix with no heading has not reported due north, one with no speed\n// has not reported stationary, one with no elevation has not reported sea level.\n// Rendering any of those as 0 would invent a fact the platform deliberately declined\n// to store, and the operator could not tell the invented value from a real one. Every\n// consumer tests `!= null` (which admits a genuine 0 and rejects an absence) and shows\n// NOTHING for an absent field.\n//\n// latitude/longitude are typed nullable because the projection's columns are \u2014 a row\n// is never written without them, but the schema does not promise it, and a consumer\n// that assumed non-null would place a marker at (0, 0), the Gulf of Guinea, rather\n// than declining to place one at all.\n//\n// Units are fixed platform-wide and never per-device: WGS84 decimal degrees;\n// elevation and accuracy in metres; speed in metres per second; heading in degrees\n// clockwise from true north, [0, 360).\nexport interface LocationSample {\n id: string;\n deviceToken: string;\n latitude: number | null;\n longitude: number | null;\n elevation: number | null;\n accuracy: number | null;\n speed: number | null;\n heading: number | null;\n occurredTime: string | null;\n}\n\n// ---- Commands (control channel) ---------------------------------------------\n\n// A device command as the command-button widget sees it (command-delivery). Mirrors\n// the stored Command row: `token` is the client-minted dispatch id (also the cancel\n// handle); `status` carries the lifecycle state as a plain string \u2014 the service\n// declares no GraphQL enum, which is also why an unrecognized value has to stay\n// survivable here. Kept decoupled from command-delivery's GraphQL types so the widget\n// layer carries no service coupling.\n//\n// The lifecycle, in full (see ./command-status for the machine-readable copy):\n//\n// non-terminal QUEUED accepted, awaiting its first dispatch decision\n// HELD dispatch deliberately withheld \u2014 the device is known absent,\n// so the command waits rather than going into the void; this is\n// where an offline fleet's backlog sits, possibly for days\n// SENT dispatched toward a reachable device, awaiting its response\n// PARKED published, but the device was not reachable, so it went\n// nowhere; the platform still holds it and delivers it when the\n// device wakes\n// terminal SUCCESSFUL / FAILED the device answered\n// TIMEOUT dispatched, never answered\n// EXPIRED TTL elapsed before it ever went out\n// CANCELLED an operator or tenant called it off\n//\n// EXPIRED and CANCELLED are separate ACTORS reaching the same dead end, not two names\n// for one thing. Cancellation used to write EXPIRED and old rows were never backfilled,\n// so both values legitimately appear in live data.\n//\n// Non-terminal is NOT the same as cancellable \u2014 SENT is the first and not the second (see\n// ./command-status). A surface offering a cancel control must ask\n// isCancellableCommandStatus rather than negating isTerminalCommandStatus.\nexport interface CommandRow {\n token: string;\n name: string;\n // QUEUED | HELD | SENT | PARKED | SUCCESSFUL | FAILED | TIMEOUT | EXPIRED | CANCELLED\n status: string;\n payload: string | null; // request JSON (as issued)\n responsePayload: string | null; // device response JSON\n error: string | null;\n queuedTime: string | null;\n sentTime: string | null;\n respondedTime: string | null;\n}\n\n// The scalar value types a command parameter can carry, reusing device-management's\n// MetricDataType vocabulary (ADR-016).\nexport type CommandParamDataType = 'DOUBLE' | 'INT' | 'BOOLEAN' | 'STRING';\n\n// One descriptor in a CommandDefinition's parameter schema (ADR-043). The console\n// bakes the selected command's parsed schema into the widget's options at author time;\n// the command-button widget renders it as a typed form. A SCALAR parameter is a single\n// typed value (dataType + optional unit/bounds/enum/default); an OBJECT parameter nests\n// a child `parameters` list. `kind` absent means SCALAR. Deliberately structural (no\n// device-management import) so the widget package stays decoupled.\nexport interface CommandParameter {\n name: string;\n description?: string;\n kind?: 'SCALAR' | 'OBJECT';\n dataType?: CommandParamDataType;\n unit?: string;\n required?: boolean;\n default?: string | null;\n minValue?: number | null;\n maxValue?: number | null;\n enum?: string[];\n parameters?: CommandParameter[];\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Whether a stored widget can be rendered by an installed implementation.\n//\n// The compatibility key is (id, optionsVersion, sdkApiVersion). `version` is PROVENANCE: a\n// differing release still renders, and the editor shows a non-blocking \"authored with X,\n// rendering with Y\" notice (widgetVersionNotice). Only a settings-shape or SDK-generation\n// difference turns a widget into a placeholder, because only those change what the stored\n// options MEAN. \"Exact replay\" is claimed only when the versions are equal.\n//\n// React-free, and it reads a WidgetCatalog (descriptors), never an implementation, so the\n// parser and the headless validators can use it.\n\nimport type { WidgetCatalog } from './widget-catalog';\nimport type { WidgetDescriptor } from './widget-descriptor';\nimport type {\n DashboardDefinition,\n ResolvedWidgetInstance,\n UnresolvedWidgetReason,\n WidgetImplementationRef,\n WidgetInstance,\n} from './types';\nimport { UNKNOWN_WIDGET_TYPE, WIDGET_TYPES, type KnownWidgetInstance } from './types';\n\nconst BUILTIN_TYPES: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\n// isBuiltinWidget narrows a parsed widget to a built-in instance: not a placeholder and not\n// an extension. For code (the built-in editor panels) that is typed against the closed\n// built-in union and must not be handed an extension widget.\nexport function isBuiltinWidget(widget: WidgetInstance): widget is KnownWidgetInstance {\n return BUILTIN_TYPES.has(widget.type);\n}\n\n// isResolvedWidget narrows a parsed widget to one an installed implementation resolved (a\n// built-in or a registered extension): everything that is not a placeholder.\nexport function isResolvedWidget(widget: WidgetInstance): widget is ResolvedWidgetInstance {\n return widget.type !== UNKNOWN_WIDGET_TYPE;\n}\n\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// parseWidgetRef reads the stored `widget` metadata. `undefined` = absent (legacy / incomplete);\n// `null` = present but malformed, which must not be mistaken for absent.\nexport function parseWidgetRef(raw: unknown): WidgetImplementationRef | undefined | null {\n if (raw === undefined) return undefined;\n if (!isRecord(raw)) return null;\n const { version, optionsVersion, sdkApiVersion } = raw;\n if (typeof version !== 'string' || !SEMVER.test(version)) return null;\n if (typeof optionsVersion !== 'number' || !Number.isInteger(optionsVersion) || optionsVersion < 1) return null;\n const ref: WidgetImplementationRef = { version, optionsVersion };\n if (sdkApiVersion !== undefined) {\n if (typeof sdkApiVersion !== 'number' || !Number.isInteger(sdkApiVersion) || sdkApiVersion < 1) return null;\n ref.sdkApiVersion = sdkApiVersion;\n }\n return ref;\n}\n\n// checkWidgetCompatibility returns why the stored reference cannot be rendered by this\n// descriptor, or undefined when it can (a differing `version` alone is compatible).\nexport function checkWidgetCompatibility(\n id: string,\n ref: WidgetImplementationRef,\n descriptor: WidgetDescriptor,\n): UnresolvedWidgetReason | undefined {\n if (ref.optionsVersion !== descriptor.optionsVersion) {\n return {\n code: 'options-version',\n message: `\"${id}\" was saved with settings version ${ref.optionsVersion}; the installed widget uses settings version ${descriptor.optionsVersion}`,\n };\n }\n if (ref.sdkApiVersion !== undefined && ref.sdkApiVersion !== descriptor.sdkApiVersion) {\n return {\n code: 'sdk-incompatible',\n message: `\"${id}\" was authored for widget SDK API ${ref.sdkApiVersion}; the installed widget uses API ${descriptor.sdkApiVersion}`,\n };\n }\n return undefined;\n}\n\n// widgetVersionNotice is the non-blocking provenance message for a widget rendered by a\n// different release than the one it was authored with, or undefined when they match (or the\n// widget carries no version).\nexport function widgetVersionNotice(\n id: string,\n ref: WidgetImplementationRef | undefined,\n descriptor: WidgetDescriptor,\n): string | undefined {\n if (!ref || ref.version === descriptor.version) return undefined;\n return `\"${id}\" was authored with version ${ref.version}; rendering with version ${descriptor.version}`;\n}\n\n// classifyStoredWidget decides, for a stored widget object of a NON-reserved or versioned\n// type, whether the catalog resolves it. Returns the reason it does not, or undefined.\n// `builtin` selects the legacy rule: a built-in with no metadata is fine.\nexport function unresolvedReason(\n type: string,\n rawRef: unknown,\n catalog: WidgetCatalog | undefined,\n builtin: boolean,\n): UnresolvedWidgetReason | undefined {\n const ref = parseWidgetRef(rawRef);\n if (ref === null) {\n return { code: 'invalid-metadata', message: `\"${type}\" carries widget version metadata that is not valid` };\n }\n if (!catalog) {\n return builtin\n ? undefined\n : { code: 'unregistered', message: `\"${type}\" is not available: no widget catalog was supplied` };\n }\n const descriptor = catalog.get(type);\n if (!descriptor) {\n return {\n code: 'unregistered',\n message: builtin\n ? `\"${type}\" is not registered in this host`\n : `\"${type}\" is not installed in this host; add its package to the host's widget registry`,\n };\n }\n if (ref === undefined) {\n return builtin\n ? undefined\n : { code: 'unversioned', message: `\"${type}\" is stored without version metadata, so the installed widget cannot be matched to it` };\n }\n return checkWidgetCompatibility(type, ref, descriptor);\n}\n\nexport interface UnresolvedWidgetReport {\n widgetId: string;\n type: string;\n reason: UnresolvedWidgetReason;\n}\n\n// unresolvedWidgets lists every widget on the board that is a placeholder, with its reason.\n// A publish gate uses it: loading, layout edits and draft saves stay possible, but a new\n// version should not be published while a widget cannot be resolved. A placeholder built\n// by an older parser (no reason) is reported as unregistered.\nexport function unresolvedWidgets(definition: Pick<DashboardDefinition, 'widgets'>): UnresolvedWidgetReport[] {\n const out: UnresolvedWidgetReport[] = [];\n for (const w of definition.widgets as readonly WidgetInstance[]) {\n if (w.type !== UNKNOWN_WIDGET_TYPE) continue;\n const storedType = typeof w.raw.type === 'string' ? w.raw.type : '';\n out.push({\n widgetId: w.id,\n type: storedType,\n reason: w.reason ?? { code: 'unregistered', message: `\"${storedType}\" is not available in this host` },\n });\n }\n return out;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Load-time helpers for a stored dashboard definition.\n//\n// dashboard-management stores the definition as opaque JSON (well-formed object,\n// size-bounded \u2014 nothing more). This package owns the *shape*, so validation and\n// default-filling live here rather than on the server: parseDashboardDefinition\n// turns an untrusted parsed value into a DashboardDefinition or throws, so the\n// renderer never has to guess at a missing canvas/breakpoint/box. Kept permissive\n// where a sensible default exists (a bare `{ widgets: [] }` is valid) and strict\n// where a wrong value would silently mis-render (a widget with no type, no base box).\n//\n// A widget whose type this build does not know is NOT an error: it is a widget a newer\n// release added. It parses to a placeholder that keeps the stored object verbatim, and\n// serializeDefinition writes that object back (see UnknownWidgetInstance), so an older\n// host that loads and saves a newer board leaves the newer widget exactly as it was.\n\nimport {\n WIDGET_TYPES,\n type AnchorTarget,\n type Breakpoints,\n type Canvas,\n type CanvasGrid,\n type CanvasSizing,\n type DashboardDefinition,\n type ExtensionWidgetInstance,\n type ExtensionWidgetType,\n type KnownWidgetInstance,\n type LocationSelection,\n type SlotBinding,\n type SlotDefinition,\n type SlotScope,\n type WidgetBox,\n type WidgetImplementationRef,\n type WidgetInstance,\n type WidgetLayout,\n type UnknownWidgetInstance,\n type UnresolvedWidgetReason,\n type WidgetType,\n UNKNOWN_WIDGET_TYPE,\n} from './types';\nimport { parseWidgetRef, unresolvedReason } from './widget-compat';\nimport type { WidgetCatalog } from './widget-catalog';\n\n// The breakpoint every layout must define; a widget/viewport with no more specific\n// box falls back to it. Named once so parse + resolveWidgetBox agree.\nexport const BASE_BREAKPOINT = 'base';\n\nconst WIDGET_TYPE_SET: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\n// The default canvas grid: 24 fluid columns, an 8px gutter, 40px rows \u2014 a\n// high-resolution grid that fills its container width (ADR-039 amendment).\nconst DEFAULT_GRID: CanvasGrid = { columns: 24, gap: 8, rowHeight: 40 };\nconst DEFAULT_SIZING: CanvasSizing = 'fill';\n\n// Thrown when a definition cannot be coerced into a renderable shape. The message\n// names the offending path so a bad document is diagnosable, not just \"invalid\".\nexport class DashboardDefinitionError extends Error {\n constructor(message: string) {\n super(`invalid dashboard definition: ${message}`);\n this.name = 'DashboardDefinitionError';\n }\n}\n\nfunction isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nfunction numberAt(rec: Record<string, unknown>, key: string, fallback: number): number {\n const v = rec[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\n// parseDashboardDefinition validates a parsed JSON value and returns a definition\n// with defaults filled, or throws DashboardDefinitionError. `raw` is the value\n// after JSON.parse (the caller owns the parse so a syntax error is theirs to\n// handle); an already-typed object round-trips unchanged, an unknown-type\n// placeholder included.\n//\n// `options.catalog` is the React-free view of the host's widget registry. With it, a\n// namespaced extension id the catalog holds parses to an ExtensionWidgetInstance, and a\n// stored `widget` version reference is checked against the installed descriptor; a widget\n// the catalog cannot resolve (absent, no/invalid metadata, settings or SDK generation\n// differing) becomes a placeholder carrying its reason and its stored JSON. Without a\n// catalog the parser knows the built-ins only (legacy behavior), and any other id is a\n// placeholder.\nexport interface ParseDefinitionOptions {\n catalog?: WidgetCatalog;\n}\n\nexport function parseDashboardDefinition(raw: unknown, options: ParseDefinitionOptions = {}): DashboardDefinition {\n if (!isRecord(raw)) throw new DashboardDefinitionError('not a JSON object');\n\n const widgetsRaw = raw.widgets;\n if (!Array.isArray(widgetsRaw)) throw new DashboardDefinitionError('widgets must be an array');\n\n const def: DashboardDefinition = {\n schemaVersion: numberAt(raw, 'schemaVersion', 1),\n title: typeof raw.title === 'string' ? raw.title : '',\n canvas: parseCanvas(raw.canvas),\n widgets: widgetsRaw.map((w, i) => parseWidget(w, i, options.catalog)),\n };\n // slots \u2014 the runtime-binding section (ADR-039). Normalized when present so a\n // stored definition round-trips, but omitted entirely otherwise so a slot-free\n // dashboard serializes unchanged (no spurious `\"slots\":{}` diff).\n const slots = parseSlots(raw.slots);\n if (slots) def.slots = slots;\n return def;\n}\n\nfunction parseSlots(raw: unknown): Record<string, SlotDefinition> | undefined {\n if (!isRecord(raw)) return undefined;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, spec] of Object.entries(raw)) {\n // Skip a `__proto__` key: `slots['__proto__'] = \u2026` hits the prototype setter (the\n // slot is lost + the map's prototype is swapped) rather than creating an own\n // property \u2014 the same guard parseBindingManifest applies to its untrusted input.\n if (name === '__proto__') continue;\n if (!isRecord(spec)) continue;\n const type = spec.type === 'anchor' ? 'anchor' : 'device';\n const slot: SlotDefinition = { type };\n if (typeof spec.label === 'string') slot.label = spec.label;\n const binding = parseSlotBinding(spec.defaultBinding);\n if (binding) slot.defaultBinding = binding;\n // Carry a well-formed `scope` (the context hierarchy). Structural parse only here;\n // cross-slot validity (parent exists, is an anchor, no cycles) is checked once the\n // whole map is built \u2014 a scope-blind whitelist would DROP the field and silently\n // erase the hierarchy on every load.\n const scope = parseScope(spec.scope);\n if (scope) slot.scope = scope;\n slots[name] = slot;\n }\n if (Object.keys(slots).length === 0) return undefined;\n validateScopes(slots); // drops any scope with a missing/non-anchor/self/cyclic parent\n return slots;\n}\n\n// parseScope reads a candidate `{ parent, strategy }` \u2014 structural shape only. A\n// non-string/empty parent drops the scope; an unrecognized strategy defaults to 'first'.\nfunction parseScope(raw: unknown): SlotScope | undefined {\n if (!isRecord(raw)) return undefined;\n const parent = typeof raw.parent === 'string' ? raw.parent : '';\n if (!parent) return undefined;\n return { parent, strategy: raw.strategy === 'manual' ? 'manual' : 'first' };\n}\n\n// validateScopes drops (in place) any slot scope whose parent is missing, not an\n// anchor-typed slot, self-referential, or part of a cycle \u2014 degrade, don't throw, since\n// the definition is opaque JSON the backend never validated. Dropping a bad scope leaves\n// the slot as a plain (root) slot rather than failing the whole dashboard to parse.\nfunction validateScopes(slots: Record<string, SlotDefinition>): void {\n const drop: string[] = [];\n for (const [name, slot] of Object.entries(slots)) {\n if (!slot.scope) continue;\n const parentName = slot.scope.parent;\n const parent = Object.prototype.hasOwnProperty.call(slots, parentName) ? slots[parentName] : undefined;\n if (!parent || parent.type !== 'anchor' || parentName === name || inScopeCycle(slots, name)) {\n drop.push(name);\n }\n }\n for (const name of drop) delete slots[name].scope;\n}\n\n// inScopeCycle walks the parent chain from `start`; a revisited node means the chain\n// reaches a loop (a forest has none). This also drops a slot that merely POINTS INTO a\n// cycle without being on it \u2014 a deliberately conservative degrade: every affected slot\n// becomes a safe root rather than risking a dangling parent, and real (non-hand-edited)\n// definitions never contain cycles. Reads scopes via own-property lookup so a slot named\n// '__proto__'/'constructor' can't reach an inherited member.\nfunction inScopeCycle(slots: Record<string, SlotDefinition>, start: string): boolean {\n const seen = new Set<string>();\n let cur: string | undefined = start;\n while (cur) {\n if (seen.has(cur)) return true;\n seen.add(cur);\n const slot: SlotDefinition | undefined = Object.prototype.hasOwnProperty.call(slots, cur)\n ? slots[cur]\n : undefined;\n cur = slot?.scope?.parent;\n }\n return false;\n}\n\n// canScopeSlot reports whether `child` may take a scope with parent `parentName` \u2014 the\n// authoring-time equivalent of the loader's validateScopes rules, so the editor cannot write\n// a scope the loader would silently drop on reload. A scope is valid iff the parent exists,\n// is an anchor-typed slot, is not the child itself, and adding the child\u2192parent edge creates\n// no cycle (walking the parent's own ancestor chain must not reach the child). Reads scopes\n// via own-property lookup so a slot named '__proto__'/'constructor' can't reach an inherited\n// member. Pure \u2014 never mutates the slots map.\nexport function canScopeSlot(\n slots: Record<string, SlotDefinition>,\n child: string,\n parentName: string,\n): boolean {\n if (child === parentName) return false;\n const parent = Object.prototype.hasOwnProperty.call(slots, parentName) ? slots[parentName] : undefined;\n if (!parent || parent.type !== 'anchor') return false;\n // Would child\u2192parent close a loop? The parent (transitively) must not already depend on\n // the child. Guard against a pre-existing upstream cycle so this can't spin.\n const seen = new Set<string>();\n let cur: string | undefined = parentName;\n while (cur) {\n if (cur === child) return false;\n // A chain that reaches a pre-existing cycle is rejected too (not just accepted-by-break),\n // matching the loader's inScopeCycle, which drops any slot whose ancestry reaches a loop.\n if (seen.has(cur)) return false;\n seen.add(cur);\n const slot: SlotDefinition | undefined = Object.prototype.hasOwnProperty.call(slots, cur)\n ? slots[cur]\n : undefined;\n cur = slot?.scope?.parent;\n }\n return true;\n}\n\n// parseSlotBinding normalizes a slot binding (device token or anchor target), or\n// drops it (undefined) when absent/malformed. The entity only \u2014 a binding never\n// carries measurement names (those live on the widget's selector). Exported so the\n// runtime binding manifest (parseBindingManifest) validates host input the same way.\nexport function parseSlotBinding(raw: unknown): SlotBinding | undefined {\n if (!isRecord(raw)) return undefined;\n if (raw.kind === 'device') {\n const token = stringAt(raw, 'deviceToken');\n return token ? { kind: 'device', deviceToken: token } : undefined;\n }\n if (raw.kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const targetToken = stringAt(anchorRec, 'targetToken');\n // Drop an anchor binding with no target (symmetric with the empty-device-token\n // case) \u2014 it names no entity, so it can't be a default binding.\n if (!targetToken) return undefined;\n return {\n kind: 'anchor',\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken,\n },\n };\n }\n return undefined;\n}\n\n// parseGrid coerces the canvas grid, filling defaults. `columns`/`rowHeight` are\n// floored to >=1 so a zero never yields an unusable `repeat(0,1fr)` / 0-px rows.\n// `gap` accepts a single number or a {row,col} pair; anything else \u2192 the default.\nfunction parseGrid(raw: unknown): CanvasGrid {\n const rec = isRecord(raw) ? raw : {};\n const columns = Math.max(1, Math.round(numberAt(rec, 'columns', DEFAULT_GRID.columns)));\n const rowHeight = Math.max(1, numberAt(rec, 'rowHeight', DEFAULT_GRID.rowHeight));\n let gap: CanvasGrid['gap'] = DEFAULT_GRID.gap;\n if (typeof rec.gap === 'number' && Number.isFinite(rec.gap)) {\n gap = Math.max(0, rec.gap);\n } else if (isRecord(rec.gap)) {\n // A missing axis falls back to the default gutter (not 0), so a partial\n // `{gap:{row:12}}` keeps the default column gutter rather than losing it.\n gap = {\n row: Math.max(0, numberAt(rec.gap, 'row', DEFAULT_GRID.gap as number)),\n col: Math.max(0, numberAt(rec.gap, 'col', DEFAULT_GRID.gap as number)),\n };\n }\n return { columns, gap, rowHeight };\n}\n\n// parseSizing coerces the container-sizing knob. 'fill' (the default) or a\n// single-axis fixed box `{width}` / `{height}`; a malformed value falls back to fill.\nfunction parseSizing(raw: unknown): CanvasSizing {\n if (isRecord(raw)) {\n if (typeof raw.width === 'number' && Number.isFinite(raw.width)) {\n return { width: Math.max(1, raw.width) };\n }\n if (typeof raw.height === 'number' && Number.isFinite(raw.height)) {\n return { height: Math.max(1, raw.height) };\n }\n }\n return DEFAULT_SIZING;\n}\n\nfunction parseCanvas(raw: unknown): Canvas {\n const rec = isRecord(raw) ? raw : {};\n\n const grid = parseGrid(rec.grid);\n const sizing = parseSizing(rec.sizing);\n\n // Breakpoints must define 'base'; default a single base:0 so a definition that\n // omits responsive layouts still resolves.\n const bpRec = isRecord(rec.breakpoints) ? rec.breakpoints : {};\n const breakpoints: Breakpoints = {};\n for (const [name, width] of Object.entries(bpRec)) {\n if (typeof width === 'number' && Number.isFinite(width)) breakpoints[name] = width;\n }\n if (!(BASE_BREAKPOINT in breakpoints)) breakpoints[BASE_BREAKPOINT] = 0;\n\n const canvas: Canvas = { grid, sizing, breakpoints };\n if (isRecord(rec.background)) {\n const { color, imageUrl } = rec.background;\n canvas.background = {\n color: typeof color === 'string' ? color : null,\n imageUrl: typeof imageUrl === 'string' ? imageUrl : null,\n };\n }\n return canvas;\n}\n\nfunction parseWidget(raw: unknown, index: number, catalog: WidgetCatalog | undefined): WidgetInstance {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}] is not an object`);\n\n const type = raw.type;\n // A missing, empty or non-string type names nothing a newer viewer could render: the\n // document is broken, not newer.\n if (typeof type !== 'string' || type.length === 0) {\n throw new DashboardDefinitionError(`widgets[${index}] has unknown type ${JSON.stringify(type)}`);\n }\n if (type === UNKNOWN_WIDGET_TYPE && isRecord(raw.raw)) return reparseUnknownWidget(raw, index, catalog);\n\n const builtin = WIDGET_TYPE_SET.has(type);\n const reason = unresolvedReason(type, raw.widget, catalog, builtin) ?? (builtin ? undefined : reservedFieldReason(type, raw));\n if (reason) return parseUnknownWidget(raw, index, reason);\n\n const ref = parseWidgetRef(raw.widget) ?? undefined;\n const widget: KnownWidgetInstance | ExtensionWidgetInstance = builtin\n ? {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as WidgetType,\n ...(ref ? { widget: ref } : {}),\n layout: parseLayout(raw.layout, index),\n }\n : {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as ExtensionWidgetType,\n widget: ref as WidgetImplementationRef, // unresolvedReason guarantees a valid ref here\n layout: parseLayout(raw.layout, index),\n };\n // datasource is owned by the hub, but the two supported kinds are NORMALIZED here\n // so downstream (hub/widgets) never sees a partial shape (a device selector with\n // no measurements array, an anchor with a non-object target, \u2026). Reserved/other\n // kinds are carried through opaquely \u2014 the hub rejects them.\n const ds = parseDatasource(raw.datasource, index);\n if (ds) widget.datasource = ds;\n if (isRecord(raw.options)) widget.options = raw.options as Record<string, unknown>;\n if (!builtin) return withStoredFields(raw, widget as ExtensionWidgetInstance);\n return widget;\n}\n\n// The envelope fields the parser owns on a resolved widget. Everything else a stored widget\n// carries is outside the owned schema.\nconst OWNED_WIDGET_KEYS: ReadonlySet<string> = new Set(['id', 'type', 'widget', 'layout', 'datasource', 'options']);\n\n// withStoredFields keeps, on a resolved EXTENSION widget, every stored field the envelope does\n// not own (a package's own top-level additions, extra keys inside `widget`), in the stored key\n// order, so a load/save round trip writes them back byte-for-byte. Owned fields take their\n// parsed (normalized) values. Built-ins are deliberately left as they always were (only the\n// owned envelope survives), so an existing board's saved bytes do not change.\nfunction withStoredFields(raw: Record<string, unknown>, parsed: ExtensionWidgetInstance): ExtensionWidgetInstance {\n const owned = parsed as unknown as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n // defineProperty, not assignment: a JSON.parse'd `__proto__` key is an own field to keep,\n // and assigning it would set the prototype instead.\n const put = (key: string, value: unknown) =>\n Object.defineProperty(out, key, { value, enumerable: true, writable: true, configurable: true });\n for (const key of Object.keys(raw)) {\n if (!OWNED_WIDGET_KEYS.has(key)) put(key, JSON.parse(JSON.stringify(raw[key])) as unknown);\n else if (key === 'widget') put(key, { ...(raw.widget as Record<string, unknown>), ...parsed.widget });\n else if (owned[key] !== undefined) put(key, owned[key]);\n }\n for (const key of OWNED_WIDGET_KEYS) {\n if (!Object.prototype.hasOwnProperty.call(out, key) && owned[key] !== undefined) put(key, owned[key]);\n }\n return out as unknown as ExtensionWidgetInstance;\n}\n\n// A stored extension widget that uses a field name the parsed model reserves for placeholders\n// cannot be carried on a resolved instance without being mistaken for one, so it is kept\n// verbatim as a placeholder instead of having the field dropped.\nfunction reservedFieldReason(type: string, raw: Record<string, unknown>): UnresolvedWidgetReason | undefined {\n const clash = ['raw', 'reason'].find((k) => Object.prototype.hasOwnProperty.call(raw, k));\n return clash === undefined\n ? undefined\n : { code: 'invalid-metadata', message: `\"${type}\" carries a top-level \"${clash}\" field, which the dashboard format reserves` };\n}\n\n// The id a stored widget carries, or undefined when it has none worth keeping.\nfunction storedWidgetId(raw: Record<string, unknown>): string | undefined {\n return typeof raw.id === 'string' && raw.id.length > 0 ? raw.id : undefined;\n}\n\n// parseUnknownWidget builds the placeholder for a widget of a type this build does not\n// know. Only `id` and `layout` are read, through the same rules as every other widget,\n// so the canvas can place it; a layout with no base box still fails the parse, because\n// a widget that cannot be placed cannot be shown even as a placeholder.\n//\n// `raw` is a JSON copy of the stored object: detached from the caller's value (which a\n// host may go on to mutate), and exactly what JSON.stringify will write back.\nfunction parseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n reason: UnresolvedWidgetReason,\n): UnknownWidgetInstance {\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: JSON.parse(JSON.stringify(raw)) as Record<string, unknown>,\n reason,\n };\n}\n\n// reparseUnknownWidget takes back a placeholder this parser already built \u2014 a typed\n// definition handed to the parser again \u2014 as the same placeholder: its stored object is\n// kept as its raw, and its id and layout (which may carry an edit) come from the\n// placeholder. Wrapping it again would make the save write the wrapper and lose the\n// widget. The serializer never writes the placeholder type, so no stored document takes\n// this path.\nfunction reparseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n catalog: WidgetCatalog | undefined,\n): UnknownWidgetInstance {\n const stored = JSON.parse(JSON.stringify(raw.raw)) as Record<string, unknown>;\n const storedType = typeof stored.type === 'string' ? stored.type : '';\n // The reason is recomputed against THIS catalog (it is never stored); a stored object the\n // catalog now resolves stays a placeholder here, because only a parse of the stored\n // document \u2014 not a re-parse of a typed definition \u2014 promotes a widget.\n const reason = unresolvedReason(storedType, stored.widget, catalog, WIDGET_TYPE_SET.has(storedType)) ?? {\n code: 'unregistered' as const,\n message: `\"${storedType}\" is not available in this host`,\n };\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: stored,\n reason,\n };\n}\n\nfunction stringAt(rec: Record<string, unknown>, key: string): string {\n const v = rec[key];\n return typeof v === 'string' ? v : '';\n}\n\nfunction stringArrayAt(rec: Record<string, unknown>, key: string): string[] {\n const v = rec[key];\n return Array.isArray(v) ? v.filter((m): m is string => typeof m === 'string') : [];\n}\n\n// parseLocationSelection reads a selector's OPTIONAL location-series field.\n//\n// \uD83D\uDD34 Additive, and it has to stay that way: every definition stored before the location\n// channel existed carries no `location`, and undefined here is exactly right for all of\n// them \u2014 the selector names no location series, which is true. Nothing about the\n// measurement path is touched, so the regression risk is confined to what an author\n// deliberately adds.\n//\n// The vocabulary is closed, so an unrecognized series is DROPPED rather than carried\n// through: the hub resolves a location scope only for a selector naming a series it\n// understands, and carrying an unknown one would produce a widget that looks configured\n// and reads nothing. Dropping it makes the widget's empty state honest.\nfunction parseLocationSelection(raw: unknown): LocationSelection | undefined {\n if (!isRecord(raw)) return undefined;\n return raw.series === 'latest' ? { series: 'latest' } : undefined;\n}\n\n// withLocation attaches a parsed location selection to a selector, and OMITS the key\n// entirely when there is none \u2014 so a measurement-only definition serializes byte-for-\n// byte as it did before this field existed (no spurious `\"location\":undefined` in the\n// diff, and isDirty stays quiet on a load/save round trip).\nfunction withLocation<S extends object>(selector: S, raw: Record<string, unknown>): S {\n const location = parseLocationSelection(raw.location);\n return location ? { ...selector, location } : selector;\n}\n\n// parseDatasource coerces a raw datasource into a normalized selector, or drops it\n// (returns undefined) when it is absent or its `kind` is not a non-empty string.\nfunction parseDatasource(raw: unknown, index: number): WidgetInstance['datasource'] | undefined {\n if (!isRecord(raw)) return undefined;\n const kind = raw.kind;\n if (typeof kind !== 'string' || kind.length === 0) return undefined;\n\n // \uD83D\uDD34 `unbound` is what the RENDERER produces for a slot with no binding; it is never\n // stored. Carried through, a stored one would render as an empty pane on every channel\n // with nothing to say why \u2014 the shape of a correctly unbound slot, not of a broken\n // definition. A widget with no entity is stored as a `slot` with no binding.\n if (kind === 'unbound') {\n throw new DashboardDefinitionError(\n `widgets[${index}].datasource has kind 'unbound', which is never stored; bind the widget through a slot`,\n );\n }\n\n if (kind === 'device') {\n return withLocation(\n { kind: 'device' as const, deviceToken: stringAt(raw, 'deviceToken'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n if (kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const selector: WidgetInstance['datasource'] = withLocation(\n {\n kind: 'anchor' as const,\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n // targetType defaults to '' (the config panel constrains it to the union;\n // a hand-edited/empty value round-trips rather than being silently coerced).\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken: stringAt(anchorRec, 'targetToken'),\n },\n measurements: stringArrayAt(raw, 'measurements'),\n },\n raw,\n );\n if (isRecord(raw.aggregation)) {\n (selector as { aggregation?: unknown }).aggregation = raw.aggregation;\n }\n return selector;\n }\n\n if (kind === 'slot') {\n // Runtime-binding kind: the entity is resolved at mount via the Hub's binding\n // manifest; the widget carries only the slot name + the series it wants (the\n // measurement names, and/or the location series).\n return withLocation(\n { kind: 'slot' as const, slot: stringAt(raw, 'slot'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n\n // Reserved/other kinds: carry through opaquely (the hub rejects them).\n return raw as unknown as WidgetInstance['datasource'];\n}\n\nfunction parseLayout(raw: unknown, index: number): WidgetLayout {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}].layout is missing`);\n\n const layout: WidgetLayout = {};\n for (const [bp, box] of Object.entries(raw)) {\n if (isRecord(box)) layout[bp] = parseBox(box);\n }\n if (!(BASE_BREAKPOINT in layout)) {\n throw new DashboardDefinitionError(`widgets[${index}].layout has no '${BASE_BREAKPOINT}' box`);\n }\n return layout;\n}\n\nfunction parseBox(rec: Record<string, unknown>): WidgetBox {\n // col/row are 0-based start lines (clamped >=0); spans are >=1 so a widget can't\n // vanish. offset is an optional signed-pixel nudge \u2014 carried only when present so a\n // box without it round-trips unchanged.\n const box: WidgetBox = {\n col: Math.max(0, Math.round(numberAt(rec, 'col', 0))),\n colSpan: Math.max(1, Math.round(numberAt(rec, 'colSpan', 1))),\n row: Math.max(0, Math.round(numberAt(rec, 'row', 0))),\n rowSpan: Math.max(1, Math.round(numberAt(rec, 'rowSpan', 1))),\n // z rounds too: a fractional zIndex is invalid CSS and silently drops to auto,\n // so keep it an integer like every other box field.\n z: Math.round(numberAt(rec, 'z', 0)),\n };\n if (isRecord(rec.offset)) {\n box.offset = { x: numberAt(rec.offset, 'x', 0), y: numberAt(rec.offset, 'y', 0) };\n }\n return box;\n}\n\n// The stored shape of a definition: what serializeDefinition writes. A placeholder for\n// an unknown widget type is written as the stored object it was parsed from.\nexport type StoredDashboardDefinition = Omit<DashboardDefinition, 'widgets'> & {\n widgets: Array<KnownWidgetInstance | ExtensionWidgetInstance | Record<string, unknown>>;\n};\n\n// storedDefinition is the definition as it is stored: every unknown-type placeholder\n// replaced by its raw object (carrying any move or id change the editor made to it).\n// Anything that writes a definition out \u2014 a save, a copy-as-JSON export \u2014 goes through\n// this, because JSON.stringify of the parsed model would write the placeholder wrapper\n// and lose the widget. Returns the same reference when there is no placeholder.\nexport function storedDefinition(def: DashboardDefinition): StoredDashboardDefinition {\n if (!def.widgets.some((w) => w.type === UNKNOWN_WIDGET_TYPE)) return def as StoredDashboardDefinition;\n return {\n ...def,\n widgets: def.widgets.map((w) => (w.type === UNKNOWN_WIDGET_TYPE ? storedUnknownWidget(w) : w)),\n };\n}\n\n// storedUnknownWidget writes a placeholder back as its raw object. The raw object is\n// returned untouched unless the editor changed one of the two fields a placeholder\n// exposes: a changed id is written over the stored one, and a moved breakpoint box is\n// written over that breakpoint (every untouched breakpoint keeps its stored bytes). An\n// id the parser GENERATED for an id-less widget is not written, so the stored widget\n// stays id-less exactly as it was.\nfunction storedUnknownWidget(w: UnknownWidgetInstance): Record<string, unknown> {\n let out = w.raw;\n const rawId = storedWidgetId(w.raw);\n if (rawId !== undefined && rawId !== w.id) out = { ...out, id: w.id };\n const layout = storedUnknownLayout(w.layout, w.raw.layout);\n if (layout !== w.raw.layout) out = { ...out, layout };\n return out;\n}\n\n// storedUnknownLayout merges the placeholder's (parsed, possibly edited) layout into\n// the stored one, breakpoint by breakpoint. A stored box that parses to the same box is\n// kept as stored; a changed one is replaced; a breakpoint the stored layout did not\n// have is appended; a stored entry that is not a box at all (the parser skipped it) is\n// carried. Returns the stored reference when nothing changed.\nfunction storedUnknownLayout(layout: WidgetLayout, rawLayout: unknown): unknown {\n const stored = isRecord(rawLayout) ? rawLayout : {};\n const out: Record<string, unknown> = {};\n let changed = !isRecord(rawLayout);\n for (const [bp, box] of Object.entries(stored)) {\n if (!isRecord(box)) {\n out[bp] = box;\n continue;\n }\n const current = Object.prototype.hasOwnProperty.call(layout, bp) ? layout[bp] : undefined;\n if (!current) {\n changed = true; // the editor removed this breakpoint\n continue;\n }\n if (sameBox(parseBox(box), current)) {\n out[bp] = box;\n } else {\n out[bp] = movedBox(box, current);\n changed = true;\n }\n }\n for (const [bp, box] of Object.entries(layout)) {\n if (Object.prototype.hasOwnProperty.call(stored, bp) && isRecord(stored[bp])) continue;\n out[bp] = box;\n changed = true;\n }\n return changed ? out : rawLayout;\n}\n\n// movedBox writes an edited box over a stored one. The fields a box has in this build\n// take the edited values; any other field the stored box carries (one a newer release\n// added) is kept, so a move does not strip it. An offset the edit cleared is removed.\nfunction movedBox(stored: Record<string, unknown>, current: WidgetBox): Record<string, unknown> {\n const out: Record<string, unknown> = { ...stored, ...current };\n if (current.offset === undefined) delete out.offset;\n return out;\n}\n\nfunction sameBox(a: WidgetBox, b: WidgetBox): boolean {\n return (\n a.col === b.col &&\n a.colSpan === b.colSpan &&\n a.row === b.row &&\n a.rowSpan === b.rowSpan &&\n a.z === b.z &&\n a.offset?.x === b.offset?.x &&\n a.offset?.y === b.offset?.y\n );\n}\n\n// serializeDefinition is the canonical on-the-wire JSON \u2014 the inverse of\n// parseDashboardDefinition \u2014 that dashboard-management stores. Named (not an inline\n// JSON.stringify) so every consumer that persists a definition shares one format.\nexport function serializeDefinition(def: DashboardDefinition): string {\n return JSON.stringify(storedDefinition(def));\n}\n\n// isDirty reports whether two definitions differ. A structural JSON compare is\n// enough: both sides come from parse/edit transforms that preserve key order, so\n// it only flips on a real change. Drives an editor's save/dirty state.\nexport function isDirty(a: DashboardDefinition, b: DashboardDefinition): boolean {\n return serializeDefinition(a) !== serializeDefinition(b);\n}\n\n// resolveWidgetBox returns the box for the active breakpoint, falling back to the\n// widget's 'base' box when it defines no override for that breakpoint. Parse\n// guarantees a base box exists, so this always resolves.\nexport function resolveWidgetBox(layout: WidgetLayout, breakpoint: string): WidgetBox {\n return layout[breakpoint] ?? layout[BASE_BREAKPOINT];\n}\n\n// activeBreakpoint picks the breakpoint whose min width is the largest one that\n// still fits the viewport (falling back to 'base' / the smallest). Deterministic\n// given equal widths by preferring the wider min.\nexport function activeBreakpoint(breakpoints: Breakpoints, viewportWidth: number): string {\n let best = BASE_BREAKPOINT;\n let bestWidth = -1;\n for (const [name, minWidth] of Object.entries(breakpoints)) {\n if (viewportWidth >= minWidth && minWidth > bestWidth) {\n best = name;\n bestWidth = minWidth;\n }\n }\n return best;\n}\n\n// generateWidgetId mints a unique widget-instance id. Uses crypto.randomUUID where\n// available (browsers, modern Node); the counter fallback keeps ids unique within a\n// session for the rare environment without it.\nlet idCounter = 0;\nexport function generateWidgetId(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return `w-${c.randomUUID()}`;\n idCounter += 1;\n return `w-${idCounter.toString(36)}`;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Runtime slot binding (ADR-039). A dashboard definition is a reusable TEMPLATE:\n// widgets reference named slots, and each slot declares a `defaultBinding` (the\n// authoring tenant's entity). At MOUNT the host may supply a manifest that overrides\n// any slot's binding \u2014 so one definition + two manifests renders as two live\n// dashboards on different entities. effectiveBindings computes the base manifest: the\n// slot defaults, overlaid by the host's overrides. The renderer resolves each widget's\n// slot through the settled manifest (resolveWidgetDatasource) before the hub sees it.\n\nimport { parseSlotBinding } from './definition';\nimport type { DashboardDefinition, DatasourceSelector, SlotBinding, SlotDefinition } from './types';\n\n// effectiveBindings merges a definition's slot default bindings with an optional\n// host manifest (manifest wins). Slots without a default and not in the manifest are\n// omitted \u2192 the renderer hands those widgets an `unbound` selector, an empty placeholder.\n//\n// This is the BASE layer (defaults + manifest), computed synchronously. For a dashboard\n// with SCOPED slots it is NOT the final manifest: a scoped slot's default here is a\n// fallback that resolveContextBindings (the cascade) supersedes with a value derived\n// from the parent. A host with scoped slots feeds this map as `base` into the cascade\n// and hands the renderer the cascade's output, not this map directly.\nexport function effectiveBindings(\n definition: DashboardDefinition,\n manifest?: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [name, slot] of Object.entries(definition.slots ?? {})) {\n if (slot.defaultBinding) out[name] = slot.defaultBinding;\n }\n if (manifest) {\n for (const [name, binding] of Object.entries(manifest)) out[name] = binding;\n }\n return out;\n}\n\n// What parseBindingManifest made of a host manifest: the bindings it accepted, and the\n// slot names it did NOT \u2014 in manifest order.\nexport interface ParsedBindingManifest {\n bindings: Record<string, SlotBinding>;\n dropped: string[];\n}\n\n// parseBindingManifest validates an untrusted host manifest (slot name \u2192 binding)\n// into a clean Record<slot, SlotBinding>, dropping malformed entries. The host of an\n// exported dashboard passes this to effectiveBindings to bind the definition's slots\n// to ITS entities: one definition + two manifests \u2192 two live dashboards.\n//\n// \uD83D\uDD34 IT REPORTS WHAT IT DROPPED, because a silent drop is this API's sharpest edge and\n// every host has to handle it. A typo'd binding does not fail \u2014 it vanishes, the slot\n// stays unbound, and the widgets on it render as empty frames with nothing anywhere\n// saying why. Returning the names makes that a thing a host can show a user. The\n// alternative every host would otherwise reach for is comparing key counts against its\n// own input, which infers the same fact less well: it cannot name the offender, and it\n// counts the `__proto__` refusal below as if it were malformed.\nexport function parseBindingManifest(raw: unknown): ParsedBindingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { bindings: {}, dropped: [] };\n }\n const bindings: Record<string, SlotBinding> = {};\n const dropped: string[] = [];\n for (const [slot, spec] of Object.entries(raw as Record<string, unknown>)) {\n // Skip a `__proto__` key: `out['__proto__'] = \u2026` would hit the prototype setter\n // (lose the binding + swap out's prototype) rather than set an own property.\n // Reported as dropped: it IS a binding the host asked for and did not get.\n if (slot === '__proto__') {\n dropped.push(slot);\n continue;\n }\n const binding = parseSlotBinding(spec);\n if (binding) bindings[slot] = binding;\n else dropped.push(slot);\n }\n return { bindings, dropped };\n}\n\n// stripDefaultBindings removes every slot's default binding, turning a concrete\n// dashboard into a TEMPLATE \u2014 the exported form a host must supply a manifest for\n// (each slot renders as a placeholder until bound). Slot names/types/labels are kept\n// so the importer knows what to bind. (Caveats: a slot's `label` is the author's\n// entity token \u2014 a naming hint, not stripped; and an anchor selector carrying a\n// Phase-2 `aggregation` stays concrete/un-slotted through migration, so it isn't\n// rebindable \u2014 neither is reachable through the console's authoring UI today.)\nexport function stripDefaultBindings(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) {\n const { defaultBinding: _drop, ...rest } = slot;\n slots[name] = rest;\n }\n return { ...def, slots };\n}\n\n// resolveWidgetDatasource turns a widget's datasource into the CONCRETE selector a data\n// source subscribes: a `slot` is looked up in the settled bindings and becomes the device\n// or anchor it is bound to, carrying the widget's own series (measurement names, and the\n// location series when it names one). Every other selector passes through unchanged.\n//\n// This is the renderer's job, not the hub's. The hub keeps no bindings, so a selection\n// that re-points one slot changes the concrete selector of exactly the widgets bound to\n// it \u2014 and only those re-subscribe, because every channel keys its subscription on the\n// selector's VALUE. A hub that resolved slots itself had to be rebuilt on every selection,\n// which tore down and re-fetched the whole board.\n//\n// \uD83D\uDD34 A slot with no binding becomes `{kind:'unbound'}`, NEVER `undefined`. An absent\n// datasource is tenant-wide on the alarm channel; an unbound slot is zero devices. The\n// two must not be confused, and this function is the one place they could be.\n//\n// The lookup is own-property only, so a slot named `constructor` or `__proto__` is\n// unbound rather than resolving to something on Object.prototype.\nexport function resolveWidgetDatasource(\n datasource: DatasourceSelector | undefined,\n bindings: Record<string, SlotBinding> | undefined,\n): DatasourceSelector | undefined {\n if (!datasource || datasource.kind !== 'slot') return datasource;\n const binding =\n bindings && Object.prototype.hasOwnProperty.call(bindings, datasource.slot)\n ? bindings[datasource.slot]\n : undefined;\n const series = datasource.location\n ? { measurements: datasource.measurements, location: datasource.location }\n : { measurements: datasource.measurements };\n if (binding?.kind === 'device') return { kind: 'device', deviceToken: binding.deviceToken, ...series };\n if (binding?.kind === 'anchor') return { kind: 'anchor', anchor: binding.anchor, ...series };\n return { kind: 'unbound', ...series };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The scoped-slot cascade (ADR-039 selection amendment). A dashboard's slots form a\n// context FOREST: a scoped slot resolves relative to its parent's binding (a device\n// within the selected building). resolveContextBindings walks that forest and produces\n// ONE settled slot\u2192binding map the host feeds to the hub + renderer.\n//\n// It is a host-level overlay, NOT in-hub machinery: the hub resolves its bindings once\n// at construction, so selection re-keys the renderer and the hub is rebuilt through the\n// shipped path \u2014 the overlay lives outside the hub and a rebuild never erases it. The\n// pass is async only because enumerating an anchor's members hits the resolver; it is\n// otherwise pure, and the host guards it with a monotonic generation so a slow members\n// response can't overwrite a newer selection.\n\nimport type { DeviceResolver } from './hub';\nimport type { DashboardDefinition, SelectionTarget, SlotBinding, SlotDefinition } from './types';\n\n// The cascade only needs to enumerate an anchor's member devices; it takes the narrow\n// slice of DeviceResolver so a caller (or a test) needn't supply the whole interface.\nexport type MemberResolver = Pick<DeviceResolver, 'devicesForAnchor'>;\n\nfunction ownGet<T>(map: Record<string, T> | undefined, key: string): T | undefined {\n return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n}\n\n// setBinding assigns an OWN property, skipping '__proto__' \u2014 a plain `map['__proto__'] =\n// v` hits the prototype setter (drops the value AND swaps the map's prototype) rather\n// than creating an own key. A slot so named names no real entity, so skipping it is safe;\n// this keeps the SCOPED path as prototype-safe as the scope-free spread path.\nfunction setBinding(map: Record<string, SlotBinding>, key: string, value: SlotBinding): void {\n if (key === '__proto__') return;\n map[key] = value;\n}\n\n// hasScopedSlots reports whether any slot declares a scope \u2014 the host's fast-path gate\n// (a scope-free dashboard needs no async cascade, just the synchronous overlay).\nexport function hasScopedSlots(definition: DashboardDefinition): boolean {\n const slots = definition.slots;\n if (!slots) return false;\n for (const name of Object.keys(slots)) if (slots[name]?.scope) return true;\n return false;\n}\n\n// bindingsWithoutScopedSlots returns `map` with every SCOPED-slot key removed \u2014 the safe\n// interim a host seeds before the async cascade first settles. At mount nothing has been\n// derived yet, so showing a scoped slot's (possibly out-of-context) default would be a\n// stale/lying binding; omitting it renders an empty placeholder until the cascade fills it\n// (\"unbound, never stale\"). Prototype-safe on write.\nexport function bindingsWithoutScopedSlots(\n definition: DashboardDefinition,\n map: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const slots = definition.slots;\n if (!slots) return map;\n const out: Record<string, SlotBinding> = {};\n for (const name of Object.keys(map)) {\n if (!ownGet(slots, name)?.scope) setBinding(out, name, map[name]);\n }\n return out;\n}\n\n// applySelection folds one selection into the overlay, returning a new map (never\n// mutating). Computed-key assignment creates an own property even for '__proto__', so\n// this is prototype-safe; the cascade reads it back via own-property lookups regardless.\nexport function applySelection(\n overlay: Record<string, SlotBinding>,\n target: SelectionTarget,\n): Record<string, SlotBinding> {\n return { ...overlay, [target.slot]: target.binding };\n}\n\n// topoOrder returns slot names parent-before-child (the forest's topological order), so\n// a child is derived only after its parent's binding is settled. DFS with a visited set;\n// parse already rejected cycles, but the guard keeps a hand-edited definition safe.\nfunction topoOrder(slots: Record<string, SlotDefinition>): string[] {\n const order: string[] = [];\n const done = new Set<string>();\n const visit = (name: string, stack: Set<string>): void => {\n if (done.has(name) || stack.has(name)) return;\n const slot = ownGet(slots, name);\n if (!slot) return;\n stack.add(name);\n const parent = slot.scope?.parent;\n if (parent) visit(parent, stack);\n stack.delete(name);\n done.add(name);\n order.push(name);\n };\n for (const name of Object.keys(slots)) visit(name, new Set());\n return order;\n}\n\n// resolveContextBindings computes the effective slot\u2192binding map: root slots take the\n// selection overlay over the slot default; a scoped slot derives from its parent's\n// resolved binding per its strategy. Unbound slots are OMITTED (the hub renders them as\n// an empty placeholder). Fail-safe: a membership error or an unbound/non-anchor parent\n// leaves the child unbound rather than throwing. `base` is the sync default+manifest\n// layer (effectiveBindings); `overlay` is the accumulated selection (wins over base).\nexport async function resolveContextBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n overlay: Record<string, SlotBinding>,\n resolver: MemberResolver,\n): Promise<Record<string, SlotBinding>> {\n const slots = definition.slots ?? {};\n const out: Record<string, SlotBinding> = {};\n\n for (const name of topoOrder(slots)) {\n const slot = ownGet(slots, name);\n const scope = slot?.scope;\n if (!scope) {\n // Root context: the selection wins over the default \u2014 but only a TYPE-COMPATIBLE\n // selection (a device pick must not re-bind an anchor slot, and vice versa). A\n // type-incompatible selection is \"not applicable\": ignore it and keep the prior\n // (default/manifest) binding rather than corrupting the context (ADR decision 7).\n const sel = ownGet(overlay, name);\n const b = sel && (!slot || bindingMatchesType(sel, slot.type)) ? sel : ownGet(base, name);\n if (b) setBinding(out, name, b);\n continue;\n }\n // Scoped: derive from the parent's ALREADY-settled binding (topo order guarantees it).\n // Every strategy binds a DEVICE, so a scoped slot must be device-typed; a hand-edited\n // anchor-typed scoped slot can't be derived (a device binding on an anchor slot mis-types\n // downstream) \u2192 leave it unbound (the authoring UI only scopes device slots).\n if (slot && slot.type !== 'device') continue;\n const parentBinding = ownGet(out, scope.parent);\n if (!parentBinding || parentBinding.kind !== 'anchor') continue; // parent unbound \u2192 child unbound\n let members: string[];\n try {\n members = [...(await resolver.devicesForAnchor(parentBinding.anchor))].sort();\n } catch {\n continue; // membership error \u2192 child unbound (fail-safe)\n }\n if (scope.strategy === 'first') {\n // Auto-follow: the parent's first member (deterministic by token). The slot's own\n // default/selection is ignored \u2014 it is fully derived.\n if (members.length > 0) setBinding(out, name, { kind: 'device', deviceToken: members[0] });\n } else {\n // Manual: the current pick (selection over default), kept iff still a member of the\n // (possibly changed) parent; otherwise unbound (a picker prompts a fresh, in-context pick).\n const pick = ownGet(overlay, name) ?? ownGet(base, name);\n if (pick && pick.kind === 'device' && members.includes(pick.deviceToken)) setBinding(out, name, pick);\n }\n }\n\n // Pass through a manifest binding for a slot the definition does not declare (a host\n // manifest may bind an undeclared slot; effectiveBindings kept it, so we must too). Only\n // BASE keys are carried \u2014 a selection targeting an undeclared slot is dropped, so a\n // mis-authored drill target (a slot that doesn't exist) can't churn a spurious rebuild.\n for (const name of Object.keys(base)) {\n if (Object.prototype.hasOwnProperty.call(slots, name) || Object.prototype.hasOwnProperty.call(out, name)) {\n continue;\n }\n const b = ownGet(overlay, name) ?? ownGet(base, name);\n if (b) setBinding(out, name, b);\n }\n\n return out;\n}\n\n// bindingMatchesType reports whether a binding's kind matches a slot's declared type \u2014 a\n// device binding fits a 'device' slot, an anchor binding an 'anchor' slot.\nfunction bindingMatchesType(binding: SlotBinding, type: SlotDefinition['type']): boolean {\n return binding.kind === type;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Slot authoring transforms (ADR-039). A dashboard is a reusable template: every\n// widget references a named slot, and each slot carries a default entity binding.\n// These pure (React/DOM-free, unit-tested) helpers convert a dashboard's concrete\n// device/anchor selectors into that model and drive the editor's rebind/prune. The\n// authoring UX stays \"pick a device/anchor\"; the storage becomes slot-based, and\n// identical bindings collapse into ONE shared slot (dedup) \u2014 so a host manifest\n// binds each real entity once, and CompositeWidget-style sharing falls out for free.\n\nimport { canScopeSlot } from './definition';\nimport {\n UNKNOWN_WIDGET_TYPE,\n type AnchorTarget,\n type DashboardDefinition,\n type DatasourceSelector,\n type LocationSelection,\n type SlotBinding,\n type SlotDefinition,\n type SlotScope,\n type WidgetInstance,\n} from './types';\n\n// slotSelector builds the slot selector these transforms write, carrying the SERIES the\n// widget reads (measurement names, and the location series when one is named) while the\n// ENTITY moves onto the slot.\n//\n// The location key is omitted when absent rather than written as undefined, so a\n// measurement-only widget serializes exactly as it did before the location channel\n// existed \u2014 a migration must not make every stored dashboard dirty.\nfunction slotSelector(\n slot: string,\n measurements: string[],\n location: LocationSelection | undefined,\n): DatasourceSelector {\n return location\n ? { kind: 'slot', slot, measurements, location }\n : { kind: 'slot', slot, measurements };\n}\n\n// sameBinding \u2014 value equality of two entity bindings (drives dedup).\nexport function sameBinding(a: SlotBinding | undefined, b: SlotBinding | undefined): boolean {\n if (!a || !b) return a === b;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') {\n return (\n a.anchor.relationship === b.anchor.relationship &&\n a.anchor.targetType === b.anchor.targetType &&\n a.anchor.targetToken === b.anchor.targetToken\n );\n }\n return false;\n}\n\n// bindingLabel \u2014 a readable slot label (the bound entity's token).\nfunction bindingLabel(b: SlotBinding): string {\n return b.kind === 'device' ? b.deviceToken : b.anchor.targetToken;\n}\n\n// nextSlotName \u2014 the lowest unused `slot-N` name in a slots map.\nfunction nextSlotName(slots: Record<string, SlotDefinition>): string {\n let n = 1;\n while (slots[`slot-${n}`]) n += 1;\n return `slot-${n}`;\n}\n\n// bindingOfSelector \u2014 the entity binding a concrete device/anchor selector names\n// (measurements are dropped \u2014 a binding is the entity only). undefined for a device\n// selector with no token, or a non-concrete selector.\nfunction bindingOfSelector(ds: DatasourceSelector | undefined): SlotBinding | undefined {\n if (ds?.kind === 'device') return ds.deviceToken ? { kind: 'device', deviceToken: ds.deviceToken } : undefined;\n // Guard an empty target token (symmetric with device): a token-less anchor names no\n // entity, so it can't be a binding \u2014 and parseSlotBinding would drop it, so slotting it\n // would silently lose the relationship/targetType on reload.\n if (ds?.kind === 'anchor') {\n return ds.anchor.targetToken ? { kind: 'anchor', anchor: ds.anchor } : undefined;\n }\n return undefined;\n}\n\n// findOrAddSlot returns the name of the slot bound to `binding`, creating one (in\n// the passed, freshly-copied map) when none exists. Dedup lives here. It only reuses a\n// PLAIN (unscoped) slot: a scoped slot follows a parent, so folding a plain widget onto\n// one that merely shares the same default binding would silently give the widget cascade\n// behavior it never asked for. The migration/authoring path only ever mints plain slots.\nfunction findOrAddSlot(slots: Record<string, SlotDefinition>, binding: SlotBinding): string {\n const existing = Object.keys(slots).find((k) => !slots[k].scope && sameBinding(slots[k].defaultBinding, binding));\n if (existing) return existing;\n const name = nextSlotName(slots);\n slots[name] = { type: binding.kind, label: bindingLabel(binding), defaultBinding: binding };\n return name;\n}\n\n// migrateToSlots rewrites every widget's concrete device/anchor selector into a slot\n// selector (default-bound to that entity), deduping identical bindings into one\n// shared slot. Idempotent: slot selectors (and slot-free widgets) pass through\n// untouched, so re-running is a no-op. This is the decisive pre-GA cutover applied\n// when the console loads a dashboard.\nexport function migrateToSlots(def: DashboardDefinition): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const ds = w.datasource;\n // Leave an anchor that carries a Phase-2 `aggregation` as a concrete selector: the\n // slot model has no aggregation field, so slotting it would drop that (reserved)\n // config. It coexists fine \u2014 the Hub resolves concrete anchors too. (Editing such a\n // widget in the panel still drops aggregation, but that's an explicit action.)\n if (ds?.kind === 'anchor' && ds.aggregation) return w;\n const binding = bindingOfSelector(ds);\n if (!binding || !ds) return w;\n changed = true;\n const slot = findOrAddSlot(slots, binding);\n // The location series moves across with the measurement names: it says WHICH SERIES\n // the widget reads, and slotting changes only WHICH ENTITY it reads them from.\n // Dropping it here would silently blank every map on the board at load time.\n return { ...w, datasource: slotSelector(slot, ds.measurements, ds.location) };\n });\n // Return the SAME reference (no spurious `slots:{}`) when nothing migrated, so the\n // early-out is a true no-op and the result round-trips byte-identically.\n if (!changed) return def;\n return { ...def, widgets, slots };\n}\n\n// bindWidgetSlot points a widget at the slot for `binding` (creating/reusing it,\n// deduped) with the given measurement names \u2014 the editor's rebind + measurements\n// operation. Picking a NEW entity forks a fresh slot, leaving other widgets' slots\n// alone. A measurements-only edit (binding unchanged) KEEPS the widget's own slot even\n// when another slot shares the binding \u2014 so a distinct slot the host may override\n// separately (I-3 templates) isn't silently collapsed away.\n// `location` names the location series the widget reads, when it reads one. It is a\n// parameter rather than something preserved from the widget's current selector for the\n// same reason `measurements` is: the caller (the config panel) is the one that knows\n// which series the widget wants, and a map widget's FIRST datasource has no previous\n// selector to preserve it from.\nexport function bindWidgetSlot(\n def: DashboardDefinition,\n widgetId: string,\n binding: SlotBinding,\n measurements: string[],\n location?: LocationSelection,\n): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n const current = def.widgets.find((w) => w.id === widgetId)?.datasource;\n const currentSlot = current?.kind === 'slot' ? current.slot : undefined;\n const currentDef = currentSlot ? slots[currentSlot] : undefined;\n // Keep the widget's own slot when it is SCOPED (a context slot the cascade drives \u2014\n // never silently rehome it onto a plain slot) or when the binding is unchanged;\n // otherwise fork/reuse a plain slot for the new entity.\n const slot =\n currentSlot && currentDef && (currentDef.scope || sameBinding(currentDef.defaultBinding, binding))\n ? currentSlot\n : findOrAddSlot(slots, binding);\n const widgets = def.widgets.map((w) =>\n // A placeholder for an unknown widget type binds nothing this build can see, so it is\n // never given a datasource here: its bindings live in its raw object.\n w.id === widgetId && w.type !== UNKNOWN_WIDGET_TYPE ? { ...w, datasource: slotSelector(slot, measurements, location) } : w,\n );\n return { ...def, widgets, slots };\n}\n\n// clearWidgetDatasource drops a widget's datasource (the \"None\" data source). Prune\n// afterwards to reclaim a now-orphaned slot.\nexport function clearWidgetDatasource(def: DashboardDefinition, widgetId: string): DashboardDefinition {\n const widgets = def.widgets.map((w) => {\n if (w.id !== widgetId) return w;\n const { datasource: _drop, ...rest } = w;\n return rest;\n });\n return { ...def, widgets };\n}\n\n// collectRawSlotReferences adds every slot name a raw (unparsed) widget object names,\n// in the two shapes this release knows a slot reference by \u2014 a slot selector\n// `{kind:'slot', slot}` and a `selectionTarget` \u2014 at any depth. Over-counting keeps an\n// unused slot, which is harmless; under-counting deletes a newer widget's binding. The\n// depth bound only guards against a hand-built cyclic object (parsed JSON has none).\nconst RAW_SLOT_SCAN_DEPTH = 32;\nfunction collectRawSlotReferences(value: unknown, used: Set<string>, depth: number): void {\n if (depth > RAW_SLOT_SCAN_DEPTH || typeof value !== 'object' || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) collectRawSlotReferences(item, used, depth + 1);\n return;\n }\n const rec = value as Record<string, unknown>;\n if (rec.kind === 'slot' && typeof rec.slot === 'string' && rec.slot.length > 0) used.add(rec.slot);\n if (typeof rec.selectionTarget === 'string' && rec.selectionTarget.length > 0) used.add(rec.selectionTarget);\n for (const child of Object.values(rec)) collectRawSlotReferences(child, used, depth + 1);\n}\n\n// pruneSlots removes slots no widget references, and omits the `slots` key entirely\n// when none remain (so a dashboard that loses its last slot serializes clean).\nexport function pruneSlots(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const defSlots = def.slots;\n const used = new Set<string>();\n for (const w of def.widgets) {\n if (w.datasource?.kind === 'slot') used.add(w.datasource.slot);\n // A widget can also REFERENCE a slot without binding it: a context/entity-selector's\n // (and an alarm-table drill's) `options.selectionTarget` names the slot it re-points.\n // Miss it and deleting the data widget that binds that slot would prune it out from\n // under the selector \u2014 a silent \"No options\". Count it as a use.\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target.length > 0) used.add(target);\n // A widget of a type this build does not know uses slots this build cannot see in\n // its parsed fields: they are inside its raw object, possibly nested (a container's\n // children). Pruning them would make the next save delete the newer widget's slots\n // out from under it, so every slot the raw object names counts as a use.\n if (w.raw) collectRawSlotReferences(w.raw, used, 0);\n }\n // Close `used` over scope.parent: a context-only parent slot (e.g. a root 'building'\n // that no widget binds directly but a scoped child depends on) must be kept, or the\n // child dangles and the cascade loses its top-level context. Walk each used slot's\n // ancestor chain (guarded against a hand-edited cycle) adding every parent.\n for (const start of [...used]) {\n let cur: string | undefined = start;\n const guard = new Set<string>();\n while (cur && Object.prototype.hasOwnProperty.call(defSlots, cur) && !guard.has(cur)) {\n guard.add(cur);\n const parent: string | undefined = defSlots[cur].scope?.parent;\n if (parent) used.add(parent);\n cur = parent;\n }\n }\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) if (used.has(name)) slots[name] = slot;\n if (Object.keys(slots).length === 0) {\n const { slots: _drop, ...rest } = def;\n return rest;\n }\n return { ...def, slots };\n}\n\n// anchorSlotNames lists the dashboard's anchor-typed slots \u2014 the candidate PARENTS a\n// scoped slot may attach to (an editor's parent picker), and the slots a root context-\n// selector can target. Empty when the dashboard has no slots.\nexport function anchorSlotNames(def: DashboardDefinition): string[] {\n const slots = def.slots ?? {};\n return Object.keys(slots).filter((name) => slots[name].type === 'anchor');\n}\n\n// setSlotScope makes `slotName` scoped to a parent (the context hierarchy) or, with\n// `scope` undefined, clears its scope back to a root context \u2014 the editor's scope-authoring\n// transform. It is a no-op when the slot doesn't exist, or when the proposed scope is\n// invalid (canScopeSlot: parent missing/non-anchor/self/cycle) \u2014 degrade, never write a\n// scope the loader would drop on reload. Prototype-free (operates on the parsed slots map).\nexport function setSlotScope(\n def: DashboardDefinition,\n slotName: string,\n scope: SlotScope | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const slots: Record<string, SlotDefinition> = { ...def.slots };\n const current = slots[slotName];\n if (!scope) {\n if (!current.scope) return def; // already root \u2014 no-op (keeps the reference stable)\n const { scope: _drop, ...rest } = current;\n slots[slotName] = rest;\n return { ...def, slots };\n }\n if (!canScopeSlot(slots, slotName, scope.parent)) return def;\n slots[slotName] = { ...current, scope: { parent: scope.parent, strategy: scope.strategy } };\n return { ...def, slots };\n}\n\n// widgetBinding resolves a widget's effective entity binding for the editor: a slot\n// widget's default binding, or a (pre-migration) concrete selector's entity.\nexport function widgetBinding(def: DashboardDefinition, widget: WidgetInstance): SlotBinding | undefined {\n const ds = widget.datasource;\n if (ds?.kind === 'slot') return def.slots?.[ds.slot]?.defaultBinding;\n return bindingOfSelector(ds);\n}\n\n// widgetSlotName is the slot a widget references, if any (for an editor hint).\nexport function widgetSlotName(widget: WidgetInstance): string | undefined {\n return widget.datasource?.kind === 'slot' ? widget.datasource.slot : undefined;\n}\n\n// A concrete device/anchor selector \u2014 what the config panel edits (slot-agnostic).\n// Rebuilt into slot storage by the workspace via bindWidgetSlot.\nexport type ConcreteSelector =\n | { kind: 'device'; deviceToken: string; measurements: string[]; location?: LocationSelection }\n | { kind: 'anchor'; anchor: AnchorTarget; measurements: string[]; location?: LocationSelection };\n\n// resolveConcrete gives the config panel a slot-free view of a widget's data source:\n// the bound entity + the series the widget reads (measurement names, and the location\n// series when it names one), or undefined when unbound.\n//\n// \uD83D\uDD34 A SELECTOR THE AUTHOR IS STILL FILLING IN IS RETURNED AS IT STANDS. Choosing the Device\n// kind stores `{kind:'device', deviceToken:''}` on the widget (the workspace keeps a partial\n// selector concrete rather than slotting it -- see applyDatasource). It names no entity, so\n// it has no binding; but the panel renders from THIS value, and answering undefined for it\n// snapped the Kind control straight back to \"None\" and never showed the device picker, so a\n// device could not be chosen at all.\nexport function resolveConcrete(\n def: DashboardDefinition,\n widget: WidgetInstance,\n): ConcreteSelector | undefined {\n const binding = widgetBinding(def, widget);\n const measurements = widget.datasource?.measurements ?? [];\n const location = widget.datasource?.location;\n const base = location ? { measurements, location } : { measurements };\n if (!binding) {\n const ds = widget.datasource;\n if (ds?.kind === 'device') return { kind: 'device', deviceToken: ds.deviceToken, ...base };\n if (ds?.kind === 'anchor') return { kind: 'anchor', anchor: ds.anchor, ...base };\n return undefined;\n }\n return binding.kind === 'device'\n ? { kind: 'device', deviceToken: binding.deviceToken, ...base }\n : { kind: 'anchor', anchor: binding.anchor, ...base };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// resolveSlotCandidates \u2014 the option set a context/entity-selector widget offers for one\n// target slot (ADR-039 selection amendment). It is the picker's half of the cascade: the\n// cascade (resolveContextBindings) DERIVES a scoped slot's binding, and this enumerates the\n// bindings a viewer may pick FROM \u2014 sharing the same member source (DeviceResolver) so the\n// picker's list and the strategies can never disagree.\n//\n// scoped child slot \u2192 the parent anchor's member devices (devicesForAnchor), one option\n// per member; empty / unbound / non-anchor parent \u21D2 no options.\n// root device slot \u2192 the tenant's devices (the injected lister).\n// root anchor slot \u2192 the tenant's entities of the slot's target type; the anchor\n// relationship + targetType come from the slot's CURRENT binding (or\n// its default) as a template, only the targetToken varies. A root\n// anchor slot with no bound/default anchor has no template \u21D2 no options.\n//\n// Pure and fail-safe: a membership/lister error yields [] (an empty picker), never a throw \u2014\n// matching resolveContextBindings' degrade-don't-throw contract.\n\nimport type { MemberResolver } from './context';\nimport type {\n DashboardDefinition,\n EntityCandidateLister,\n SelectionCandidate,\n SlotBinding,\n} from './types';\n\nfunction ownGet<T>(map: Record<string, T> | undefined, key: string): T | undefined {\n return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n}\n\n// sameBinding \u2014 value equality of two bindings, so a candidate can flag the current pick.\nfunction sameBinding(a: SlotBinding | undefined, b: SlotBinding): boolean {\n if (!a) return false;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') return a.anchor.targetToken === b.anchor.targetToken;\n return false;\n}\n\nexport async function resolveSlotCandidates(\n definition: DashboardDefinition,\n slot: string,\n bindings: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister: EntityCandidateLister,\n): Promise<SelectionCandidate[]> {\n const slots = definition.slots ?? {};\n const def = ownGet(slots, slot);\n if (!def) return [];\n const current = ownGet(bindings, slot);\n const mark = (binding: SlotBinding, label: string): SelectionCandidate => ({\n binding,\n label,\n selected: sameBinding(current, binding),\n });\n\n // Scoped child: the parent anchor's members (the same source the cascade's strategies use).\n if (def.scope) {\n // A 'first' slot is fully auto-derived (the cascade always binds the parent's first\n // member and IGNORES any selection), so a picker over it would snap back on every pick.\n // Only a 'manual' slot is user-pickable; offer no options for 'first'.\n if (def.scope.strategy === 'first') return [];\n const parentBinding = ownGet(bindings, def.scope.parent);\n if (!parentBinding || parentBinding.kind !== 'anchor') return [];\n let members: string[];\n try {\n members = [...(await resolver.devicesForAnchor(parentBinding.anchor))].sort();\n } catch {\n return [];\n }\n return members.map((token) => mark({ kind: 'device', deviceToken: token }, token));\n }\n\n // Root device slot: list the tenant's devices.\n if (def.type === 'device') {\n let rows: Array<{ token: string; name?: string | null }>;\n try {\n rows = await lister('device');\n } catch {\n return [];\n }\n return rows.map((r) => mark({ kind: 'device', deviceToken: r.token }, r.name || r.token));\n }\n\n // Root anchor slot: list the tenant's entities of the target type, reusing the current/\n // default binding's relationship+targetType as the template (only targetToken varies).\n const template =\n current?.kind === 'anchor'\n ? current.anchor\n : def.defaultBinding?.kind === 'anchor'\n ? def.defaultBinding.anchor\n : undefined;\n if (!template) return [];\n let rows: Array<{ token: string; name?: string | null }>;\n try {\n rows = await lister(template.targetType);\n } catch {\n return [];\n }\n return rows.map((r) =>\n mark(\n {\n kind: 'anchor',\n anchor: {\n relationship: template.relationship,\n targetType: template.targetType,\n targetToken: r.token,\n },\n },\n r.name || r.token,\n ),\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// createEntityLister \u2014 the device-management-backed EntityCandidateLister a host injects to\n// back a ROOT context-selector's candidate set (ADR-039 selection amendment). It lives here,\n// beside createDeviceResolver, because this is the package's one seam onto device-management's\n// schema; the widget/renderer layers stay presentational and both apps (console + the /dash\n// reference viewer) share ONE implementation rather than hand-authoring the same query twice.\n//\n// Results are memoized per kind for the lister's lifetime: a context-selector re-opens over a\n// viewing session, and the tenant's customers/areas are stable enough that a per-mount refetch\n// buys nothing. A failed load drops the cache entry so the next open retries.\n\nimport { gql } from '@devicechain/client';\n\nimport { LIST_AREAS, LIST_ASSETS, LIST_CUSTOMERS, LIST_DEVICES } from './queries';\nimport type { EntityCandidateLister, EntityListKind } from './types';\n\n// A generous single page \u2014 a flat picker over tens of entities, filtered client-side.\nconst LIST_PAGE_SIZE = 500;\n\nexport function createEntityLister(): EntityCandidateLister {\n const cache = new Map<EntityListKind, Promise<Array<{ token: string; name: string | null }>>>();\n const criteria = { pageNumber: 1, pageSize: LIST_PAGE_SIZE };\n\n return (kind: EntityListKind) => {\n let pending = cache.get(kind);\n if (!pending) {\n pending = fetchKind(kind, criteria).catch((err) => {\n cache.delete(kind);\n throw err;\n });\n cache.set(kind, pending);\n }\n return pending;\n };\n}\n\nfunction fetchKind(\n kind: EntityListKind,\n criteria: { pageNumber: number; pageSize: number },\n): Promise<Array<{ token: string; name: string | null }>> {\n switch (kind) {\n case 'device':\n return gql('device-management', LIST_DEVICES, { criteria }).then((r) => r.devices.results);\n case 'customer':\n return gql('device-management', LIST_CUSTOMERS, { criteria }).then((r) => r.customers.results);\n case 'area':\n return gql('device-management', LIST_AREAS, { criteria }).then((r) => r.areas.results);\n case 'asset':\n return gql('device-management', LIST_ASSETS, { criteria }).then((r) => r.assets.results);\n default:\n // An out-of-union kind (a hand-edited definition can carry an empty/other targetType)\n // yields no candidates rather than throwing synchronously inside the lister.\n return Promise.resolve([]);\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Hand-authored typed GraphQL documents the dashboard runtime issues to resolve\n// datasources and seed history. Like the rest of the package there is no codegen \u2014\n// the SDK runs in documentMode 'string', so a raw query string cast to\n// TypedDocument<Result, Vars> is exactly what a generated document is at runtime.\n// Each doc targets one service area (see the `gql(area, ...)` call sites in\n// resolver.ts / history.ts).\n\nimport type { TypedDocument } from '@devicechain/client';\n\n// \u2500\u2500 device-management: the devices anchored to a target (area/customer/asset) \u2500\n// Filters relationships whose source is a device and whose target is the anchor\n// entity; `source { token }` yields each member device's token (measurementStream\n// is keyed by token, per ADR-044).\n\nexport interface EntityRelationshipsResult {\n entityRelationships: {\n results: Array<{ source: { token: string } }>;\n };\n}\nexport interface EntityRelationshipsVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n sourceType: string;\n targetType: string;\n target: string;\n relationshipType?: string | null;\n };\n}\n\nexport const DEVICES_FOR_ANCHOR = `\n query DevicesForAnchor($criteria: EntityRelationshipSearchCriteria!) {\n entityRelationships(criteria: $criteria) {\n results {\n source {\n token\n }\n }\n }\n }\n` as unknown as TypedDocument<EntityRelationshipsResult, EntityRelationshipsVariables>;\n\n// \u2500\u2500 device-management: which of the given device tokens still exist \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A dashboard references a device by a STABLE token (ADR-044); the token re-key\n// dropped the token\u2192id hop that used to fail on a deleted device, so a widget bound to\n// a since-deleted device streamed nothing and rendered blank. This existence check\n// restores the \"unavailable\" signal \u2014 devicesByToken returns only the tokens that\n// resolve, so a missing token is absent from the result.\n\nexport interface DevicesByTokenResult {\n devicesByToken: Array<{ token: string }>;\n}\nexport interface DevicesByTokenVariables {\n tokens: string[];\n}\n\nexport const DEVICES_BY_TOKEN = `\n query DashboardDevicesByToken($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n }\n }\n` as unknown as TypedDocument<DevicesByTokenResult, DevicesByTokenVariables>;\n\n// \u2500\u2500 device-management: list entities of one kind (root context-selector candidates) \u2500\n// Each anchor target type (customer/area/asset) plus bare devices exposes a\n// `<kind>(criteria){results{token name}}` query. A root context-selector lists these so a\n// viewer can re-point the dashboard's top-level context (which building/customer). One doc\n// per kind since each is a distinct root field; a generous single page (dashboards pick\n// among tens, not thousands \u2014 nested tree picking is deferred).\n\nexport interface EntityListResult {\n results: Array<{ token: string; name: string | null }>;\n}\nexport interface EntityListVariables {\n criteria: { pageNumber: number; pageSize: number };\n}\n\nexport const LIST_DEVICES = `\n query DashboardListDevices($criteria: DeviceSearchCriteria!) {\n devices(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ devices: EntityListResult }, EntityListVariables>;\n\nexport const LIST_CUSTOMERS = `\n query DashboardListCustomers($criteria: CustomerSearchCriteria!) {\n customers(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ customers: EntityListResult }, EntityListVariables>;\n\nexport const LIST_AREAS = `\n query DashboardListAreas($criteria: AreaSearchCriteria!) {\n areas(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ areas: EntityListResult }, EntityListVariables>;\n\nexport const LIST_ASSETS = `\n query DashboardListAssets($criteria: AssetSearchCriteria!) {\n assets(criteria: $criteria) {\n results {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<{ assets: EntityListResult }, EntityListVariables>;\n\n// \u2500\u2500 event-management: bucketed history for chart seeding \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface MeasurementBucket {\n bucketStart: string;\n name: string;\n avg: number | null;\n}\nexport interface BucketedMeasurementsResult {\n bucketedMeasurements: MeasurementBucket[];\n}\nexport interface BucketedMeasurementsVariables {\n criteria: {\n deviceToken: string;\n name?: string | null;\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n };\n}\n\nexport const BUCKETED_MEASUREMENTS = `\n query BucketedMeasurements($criteria: MeasurementAggregationCriteria!) {\n bucketedMeasurements(criteria: $criteria) {\n bucketStart\n name\n avg\n }\n }\n` as unknown as TypedDocument<BucketedMeasurementsResult, BucketedMeasurementsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// DashboardHub \u2014 the dashboard runtime (ADR-039).\n//\n// It multiplexes every widget's live telemetry over the SDK's single per-area\n// graphql-ws connection: many widgets bound to the same device share ONE upstream\n// measurementStream subscription (ref-counted), so a crowded dashboard opens one\n// stream per distinct device token, not one per widget. A per-widget subscription\n// model fans out badly on a crowded board. The Hub owns the subscription\n// lifecycle; widgets just hand it a datasource selector and a sink.\n\nimport { gql, isForbiddenError, subscribe, type Area, type SubscriptionSink } from '@devicechain/client';\n\nimport {\n ACKNOWLEDGE_ALARM,\n ALARM_STREAM,\n ALARMS_QUERY,\n CLEAR_ALARM,\n type AlarmSearchCriteriaInput,\n type AlarmStreamResult,\n} from './internal/alarm-doc';\nimport {\n COMMANDS_QUERY,\n CREATE_COMMAND,\n} from './internal/command-doc';\nimport { LATEST_LOCATIONS_QUERY } from './internal/location-doc';\nimport {\n MEASUREMENT_STREAM,\n type MeasurementStreamResult,\n type MeasurementStreamVariables,\n} from './internal/measurement-doc';\nimport type {\n AlarmRow,\n AnchorTarget,\n CommandRow,\n DatasourceSelector,\n LocationSample,\n MeasurementSample,\n SlotBinding,\n} from './types';\n\nconst EVENT_AREA: Area = 'event-management';\nconst DEVICE_AREA: Area = 'device-management';\nconst COMMAND_AREA: Area = 'command-delivery';\nconst STATE_AREA: Area = 'device-state';\n\n// randomToken mints a command dispatch token (its idempotency key + cancel handle).\n// crypto.randomUUID is only defined in a secure context, so fall back to a random-hex\n// token for a plain-HTTP on-prem host \u2014 matching the guarded pattern generateWidgetId\n// uses rather than throwing at send time.\nfunction randomToken(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return c.randomUUID();\n return `cmd-${Math.random().toString(16).slice(2)}-${Math.random().toString(16).slice(2)}`;\n}\n\n// Alarm channel cadence. The live stream is a best-effort trigger, so the poll is\n// the correctness backstop (an alarm cleared while the socket was down still\n// converges within one poll); the debounce coalesces a burst of events into one\n// re-query.\nconst ALARM_RECONCILE_DEBOUNCE_MS = 800;\nconst ALARM_POLL_MS = 30_000;\n\n// Command channel cadence. command-delivery exposes NO subscription, so the control\n// channel is poll-only \u2014 but a command's lifecycle (QUEUED\u2192SENT\u2192SUCCESSFUL)\n// resolves in seconds, so it polls far faster than the alarm channel. An issued command\n// reconciles immediately (not on the next tick) so the operator sees it appear at once.\nconst COMMAND_POLL_MS = 4_000;\n\n// Location channel cadence. device-state exposes NO location subscription either, so\n// like the control channel this is poll-only \u2014 but the two poll for opposite reasons,\n// and the cadence follows from that rather than from copying a number:\n//\n// \u2022 the control channel polls FAST (4s) for a SHORT time: a command's lifecycle is a\n// burst that reaches a terminal status in seconds and then stops changing.\n// \u2022 the alarm channel polls SLOWLY (30s) because it is only a backstop \u2014 the live\n// ALARM_STREAM does the real work and the poll exists for what the socket missed.\n//\n// A fleet's positions have neither property: they change continuously, for as long as\n// the board is open, with nothing else watching them. So this poll is not a backstop,\n// it IS the channel, and its cost is a standing one. What makes a middle cadence\n// affordable is the BATCH query: one `latestLocations` round trip per tick covers\n// every device on the widget, so the cost is per-poll, not per-marker, and a 200-device\n// map costs what a 2-device map costs. Anything materially faster than this wants a\n// subscription rather than a tighter poll \u2014 a tighter poll would multiply a whole\n// board's queries against a projection that is only written when a device moves.\nconst LOCATION_POLL_MS = 15_000;\n\n// DeviceResolver turns the graph references in a dashboard definition into the\n// device tokens event-management keys on (measurementStream(deviceToken:), per\n// ADR-044). It is injected so this package carries no device-management coupling\n// and stays unit-testable; a host backs it with device-management queries.\nexport interface DeviceResolver {\n // The device tokens currently anchored to the given target. This is where\n // \"the Hub expands an anchor to its current membership\" lives (Phase 1);\n // server-side expansion is a Phase-2 optimization.\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]>;\n // Whether a device with this token currently exists (device-management). Backs the\n // widget availability check: a dashboard references a device by a stable token\n // (ADR-044), and a since-deleted device's token no longer resolves.\n deviceExists(deviceToken: string): Promise<boolean>;\n}\n\n// WidgetStreamSink receives live samples for one widget, across every device its\n// datasource resolves to. next() fires per sample; error() once if selector\n// resolution or the socket fails.\nexport interface WidgetStreamSink {\n next: (sample: MeasurementSample) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Alarm channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Alarm widgets consume a different surface than telemetry: the raised-alarm rows\n// (ADR-041), read query-then-reconcile. An alarm subscription describes the SCOPE\n// (which entity's alarms) plus the server-side filters, and receives whole snapshots\n// (not incremental events), because the authoritative rows come from the query.\n\n// AlarmSubscription is one alarm widget's interest: its scope selector (undefined =\n// tenant-wide \u2014 every alarm the viewer can see) plus filters. `pageSize` bounds the\n// rows returned (an alarm table shows the newest N); the total count reported in a\n// snapshot is independent of it (server totalRecords), so an alarm-count reflects the\n// true match count even past the page.\nexport interface AlarmSubscription {\n datasource?: DatasourceSelector;\n state?: string;\n severity?: string;\n acknowledged?: boolean;\n pageSize: number;\n}\n\n// A full alarm snapshot: the current rows (newest first, capped to pageSize) and the\n// total number of alarms matching the filter (past the page). One replaces the last.\nexport interface AlarmSnapshot {\n alarms: AlarmRow[];\n total: number;\n}\n\nexport interface AlarmStreamSink {\n next: (snapshot: AlarmSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Control channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The command-button widget issues commands to a device and watches their delivery\n// lifecycle. Unlike telemetry/alarms there is no live subscription (command-delivery\n// exposes none), so the channel is poll-only: a device-scoped `commands` query re-read\n// on a short interval (and immediately after an issue). A command targets ONE device,\n// so a subscription resolves its scope to a single device token.\n\n// CommandSubscription is one command widget's interest: its scope (which device) plus\n// the page size bounding the recent-command history it shows. A command button binds a\n// single device; an unscoped (or unresolved) widget has no target and renders empty.\nexport interface CommandSubscription {\n datasource?: DatasourceSelector;\n pageSize: number;\n}\n\n// A command-history snapshot: the resolved target device (null when unbound \u2014 the\n// widget then can't issue), the recent commands (newest first, capped to pageSize) and\n// the total matching count. One replaces the last (poll-then-emit).\nexport interface CommandSnapshot {\n deviceToken: string | null;\n commands: CommandRow[];\n total: number;\n}\n\nexport interface CommandStreamSink {\n next: (snapshot: CommandSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// The result of issuing a command. It is a DISCRIMINATED UNION rather than a token,\n// because the enqueue has two possible answers and a widget must not show them the same\n// way:\n//\n// - 'sent' \u2014 the command was created; `token` is its freshly-minted dispatch\n// token, so the widget can highlight the command it just issued as it\n// moves through the lifecycle.\n// - 'rejected' \u2014 the server REFUSED the request and said why: `code` is the stable\n// classification to branch on (DEVICE_NOT_FOUND,\n// COMMAND_NOT_IN_VOCABULARY, PAYLOAD_SCHEMA_VIOLATION,\n// PAYLOAD_NOT_JSON, METADATA_NOT_JSON, EXPIRES_AT_INVALID,\n// HELD_CEILING_EXCEEDED, \u2026 \u2014 an OPEN set), `reason` is client-safe\n// prose to show a person.\n//\n// A THROWN error is neither: it means the platform could not decide the enqueue at all\n// (the service unreachable, a database error), which says nothing about the command and\n// so deserves a generic failure message rather than a reason.\n//\n// The union is deliberately not \"token, plus an optional rejection\". A caller reading\n// `.token` off a refusal would compile, highlight nothing, and report no failure \u2014 the\n// send would look successful. The discriminant makes the refusal impossible to ignore\n// at the type level.\nexport type CommandDispatch =\n | { status: 'sent'; token: string }\n | { status: 'rejected'; code: string; reason: string };\n\n// \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The map widget consumes a fifth surface: where the bound devices ARE. Like the\n// control channel there is no live subscription to ride (device-state exposes none for\n// position), so it is query-then-poll \u2014 the alarm and command channels are the\n// precedent here, not the measurement channel.\n//\n// What is different from all three is the REFUSAL. Position is gated on its own\n// `location:read` authority, which is deliberately absent from the read-only viewer\n// baseline, so an ordinary member with full telemetry access is routinely refused. The\n// channel therefore turns a refusal into a VALUE rather than an error: it is not a\n// fault, and it is not emptiness either.\n\n// LocationSubscription is one map widget's interest: the selector naming both the\n// devices and the location series to read. There is no page size \u2014 a map shows every\n// device it is bound to, and the bound set is the selector's own resolution.\nexport interface LocationSubscription {\n datasource?: DatasourceSelector;\n}\n\n// A location snapshot, discriminated so the refusal cannot be mistaken for emptiness.\n//\n// \uD83D\uDD34 `positions` with an empty `locations` and `forbidden` are OPPOSITE facts, and only\n// one of them is actionable. \"No device here has ever reported a position\" is a claim\n// about the DEVICES; \"you may not view location\" is a claim about the CALLER. Folding\n// them together (a bare `locations: []`) would tell an operator their fleet is\n// unlocated when the truth is that they need a role \u2014 which is the exact mistake the\n// device-detail position panel was built to avoid, held here in the type.\n//\n// `deviceTokens` is the set the selector resolved to, carried alongside the positions\n// because a never-located device is ABSENT from the query result: without it, \"bound to\n// nothing\" and \"bound to devices that have never moved\" are indistinguishable.\nexport type LocationSnapshot =\n | { kind: 'positions'; deviceTokens: string[]; locations: LocationSample[] }\n | { kind: 'forbidden' };\n\nexport interface LocationStreamSink {\n next: (snapshot: LocationSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Action seam (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Read widgets are pure `(widget, data)`; a widget that ACTS (acknowledge/clear an\n// alarm, send a command) never touches the SDK either \u2014 it calls this seam, which the\n// renderer threads in from the runtime. So preview stays offline (SyntheticDataSource\n// implements a no-op/echo WidgetActions) and the \"widget never reaches the backend\"\n// invariant holds. `can` gates the UI: a widget hides an action the viewer isn't\n// authorized for (the server enforces it regardless).\n//\n// Growing this interface is a breaking change for any external host implementing it.\n// The alarm actions (acknowledgeAlarm/clearAlarm) are the required baseline; capabilities\n// added later \u2014 like sendCommand below \u2014 are declared OPTIONAL so a host predating them\n// still satisfies the type, and consumers feature-detect (typeof actions?.sendCommand).\nexport interface WidgetActions {\n // Acknowledge / clear a raised alarm by token (requires alarm:write). Resolves when\n // the mutation succeeds; the runtime reconciles the affected alarm widgets so the\n // change shows immediately rather than waiting for the next poll.\n acknowledgeAlarm(alarmToken: string): Promise<void>;\n clearAlarm(alarmToken: string): Promise<void>;\n // Issue a command to a device (requires command:write). The runtime mints the dispatch\n // token and returns it; it also reconciles the command widgets so the new command\n // appears at once. It RESOLVES on a refusal too \u2014 with a 'rejected' dispatch carrying\n // the server's code + reason (see CommandDispatch) \u2014 and rejects only when the enqueue\n // could not be decided at all. OPTIONAL: added after this interface shipped, so a host\n // predating it (or a strictly read-only one) may omit it \u2014 a command widget then\n // renders its Send control disabled. `payload` is the request body sent to the device\n // verbatim (the widget serializes its typed parameter form to JSON).\n sendCommand?(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch>;\n // Whether the current viewer holds an authority (e.g. 'alarm:write'). Drives whether\n // an action control renders at all.\n can(authority: string): boolean;\n}\n\n// The hub holds NO slot bindings. A widget's `slot` selector is resolved by the renderer\n// (resolveWidgetDatasource) through the settled bindings before it reaches the hub, so one\n// hub serves a board for its whole life: a selection that re-points a slot changes the\n// concrete selector of just the widgets bound to it, and only those re-subscribe. A host\n// builds one hub per (resolver, authorities).\nexport interface DashboardHubConfig {\n resolver: DeviceResolver;\n // The current viewer's authorities (access-token claims). Drives `can()` \u2014 which\n // gates whether a widget's action controls render. Omitted/empty = no write actions\n // (the read-only default); '*' grants all. The server enforces authority regardless.\n authorities?: string[];\n}\n\n// WidgetDataSource is the minimal contract a widget renderer needs from a data\n// source: bind a datasource selector to a sink, get a disposer back. DashboardHub\n// is the live implementation (multiplexed backend telemetry); SyntheticDataSource\n// is the offline preview implementation. The widget layer depends on THIS interface,\n// not the concrete class, so a host can feed either without widgets knowing which.\nexport interface WidgetDataSource {\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void;\n // Bind an alarm widget's scope+filters to a sink; returns a disposer. Delivers\n // whole snapshots (query-then-reconcile), not incremental events. Implemented by\n // both the live hub and the synthetic preview source so alarm widgets render\n // identically from either.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void;\n // Bind a command widget's scope to a sink; returns a disposer. Delivers whole\n // command-history snapshots on a poll (command-delivery has no subscription).\n // Implemented by both the live hub and the synthetic preview source.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;\n // Bind a map widget's selector to a sink; returns a disposer. Delivers whole\n // position snapshots on a poll (device-state has no location subscription), and\n // reports a `location:read` refusal as a snapshot state rather than an error.\n // Implemented by both the live hub and the synthetic preview source.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;\n // Whether a widget's bound entity still exists. Optimistic + async: a widget renders\n // from its stream immediately, and this resolves separately \u2014 only a device selector\n // (or a slot bound to a device) whose token no longer resolves reports false, so the\n // widget shows \"unavailable\" instead of a blank pane. Anchor / unbound-slot / no-\n // datasource report true (their empty state is legitimate, not \"unavailable\"). Fails\n // OPEN (true) on an inconclusive check, so a device-management blip never falsely marks\n // a live device unavailable.\n isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;\n}\n\n// One widget's interest in a device stream: the measurement names it wants (an\n// empty set means every measurement) and where to deliver them.\ninterface Subscriber {\n names: Set<string>;\n sink: WidgetStreamSink;\n}\n\n// The shared upstream for one distinct device token.\ninterface DeviceStream {\n subscribers: Set<Subscriber>;\n unsubscribe: () => void;\n}\n\nexport class DashboardHub implements WidgetDataSource, WidgetActions {\n private readonly resolver: DeviceResolver;\n // The viewer's authorities (for can()); '*' is the superuser wildcard.\n private readonly authorities: ReadonlySet<string>;\n // Per alarm-subscription reconcile triggers \u2014 invoked after an ack/clear so the\n // affected alarm widgets refresh immediately instead of waiting for the poll/stream.\n private readonly alarmReconcilers = new Set<() => void>();\n // Per command-subscription reconcile triggers \u2014 invoked after an issue so the command\n // widgets show the new command immediately instead of waiting for the next poll tick.\n private readonly commandReconcilers = new Set<() => void>();\n // One entry per distinct device token that has at least one subscriber.\n private readonly streams = new Map<string, DeviceStream>();\n // Live alarm-subscription disposers. The alarm channel isn't ref-counted through\n // `streams` (it holds a poll/debounce/trigger per subscription, not a shared device\n // stream), so its disposers are tracked here for disposeAll() to reach \u2014 otherwise an\n // imperative host closing the dashboard would leak every alarm widget's poll + socket.\n private readonly alarmDisposers = new Set<() => void>();\n // Live command-subscription disposers (same rationale as alarmDisposers \u2014 the control\n // channel holds a poll per subscription, not a shared device stream), so disposeAll()\n // can tear down every command widget's poll.\n private readonly commandDisposers = new Set<() => void>();\n // Live location-subscription disposers (same rationale as alarmDisposers/\n // commandDisposers \u2014 the location channel holds a poll per subscription, not a shared\n // device stream), so disposeAll() can tear down every map widget's poll.\n private readonly locationDisposers = new Set<() => void>();\n\n constructor(config: DashboardHubConfig) {\n this.resolver = config.resolver;\n this.authorities = new Set(config.authorities ?? []);\n }\n\n // subscribeWidget binds a widget's datasource to a sink and returns a disposer.\n // Selector resolution is async (anchor\u2192devices); the disposer is returned\n // synchronously and cancels a still-pending resolution, so tearing a widget down\n // before its streams open never attaches a leaked subscriber.\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n let disposed = false;\n const detachers: Array<() => void> = [];\n const dispose = (): void => {\n disposed = true;\n for (const detach of detachers.splice(0)) detach();\n };\n\n this.resolveDevices(datasource)\n .then((groups) => {\n if (disposed) return;\n for (const group of groups) {\n detachers.push(this.attach(group.deviceToken, group.names, sink));\n }\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // subscribeAlarms binds an alarm widget's scope+filters to a sink and returns a\n // disposer. Unlike the measurement channel it is NOT multiplexed \u2014 alarm widgets are\n // few, and each carries its own filter \u2014 so every subscription opens its own trigger\n // stream + reconcile poll (sharing one tenant-wide trigger stream across widgets is a\n // deferred optimization). Query-then-reconcile: an initial query, then the live\n // ALARM_STREAM debounced into re-queries, plus a poll backstop and a reconnect\n // re-query. Scope resolution is async (slot/anchor \u2192 devices); the disposer is\n // returned synchronously and cancels a still-pending resolution.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n let disposed = false;\n let debounce: ReturnType<typeof setTimeout> | undefined;\n let poll: ReturnType<typeof setInterval> | undefined;\n let unsubscribe: (() => void) | undefined;\n let reconciler: (() => void) | undefined;\n // Monotonic generation: only the newest reconcile's result may be emitted, so a\n // slow query that resolves after a newer one can't overwrite fresher rows.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (debounce) clearTimeout(debounce);\n if (poll) clearInterval(poll);\n unsubscribe?.();\n if (reconciler) this.alarmReconcilers.delete(reconciler);\n this.alarmDisposers.delete(dispose);\n };\n this.alarmDisposers.add(dispose);\n\n const reconcile = (tokens: string[], tenantWide: boolean): void => {\n const gen = ++generation;\n this.queryAlarms(subscription, tokens, tenantWide)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveAlarmScope(subscription.datasource)\n .then((scope) => {\n if (disposed) return;\n\n // A scoped widget that resolves to no device (an unbound slot, an empty anchor)\n // shows an empty state \u2014 NOT tenant-wide. Only a widget with no datasource at\n // all is tenant-wide. Nothing to stream/poll here. Scope is resolved once (like\n // the measurement channel): a slot rebind arrives as a new concrete selector, which\n // re-subscribes this widget, but organic anchor-membership change isn't picked up\n // until the widget re-subscribes \u2014 a deferred enhancement shared with the\n // measurement channel.\n if (!scope.tenantWide && scope.tokens.length === 0) {\n sink.next({ alarms: [], total: 0 });\n return;\n }\n\n const trigger = (): void => {\n if (debounce) clearTimeout(debounce);\n debounce = setTimeout(() => reconcile(scope.tokens, scope.tenantWide), ALARM_RECONCILE_DEBOUNCE_MS);\n };\n // Subscribe unfiltered (server filters resolve once at subscribe time and a\n // widget's scope may span devices) and treat every event as a reconcile\n // trigger \u2014 the query re-applies the scope+filters. On reconnect, re-query to\n // catch transitions missed while the socket was down.\n const adapter: SubscriptionSink<AlarmStreamResult> = {\n next: () => trigger(),\n connected: (wasRetry) => {\n if (wasRetry) reconcile(scope.tokens, scope.tenantWide);\n },\n };\n unsubscribe = subscribe(DEVICE_AREA, ALARM_STREAM, {}, adapter);\n poll = setInterval(() => reconcile(scope.tokens, scope.tenantWide), ALARM_POLL_MS);\n // Register a reconcile trigger so an ack/clear (via the action seam) refreshes\n // this widget immediately, not on the next poll tick.\n reconciler = () => reconcile(scope.tokens, scope.tenantWide);\n this.alarmReconcilers.add(reconciler);\n reconcile(scope.tokens, scope.tenantWide); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveAlarmScope turns an alarm widget's scope selector into the originator device\n // tokens to filter on, or tenant-wide when it carries no datasource. Reuses the same\n // device/anchor/unbound resolution the measurement channel does.\n //\n // \uD83D\uDD34 Only an ABSENT datasource is tenant-wide. `{kind:'unbound'}` is a datasource, and\n // it resolves to zero devices like any other empty scope.\n private async resolveAlarmScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<{ tenantWide: boolean; tokens: string[] }> {\n if (!datasource) return { tenantWide: true, tokens: [] };\n const groups = await this.resolveDevices(datasource);\n return { tenantWide: false, tokens: groups.map((g) => g.deviceToken) };\n }\n\n // queryAlarms reads the authoritative rows. Tenant-wide is one query; a scoped widget\n // runs one query per originator device (the alarms query filters a single originator)\n // and merges \u2014 deduped by token, newest first, capped to pageSize; total is the sum of\n // per-originator match counts.\n private async queryAlarms(\n sub: AlarmSubscription,\n tokens: string[],\n tenantWide: boolean,\n ): Promise<AlarmSnapshot> {\n const base = {\n pageNumber: 1, // the alarms query paginates 1-based\n pageSize: sub.pageSize,\n state: sub.state ?? null,\n severity: sub.severity ?? null,\n acknowledged: sub.acknowledged ?? null,\n } satisfies Partial<AlarmSearchCriteriaInput>;\n\n if (tenantWide) {\n const data = await gql(DEVICE_AREA, ALARMS_QUERY, {\n criteria: { ...base, originatorType: null, originator: null },\n });\n return { alarms: data.alarms.results, total: data.alarms.pagination.totalRecords };\n }\n\n const pages = await Promise.all(\n tokens.map((token) =>\n gql(DEVICE_AREA, ALARMS_QUERY, {\n criteria: { ...base, originatorType: 'device', originator: token },\n }),\n ),\n );\n const byToken = new Map<string, AlarmRow>();\n let total = 0;\n for (const page of pages) {\n total += page.alarms.pagination.totalRecords;\n for (const row of page.alarms.results) byToken.set(row.token, row);\n }\n const alarms = [...byToken.values()]\n .sort((a, b) => (b.raisedTime ?? '').localeCompare(a.raisedTime ?? ''))\n .slice(0, sub.pageSize);\n return { alarms, total };\n }\n\n // \u2500\u2500 Control channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // subscribeCommands binds a command widget's scope to a sink and returns a disposer.\n // Poll-only (command-delivery has no subscription): resolve the target device once,\n // then re-read its recent commands on an interval (and immediately after an issue via\n // the reconciler). Scope resolution is async; the disposer is returned synchronously\n // and cancels a still-pending resolution.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let reconciler: (() => void) | undefined;\n let deviceToken: string | null = null;\n // Monotonic generation: a slow poll that resolves after a newer one can't overwrite\n // fresher rows.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n if (reconciler) this.commandReconcilers.delete(reconciler);\n this.commandDisposers.delete(dispose);\n };\n this.commandDisposers.add(dispose);\n\n const reconcile = (): void => {\n const gen = ++generation;\n this.queryCommands(subscription, deviceToken)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveCommandScope(subscription.datasource)\n .then((token) => {\n if (disposed) return;\n deviceToken = token;\n // A command button needs a single target device. Unscoped/unresolved (an unbound\n // slot, no device) \u2192 an empty state, NOT tenant-wide: a command can't be issued\n // to \"all devices\". Nothing to poll.\n if (!deviceToken) {\n sink.next({ deviceToken: null, commands: [], total: 0 });\n return;\n }\n poll = setInterval(reconcile, COMMAND_POLL_MS);\n reconciler = reconcile;\n this.commandReconcilers.add(reconciler);\n reconcile(); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveCommandScope turns a command widget's scope selector into its single target\n // device token (a command targets one device), or null when it carries no datasource\n // or resolves to no device. When a selector expands to several devices (an anchor), the\n // first is the target \u2014 the console restricts command widgets to a device scope, so\n // this is a defensive fallback, not the authoring path.\n private async resolveCommandScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<string | null> {\n if (!datasource) return null;\n const groups = await this.resolveDevices(datasource);\n return groups[0]?.deviceToken ?? null;\n }\n\n // queryCommands reads the recent commands for the target device (newest first, capped\n // to pageSize) with their live delivery status.\n private async queryCommands(\n sub: CommandSubscription,\n deviceToken: string | null,\n ): Promise<CommandSnapshot> {\n if (!deviceToken) return { deviceToken: null, commands: [], total: 0 };\n const data = await gql(COMMAND_AREA, COMMANDS_QUERY, {\n criteria: { pageNumber: 1, pageSize: sub.pageSize, deviceToken, status: null },\n });\n return {\n deviceToken,\n commands: data.commands.results,\n total: data.commands.pagination.totalRecords,\n };\n }\n\n // \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // subscribeLocations binds a map widget's selector to a sink and returns a disposer.\n // Poll-only (device-state has no location subscription): resolve the bound devices\n // once, then re-read their last-known positions on an interval. Scope resolution is\n // async; the disposer is returned synchronously and cancels a still-pending\n // resolution, matching every other channel.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let deviceTokens: string[] = [];\n // Monotonic generation: a slow poll that resolves after a newer one can't overwrite\n // fresher positions.\n let generation = 0;\n\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n this.locationDisposers.delete(dispose);\n };\n this.locationDisposers.add(dispose);\n\n const reconcile = (): void => {\n const gen = ++generation;\n this.queryLocations(deviceTokens)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveLocationScope(subscription.datasource)\n .then((tokens) => {\n if (disposed) return;\n deviceTokens = tokens;\n // A widget that names no location series, carries no datasource, or resolves to\n // no device has nothing to place on a map. Emit the empty POSITIONS snapshot \u2014\n // never `forbidden`, which would claim a permission problem that does not\n // exist \u2014 and open no poll.\n if (deviceTokens.length === 0) {\n sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return;\n }\n poll = setInterval(reconcile, LOCATION_POLL_MS);\n reconcile(); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveLocationScope turns a map widget's selector into the device tokens whose\n // positions to read.\n //\n // \uD83D\uDD34 It resolves NOTHING unless the selector NAMES A LOCATION SERIES. That is the\n // point of the separate field: a device selector carrying only `measurements` is a\n // telemetry binding, and quietly reading its device's position because a map widget\n // happens to hold it would make the location field decorative \u2014 authored or not, the\n // behaviour would be identical, so nothing would ever hold it. A map bound to a\n // measurement-only selector shows its empty state, which is the honest answer.\n private async resolveLocationScope(\n datasource: DatasourceSelector | undefined,\n ): Promise<string[]> {\n if (!datasource?.location) return [];\n const groups = await this.resolveDevices(datasource);\n return groups.map((g) => g.deviceToken);\n }\n\n // queryLocations reads the last-known position of each bound device in ONE batch\n // round trip. A device that has never been located is absent from the result (the\n // service's contract), so the caller reads \"how many are located\" from the returned\n // rows and \"how many are bound\" from the tokens.\n private async queryLocations(deviceTokens: string[]): Promise<LocationSnapshot> {\n if (deviceTokens.length === 0) return { kind: 'positions', deviceTokens, locations: [] };\n try {\n const data = await gql(STATE_AREA, LATEST_LOCATIONS_QUERY, { deviceTokens });\n return { kind: 'positions', deviceTokens, locations: data.latestLocations };\n } catch (err) {\n // Only a REFUSAL becomes a value; every other failure stays a failure, so a broken\n // device-state is never dressed up as a permission boundary (and vice versa \u2014 a\n // permission boundary is never dressed up as an outage the operator should page\n // someone about).\n if (isForbiddenError(err)) return { kind: 'forbidden' };\n throw err;\n }\n }\n\n // \u2500\u2500 WidgetActions (the write seam) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // can reports whether the viewer holds an authority ('*' grants all). Drives whether\n // a widget renders an action control; the server enforces authority regardless.\n can(authority: string): boolean {\n return this.authorities.has('*') || this.authorities.has(authority);\n }\n\n // acknowledgeAlarm / clearAlarm mutate the alarm by token, then nudge every open alarm\n // widget to reconcile so the change shows at once. The mutation reaches device-management\n // (the acknowledging identity is taken server-side from the token).\n async acknowledgeAlarm(alarmToken: string): Promise<void> {\n await gql(DEVICE_AREA, ACKNOWLEDGE_ALARM, { token: alarmToken });\n this.reconcileAlarms();\n }\n\n async clearAlarm(alarmToken: string): Promise<void> {\n await gql(DEVICE_AREA, CLEAR_ALARM, { token: alarmToken });\n this.reconcileAlarms();\n }\n\n // sendCommand issues a command to a device, minting the dispatch token here (the\n // idempotency key + cancel handle), then nudges every open command widget to reconcile\n // so the new command shows at once. The mutation reaches command-delivery (requires\n // command:write, enforced server-side regardless of can()).\n //\n // A REFUSAL COMES BACK AS A VALUE, not an exception (see CommandDispatch): the server\n // decided the request and named the reason, so the widget can show it. Nothing was\n // created in that case, so the open command widgets are NOT reconciled \u2014 a re-poll\n // would only re-render the same history and make a refused send look like it did\n // something.\n async sendCommand(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch> {\n const token = randomToken();\n const result = await gql(COMMAND_AREA, CREATE_COMMAND, {\n request: { token, deviceToken, name, payload: payload ?? null },\n });\n const rejection = result.createCommand?.rejection;\n if (rejection) {\n return { status: 'rejected', code: rejection.code, reason: rejection.reason };\n }\n // \uD83D\uDD34 A response carrying NEITHER arm is a contract violation, and it must not read\n // as success. Exactly one of command/rejection is populated; neither means the\n // answer never arrived intact \u2014 a resolver returning nothing, a host GraphQL layer\n // dropping a field, a mangled response.\n //\n // The trap is that the dispatch token is minted HERE, so nothing forces this code\n // to look at the server's answer at all: without this guard the fall-through\n // returns { status: 'sent' } with a token that names no command, and reconciles\n // every open command widget as though something had been created. The operator is\n // told their command went out and has a handle that cancels nothing.\n //\n // The two siblings written alongside this both guard it \u2014 the REACT sink returns an\n // explicit error, the console throws \u2014 and this is the surface most likely to be\n // embedded outside the console, so it is the last one that should be lenient.\n if (!result.createCommand?.command) {\n throw new Error('The command could not be issued: the platform returned no answer for it.');\n }\n this.reconcileCommands();\n return { status: 'sent', token };\n }\n\n // reconcileAlarms re-queries every open alarm subscription hub-wide (after a mutation).\n // Hub-wide is deliberate: one alarm can appear in several widgets (different scopes),\n // and the acked/cleared row must refresh in all of them; scoping the nudge would need\n // per-reconciler token knowledge for no real saving. Iterate a copy for safety.\n private reconcileAlarms(): void {\n for (const reconcile of [...this.alarmReconcilers]) reconcile();\n }\n\n // reconcileCommands re-polls every open command subscription (after an issue). Iterate\n // a copy for safety.\n private reconcileCommands(): void {\n for (const reconcile of [...this.commandReconcilers]) reconcile();\n }\n\n // disposeAll tears down every upstream stream (e.g. on dashboard close): the\n // ref-counted measurement device streams AND every alarm/command/location\n // subscription's poll + trigger. Iterate a copy of the disposer sets since each\n // removes itself as it runs.\n disposeAll(): void {\n for (const stream of this.streams.values()) stream.unsubscribe();\n this.streams.clear();\n for (const dispose of [...this.alarmDisposers]) dispose();\n this.alarmDisposers.clear();\n for (const dispose of [...this.commandDisposers]) dispose();\n this.commandDisposers.clear();\n for (const dispose of [...this.locationDisposers]) dispose();\n this.locationDisposers.clear();\n }\n\n // The number of distinct upstream device streams currently open (observability\n // + test hook \u2014 proves multiplexing collapses shared devices to one stream).\n get openStreamCount(): number {\n return this.streams.size;\n }\n\n // isDatasourceAvailable reports whether a widget's bound device still exists. Only a\n // device selector is validated (a slot bound to a device arrives as one) \u2014 an anchor, an\n // unbound slot, or no datasource has a legitimate empty state and is always \"available\".\n // Fails open: an existence-check outage returns true (never falsely mark a live device\n // gone).\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n const deviceToken = this.availabilityToken(datasource);\n if (deviceToken === undefined) return true;\n try {\n return await this.resolver.deviceExists(deviceToken);\n } catch {\n return true;\n }\n }\n\n // availabilityToken returns the single device token whose existence gates a widget's\n // availability, or undefined when there is nothing device-specific to validate (an\n // anchor's membership is self-validating; an unbound slot is a placeholder; a reserved\n // kind isn't resolved yet; an unresolved slot fails on its data channel instead).\n private availabilityToken(datasource: DatasourceSelector | undefined): string | undefined {\n if (!datasource) return undefined;\n // An empty token (a half-authored or hand-edited definition) has nothing to\n // validate \u2014 treat it like an unbound slot (available/empty), not a device that\n // \"no longer exists\", and skip the guaranteed-empty query.\n if (datasource.kind === 'device') return datasource.deviceToken || undefined;\n return undefined;\n }\n\n // resolveDevices turns a selector into the devices to stream, each with the\n // measurement names the widget wants (empty = all). Reserved selector kinds are\n // rejected here, mirroring the backend (Phase 1 ships device + anchor).\n private async resolveDevices(\n datasource: DatasourceSelector,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n switch (datasource.kind) {\n case 'device':\n return this.resolveBinding(\n { kind: 'device', deviceToken: datasource.deviceToken },\n new Set(datasource.measurements),\n );\n case 'anchor':\n return this.resolveBinding(\n { kind: 'anchor', anchor: datasource.anchor },\n new Set(datasource.measurements),\n );\n case 'unbound':\n // A slot with no binding, as the renderer resolved it: a valid placeholder (a\n // template the host hasn't bound, a selection not yet made), not an error.\n // Zero devices \u2014 a silent empty state, like an anchor with no members.\n return [];\n case 'slot':\n // \uD83D\uDD34 FAIL LOUDLY. The hub keeps no bindings, so a slot here was never resolved:\n // the host handed a raw definition widget to the data source instead of going\n // through the renderer (or resolveWidgetDatasource). Answering \"zero devices\"\n // would make that bug look exactly like a slot that is legitimately unbound.\n throw new Error(\n `dashboard slot '${datasource.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(\n `dashboard selector kind '${datasource.kind}' is not supported yet`,\n );\n }\n }\n\n // resolveBinding turns a concrete entity binding (device or anchor) into the\n // device streams to open, each carrying the given measurement names. Shared by the\n // device/anchor selectors and by slot resolution (whose binding is either kind).\n // A device binding streams its token directly (measurementStream is keyed by token,\n // per ADR-044); an anchor expands to its member device tokens.\n private async resolveBinding(\n binding: SlotBinding,\n names: Set<string>,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n if (binding.kind === 'device') {\n return [{ deviceToken: binding.deviceToken, names }];\n }\n const tokens = await this.resolver.devicesForAnchor(binding.anchor);\n return tokens.map((deviceToken) => ({ deviceToken, names }));\n }\n\n // attach registers a subscriber on a device's stream (opening the upstream on\n // the first subscriber) and returns a detacher that drops it and closes the\n // upstream once the last subscriber leaves.\n private attach(deviceToken: string, names: Set<string>, sink: WidgetStreamSink): () => void {\n const stream = this.ensureStream(deviceToken);\n const subscriber: Subscriber = { names, sink };\n stream.subscribers.add(subscriber);\n\n return () => {\n if (!stream.subscribers.delete(subscriber)) return;\n // Only tear down and forget the stream if it is STILL the registered stream\n // for this device \u2014 an upstream error may have evicted and replaced it, and a\n // lingering old subscriber's detach must not delete the replacement.\n if (stream.subscribers.size === 0 && this.streams.get(deviceToken) === stream) {\n stream.unsubscribe();\n this.streams.delete(deviceToken);\n }\n };\n }\n\n private ensureStream(deviceToken: string): DeviceStream {\n const existing = this.streams.get(deviceToken);\n if (existing) return existing;\n\n const stream: DeviceStream = { subscribers: new Set(), unsubscribe: () => {} };\n // Register before subscribing so that even a synchronously-delivered first\n // sample resolves through fanout (unsubscribe stays the no-op placeholder only\n // for the brief window until subscribe() returns the real disposer).\n this.streams.set(deviceToken, stream);\n\n // Subscribe unfiltered by name (name: null) so a device's every reading rides\n // ONE upstream and each subscriber filters to the names it wants \u2014 a chart and\n // a card on the same device share the stream instead of opening two.\n const adapter: SubscriptionSink<MeasurementStreamResult> = {\n next: (data) => this.fanout(deviceToken, data.measurementStream),\n error: (err) => {\n // The upstream is dead. Evict it (and drop the socket-level subscription)\n // so the NEXT subscriber for this device opens a fresh stream instead of\n // attaching to this corpse and freezing silently \u2014 the reconnect path.\n // Guard the delete so a stream that has already been replaced is left be.\n if (this.streams.get(deviceToken) === stream) this.streams.delete(deviceToken);\n stream.unsubscribe();\n for (const subscriber of stream.subscribers) subscriber.sink.error?.(err);\n },\n };\n const variables: MeasurementStreamVariables = { deviceToken, name: null };\n stream.unsubscribe = subscribe(EVENT_AREA, MEASUREMENT_STREAM, variables, adapter);\n\n return stream;\n }\n\n private fanout(deviceToken: string, sample: MeasurementSample): void {\n const stream = this.streams.get(deviceToken);\n if (!stream) return;\n for (const subscriber of stream.subscribers) {\n if (subscriber.names.size === 0 || subscriber.names.has(sample.name)) {\n subscriber.sink.next(sample);\n }\n }\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed alarm documents the hub's alarm channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc \u2014 these\n// are written by hand and cast to TypedDocument. The alarm channel is query-then-\n// reconcile (ADR-041): ALARMS_QUERY reads the authoritative raised-alarm rows\n// (device-management, requires device:read), and ALARM_STREAM is a best-effort\n// (at-most-once) trigger the hub debounces into a re-query \u2014 never the row source of\n// truth. The trigger selects only what proves an event arrived; the rows come from the\n// query.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { AlarmRow } from '../types';\n\n// \u2500\u2500 Query (source of truth) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface AlarmSearchCriteriaInput {\n pageNumber: number;\n pageSize: number;\n originatorType?: string | null;\n originator?: string | null;\n state?: string | null;\n severity?: string | null;\n acknowledged?: boolean | null;\n alarmKey?: string | null;\n}\n\nexport interface AlarmsQueryResult {\n alarms: {\n results: AlarmRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface AlarmsQueryVariables {\n criteria: AlarmSearchCriteriaInput;\n}\n\nexport const ALARMS_QUERY = `\n query DashboardAlarms($criteria: AlarmSearchCriteria!) {\n alarms(criteria: $criteria) {\n results {\n token\n originatorType\n originatorToken\n alarmKey\n metricKey\n state\n acknowledged\n severity\n raisedTime\n clearedTime\n acknowledgedTime\n acknowledgedBy\n lastValue\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<AlarmsQueryResult, AlarmsQueryVariables>;\n\n// \u2500\u2500 Live trigger \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface AlarmStreamResult {\n alarmStream: { alarmToken: string; eventType: string };\n}\n\nexport interface AlarmStreamVariables {\n originatorType?: string | null;\n originator?: string | null;\n state?: string | null;\n severity?: string | null;\n alarmKey?: string | null;\n}\n\nexport const ALARM_STREAM = `\n subscription DashboardAlarmStream(\n $originatorType: String\n $originator: String\n $state: String\n $severity: String\n $alarmKey: String\n ) {\n alarmStream(\n originatorType: $originatorType\n originator: $originator\n state: $state\n severity: $severity\n alarmKey: $alarmKey\n ) {\n alarmToken\n eventType\n }\n }\n` as unknown as TypedDocument<AlarmStreamResult, AlarmStreamVariables>;\n\n// \u2500\u2500 Operator actions (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// acknowledge/clear an alarm by token (device-management, require alarm:write). The\n// acknowledging identity is taken server-side from the authenticated subject; the\n// caller supplies only the token. Only the token is selected back \u2014 the hub reconciles\n// the rows via the query afterward.\n\nexport interface AlarmMutationVariables {\n token: string;\n}\n\nexport interface AlarmMutationResult {\n [key: string]: { token: string };\n}\n\nexport const ACKNOWLEDGE_ALARM = `\n mutation DashboardAcknowledgeAlarm($token: String!) {\n acknowledgeAlarm(token: $token) {\n token\n }\n }\n` as unknown as TypedDocument<AlarmMutationResult, AlarmMutationVariables>;\n\nexport const CLEAR_ALARM = `\n mutation DashboardClearAlarm($token: String!) {\n clearAlarm(token: $token) {\n token\n }\n }\n` as unknown as TypedDocument<AlarmMutationResult, AlarmMutationVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed command documents the hub's control channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc and\n// alarm-doc \u2014 these are written by hand and cast to TypedDocument. The control channel\n// is poll-then-emit: command-delivery exposes NO subscription, so COMMANDS_QUERY is\n// polled (device-scoped) for the live delivery lifecycle, and CREATE_COMMAND issues a\n// new command (requires command:write). The command-button widget bakes its parameter\n// schema from the console at author time; there is no definition query here.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { CommandRow } from '../types';\n\n// \u2500\u2500 Query (lifecycle poll) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CommandSearchCriteriaInput {\n pageNumber: number;\n pageSize: number;\n deviceToken?: string | null;\n // Match a single lifecycle state exactly.\n status?: string | null;\n // Match ANY of several lifecycle states. ANDed with `status` when both are given; an\n // empty list is ignored rather than matching nothing. The states a caller cares about\n // are usually a set \u2014 \"still outstanding\" is QUEUED \u222A HELD \u222A SENT \u222A PARKED, not one\n // value, and \"still cancellable\" is a different set again (SENT is outstanding but\n // cannot be called back).\n statuses?: string[] | null;\n}\n\nexport interface CommandsQueryResult {\n commands: {\n results: CommandRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface CommandsQueryVariables {\n criteria: CommandSearchCriteriaInput;\n}\n\nexport const COMMANDS_QUERY = `\n query DashboardCommands($criteria: CommandSearchCriteria!) {\n commands(criteria: $criteria) {\n results {\n token\n name\n status\n payload\n responsePayload\n error\n queuedTime\n sentTime\n respondedTime\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<CommandsQueryResult, CommandsQueryVariables>;\n\n// \u2500\u2500 Mutation (issue a command) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// createCommand persists a new command to a device (command-delivery, requires\n// command:write). The caller mints a fresh unique `token` per dispatch (the\n// idempotency key + cancel handle); `payload` is the request body the device receives\n// verbatim (the widget serializes its typed form to JSON). Only the token + status are\n// selected back \u2014 the hub re-reads the lifecycle via the poll query afterward.\n//\n// \uD83D\uDD34 THE RESULT HAS TWO ARMS, and they mean opposite things. `command` is the command\n// that was created; `rejection` is a decided REFUSAL of the request (unknown device,\n// command not in the device's published vocabulary, payload violating its parameter\n// schema, malformed JSON/timestamp, the tenant's held-command ceiling full), carrying a\n// stable `code` to branch on and a client-safe `reason` for a person. Exactly one is\n// non-null. A GraphQL error instead of either means the enqueue could not be DECIDED \u2014\n// an availability failure that says nothing about the command \u2014 which is why a\n// rejection is a value here rather than something thrown.\n//\n// The code set is OPEN (the enqueue gate that owns the vocabulary relays its own codes\n// through), so an unrecognized code is still a rejection, never a success.\n\nexport interface CreateCommandRequestInput {\n token: string;\n deviceToken: string;\n name: string;\n payload?: string | null;\n}\n\nexport interface CreateCommandResult {\n createCommand: {\n command: { token: string; status: string } | null;\n rejection: { code: string; reason: string } | null;\n };\n}\n\nexport interface CreateCommandVariables {\n request: CreateCommandRequestInput;\n}\n\nexport const CREATE_COMMAND = `\n mutation DashboardCreateCommand($request: CommandCreateRequest!) {\n createCommand(request: $request) {\n command {\n token\n status\n }\n rejection {\n code\n reason\n }\n }\n }\n` as unknown as TypedDocument<CreateCommandResult, CreateCommandVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed location document the hub's location channel drives, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so \u2014 like measurement-doc,\n// alarm-doc and command-doc \u2014 this is written by hand and cast to TypedDocument.\n//\n// The channel is poll-only: device-state exposes NO location subscription, so this is\n// the whole wire. It reads the BATCH form (`latestLocations`, plural), which the\n// service documents as the fleet-map query: one round trip for a whole board's worth\n// of devices instead of one per marker, which is what keeps a 200-device map's poll\n// the same cost as a 2-device map's.\n//\n// \uD83D\uDD34 A device that has never been located is ABSENT from the result rather than\n// present with null coordinates. That is the service's contract, and the channel\n// preserves it: the snapshot reports the devices the selector resolved to alongside\n// the positions, so a widget can tell \"no devices bound\" from \"devices bound, none\n// has ever reported a position\" \u2014 which are different things to tell an operator.\n//\n// Requires location:read, which is deliberately NOT in the read-only viewer baseline,\n// so a refusal here is an ordinary state (see the hub's forbidden handling) rather\n// than a fault.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { LocationSample } from '../types';\n\nexport interface LatestLocationsResult {\n latestLocations: LocationSample[];\n}\n\nexport interface LatestLocationsVariables {\n deviceTokens: string[];\n}\n\nexport const LATEST_LOCATIONS_QUERY = `\n query DashboardLatestLocations($deviceTokens: [String!]!) {\n latestLocations(deviceTokens: $deviceTokens) {\n id\n deviceToken\n latitude\n longitude\n elevation\n accuracy\n speed\n heading\n occurredTime\n }\n }\n` as unknown as TypedDocument<LatestLocationsResult, LatestLocationsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The typed measurementStream subscription document, hand-authored.\n//\n// Packages carry no graphql-codegen (only apps do), so unlike the console this\n// document is written by hand. The SDK uses documentMode:'string' \u2014 it only ever\n// calls document.toString() and sends the text over graphql-ws \u2014 so a raw\n// GraphQL string carrying phantom result/variable types IS exactly what a\n// generated TypedDocumentString would be at runtime, minus the class wrapper.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { MeasurementSample } from '../types';\n\nexport interface MeasurementStreamResult {\n measurementStream: MeasurementSample;\n}\n\nexport interface MeasurementStreamVariables {\n deviceToken?: string | null;\n name?: string | null;\n}\n\nexport const MEASUREMENT_STREAM = `\n subscription MeasurementStream($deviceToken: String, $name: String) {\n measurementStream(deviceToken: $deviceToken, name: $name) {\n id\n deviceToken\n eventType\n occurredTime\n name\n value\n classifier\n }\n }\n` as unknown as TypedDocument<MeasurementStreamResult, MeasurementStreamVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// SyntheticDataSource \u2014 an offline, client-side data source for the dashboard\n// PREVIEW mode (ADR-039). It implements the same WidgetDataSource contract the live\n// DashboardHub does, so the renderer and widgets consume it unchanged \u2014 but instead\n// of subscribing to backend telemetry it generates values from a chosen waveform.\n// This lets an author validate layout, scales, and thresholds before any device has\n// reported (and it works for ANY selector, including a slot with no binding \u2014 which\n// the live hub renders empty \u2014 because it only reads the datasource's measurement\n// names, never resolves\n// a device).\n\nimport type {\n AlarmStreamSink,\n AlarmSubscription,\n CommandDispatch,\n CommandStreamSink,\n CommandSubscription,\n LocationStreamSink,\n LocationSubscription,\n WidgetActions,\n WidgetDataSource,\n WidgetStreamSink,\n} from './hub';\nimport type {\n AlarmRow,\n CommandRow,\n DatasourceSelector,\n LocationSample,\n MeasurementSample,\n} from './types';\n\n// The waveforms an author can preview with. Sine is the default (smooth, obviously\n// synthetic); ramp is a sawtooth; random-walk drifts within range.\nexport type SyntheticGenerator = 'sine' | 'ramp' | 'random-walk';\n\n// Presentation list for a generator picker (value + human label).\nexport const SYNTHETIC_GENERATORS: ReadonlyArray<{ value: SyntheticGenerator; label: string }> = [\n { value: 'sine', label: 'Sine wave' },\n { value: 'ramp', label: 'Ramp' },\n { value: 'random-walk', label: 'Random walk' },\n];\n\nexport interface SyntheticDataSourceConfig {\n generator?: SyntheticGenerator;\n // Emit cadence in ms (also the backfill spacing). Default 1s.\n intervalMs?: number;\n // How many past points to backfill on subscribe so a chart shows a full waveform\n // immediately instead of drawing in one tick at a time. Default 60.\n backfill?: number;\n // The value range the waveforms span. Default 0..100.\n min?: number;\n // Default 100.\n max?: number;\n // Period of one sine/ramp cycle in ms. Default 60s.\n periodMs?: number;\n}\n\n// A measurement name for a selector that lists none (empty = \"all\" on the live hub);\n// gives cards/gauges/charts something to render in preview.\nconst DEFAULT_NAME = 'value';\n\n// A canonical spread of synthetic alarms (one per severity, mixed state/ack) so an\n// author previewing an alarm table/count sees populated, representative rows before any\n// real alarm has raised. The filter on the subscription is applied so the preview\n// reflects the widget's configured scope-of-interest.\nconst SYNTHETIC_ALARMS: ReadonlyArray<\n Pick<\n AlarmRow,\n 'severity' | 'state' | 'acknowledged' | 'alarmKey' | 'metricKey' | 'lastValue' | 'originatorToken'\n >\n> = [\n { severity: 'CRITICAL', state: 'ACTIVE', acknowledged: false, alarmKey: 'over-temperature', metricKey: 'temperature', lastValue: 87.4, originatorToken: 'thermostat-01' },\n { severity: 'MAJOR', state: 'ACTIVE', acknowledged: true, alarmKey: 'low-battery', metricKey: 'battery', lastValue: 12, originatorToken: 'sensor-14' },\n { severity: 'MINOR', state: 'ACTIVE', acknowledged: false, alarmKey: 'humidity-high', metricKey: 'humidity', lastValue: 78, originatorToken: 'sensor-03' },\n { severity: 'WARNING', state: 'CLEARED', acknowledged: true, alarmKey: 'signal-weak', metricKey: 'rssi', lastValue: -89, originatorToken: 'gateway-02' },\n { severity: 'INDETERMINATE', state: 'ACTIVE', acknowledged: false, alarmKey: 'self-test', metricKey: 'status', lastValue: null, originatorToken: 'device-99' },\n];\n\n// A canonical spread of synthetic commands (one per lifecycle stage) so an author\n// previewing a command-button sees a populated, representative history \u2014 an in-flight\n// command, one withheld for an absent device, one parked after finding nobody home, a\n// completed one, a failure, a cancellation \u2014 before any real command has been issued.\n//\n// `dispatched` says whether this command was ever actually put on the wire, which is\n// what decides sentTime below. It is a per-fixture fact, not something derivable from\n// the status: a CANCELLED command may have been called off while still held (never\n// sent) or after dispatch, and the preview picks the former.\nconst SYNTHETIC_COMMANDS: ReadonlyArray<\n Pick<CommandRow, 'name' | 'status' | 'payload' | 'responsePayload' | 'error'> & { dispatched: boolean }\n> = [\n { name: 'reboot', status: 'SENT', payload: '{\"delaySeconds\":5}', responsePayload: null, error: null, dispatched: true },\n // Early in the list on purpose: an author can cap the widget's row count, and a\n // withheld command is the state a preview most needs to show.\n { name: 'self-test', status: 'HELD', payload: null, responsePayload: null, error: null, dispatched: false },\n // The other way a command ends up waiting on an absent device: this one was published\n // and found nobody there, so it is parked until the device wakes. Included next to HELD\n // because the two look alike in a list and an author laying out a command-button needs\n // to see that they are distinguishable. `dispatched` is false, matching the service: it\n // CLEARS sent_time when it parks a command, because a dispatch that reached nobody sent\n // nothing. That is also why a parked command is still cancellable \u2014 nothing has taken\n // delivery of it.\n { name: 'sync-clock', status: 'PARKED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'set-interval', status: 'SUCCESSFUL', payload: '{\"seconds\":30}', responsePayload: '{\"ok\":true}', error: null, dispatched: true },\n { name: 'calibrate', status: 'QUEUED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'firmware-update', status: 'FAILED', payload: '{\"version\":\"2.1.0\"}', responsePayload: null, error: 'device offline', dispatched: true },\n { name: 'open-valve', status: 'CANCELLED', payload: '{\"percent\":100}', responsePayload: null, error: null, dispatched: false },\n];\n\n// The device a synthetic command-button reports as its target, so the Send control\n// renders (a real widget needs a bound device to issue against).\nconst SYNTHETIC_COMMAND_DEVICE = 'synthetic-device';\n\n// A canonical spread of synthetic positions so an author previewing a map sees markers\n// laid out the way a real fleet would be \u2014 several devices a short distance apart, at a\n// zoom a map actually renders at, rather than one dot in the middle.\n//\n// \uD83D\uDD34 THE OPTIONALS ARE DELIBERATELY MIXED, INCLUDING NULLS AND A GENUINE ZERO. Preview\n// is where an author decides what a marker looks like, so it must show the real range:\n// `sp-loader-03` reports no heading and no speed at all (a receiver that does not supply\n// them), while `sp-dozer-01` reports speed 0 \u2014 parked, which is a reading, not an\n// absence. A preview that filled every optional in would teach an author that a marker\n// always has a heading, and the first real fleet would prove otherwise.\nconst SYNTHETIC_LOCATIONS: ReadonlyArray<\n Pick<LocationSample, 'deviceToken' | 'latitude' | 'longitude' | 'elevation' | 'accuracy' | 'speed' | 'heading'>\n> = [\n { deviceToken: 'sp-dozer-01', latitude: 33.749, longitude: -84.388, elevation: 320.5, accuracy: 4.2, speed: 0, heading: 271.5 },\n { deviceToken: 'sp-excavator-02', latitude: 33.7512, longitude: -84.3858, elevation: 318.1, accuracy: 3.1, speed: 1.4, heading: 88 },\n { deviceToken: 'sp-loader-03', latitude: 33.7468, longitude: -84.3903, elevation: null, accuracy: 9.8, speed: null, heading: null },\n { deviceToken: 'sp-truck-04', latitude: 33.7481, longitude: -84.3841, elevation: 315.9, accuracy: 2.5, speed: 8.3, heading: 12.25 },\n];\n\n// Deterministic small hash of a name \u2192 a stable phase offset, so multiple series on\n// one dashboard are visibly out of phase rather than overlapping identically.\nfunction hashName(name: string): number {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (Math.imul(h, 31) + name.charCodeAt(i)) | 0;\n return h >>> 0;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nexport class SyntheticDataSource implements WidgetDataSource, WidgetActions {\n private readonly generator: SyntheticGenerator;\n private readonly intervalMs: number;\n private readonly backfill: number;\n private readonly min: number;\n private readonly max: number;\n private readonly periodMs: number;\n // Live timers, tracked so disposeAll() can stop every widget's stream at once.\n private readonly timers = new Set<ReturnType<typeof setInterval>>();\n\n constructor(config: SyntheticDataSourceConfig = {}) {\n this.generator = config.generator ?? 'sine';\n // Guard the divisors/counts so a misconfigured host can't produce NaN values\n // (periodMs:0 \u2192 tMs/0) or a zero-delay flood: intervalMs/periodMs floor at 1ms,\n // backfill can't go negative.\n this.intervalMs = Math.max(1, config.intervalMs ?? 1000);\n this.backfill = Math.max(0, config.backfill ?? 60);\n this.min = config.min ?? 0;\n this.max = config.max ?? 100;\n this.periodMs = Math.max(1, config.periodMs ?? 60_000);\n }\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n // Per-name random-walk state, private to this subscription so two widgets don't\n // share (and corrupt) each other's walk.\n const walk = new Map<string, number>();\n let seq = 0;\n\n const emit = (name: string, tMs: number): void => {\n const value = this.valueFor(name, tMs, walk);\n const s: MeasurementSample = {\n id: `syn-${seq++}`,\n deviceToken: 'synthetic',\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value,\n classifier: null,\n };\n sink.next(s);\n };\n\n // Backfill oldest \u2192 newest so the widget window is chronological (and the\n // random-walk advances forward through the backfilled points).\n const now = Date.now();\n for (let i = this.backfill - 1; i >= 0; i--) {\n const tMs = now - i * this.intervalMs;\n for (const name of names) emit(name, tMs);\n }\n\n const timer = setInterval(() => {\n const tMs = Date.now();\n for (const name of names) emit(name, tMs);\n }, this.intervalMs);\n this.timers.add(timer);\n\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeAlarms emits a synthetic alarm snapshot for preview. The canonical set is\n // filtered by the subscription (state/severity/ack) so the preview reflects what the\n // widget is configured to show; scope (datasource) is ignored \u2014 preview never resolves\n // a device. Re-emits on the same cadence with advancing raised times so the table\n // looks live. Returns whole snapshots, matching the live hub's contract.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n const matches = SYNTHETIC_ALARMS.filter(\n (a) =>\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.severity === subscription.severity) &&\n (subscription.acknowledged == null || a.acknowledged === subscription.acknowledged),\n );\n\n const emit = (): void => {\n const now = Date.now();\n const rows: AlarmRow[] = matches.map((a, i) => {\n const raised = new Date(now - i * 45_000).toISOString();\n return {\n token: `syn-alarm-${i}`,\n originatorType: 'device',\n alarmKey: a.alarmKey,\n metricKey: a.metricKey,\n state: a.state,\n acknowledged: a.acknowledged,\n severity: a.severity,\n originatorToken: a.originatorToken,\n lastValue: a.lastValue,\n raisedTime: raised,\n clearedTime: a.state === 'CLEARED' ? raised : null,\n acknowledgedTime: a.acknowledged ? raised : null,\n acknowledgedBy: a.acknowledged ? 'preview@devicechain' : null,\n };\n });\n sink.next({ alarms: rows.slice(0, subscription.pageSize), total: rows.length });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeCommands emits a synthetic command history for preview so a command-button\n // shows a populated, lifecycle-varied list (and a bound target device, so its Send\n // control renders). Re-emits on the same cadence with advancing queued times. Scope\n // (datasource) is ignored \u2014 preview never resolves a device. Returns whole snapshots,\n // matching the live hub's contract.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n const emit = (): void => {\n const now = Date.now();\n const commands: CommandRow[] = SYNTHETIC_COMMANDS.map((c, i) => {\n const queued = new Date(now - i * 20_000).toISOString();\n // respondedTime is NOT \"terminal\" \u2014 it is \"the DEVICE answered\", which is a\n // narrower thing. TIMEOUT, EXPIRED and CANCELLED are all terminal with nothing\n // ever coming back from the device, so stamping them with a response time would\n // teach an author's layout to expect a value the live hub never supplies.\n const answered = c.status === 'SUCCESSFUL' || c.status === 'FAILED';\n return {\n token: `syn-command-${i}`,\n name: c.name,\n status: c.status,\n payload: c.payload,\n responsePayload: c.responsePayload,\n error: c.error,\n queuedTime: queued,\n // Likewise: only a command that actually reached a device has a sentTime. QUEUED,\n // HELD and PARKED never did, and neither did the cancelled one (called off while\n // held).\n sentTime: c.dispatched ? queued : null,\n respondedTime: answered ? queued : null,\n };\n });\n sink.next({\n deviceToken: SYNTHETIC_COMMAND_DEVICE,\n commands: commands.slice(0, subscription.pageSize),\n total: commands.length,\n });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeLocations emits a synthetic position snapshot for preview so a map shows a\n // populated, representative fleet before any device has reported one. Scope\n // (datasource) is ignored \u2014 preview never resolves a device \u2014 but the LOCATION SERIES\n // is honored: a selector that names none gets the empty snapshot, exactly as the live\n // hub gives it, so an author who has not bound the map sees preview agree with\n // production rather than paper over the omission with fake markers.\n //\n // Preview NEVER reports `forbidden`: it reaches no backend, so there is no authority\n // to be refused, and inventing a permission state would show an author a wall their\n // viewers may not actually hit.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n if (!subscription.datasource?.location) {\n sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return () => {};\n }\n\n const emit = (): void => {\n const now = Date.now();\n const locations: LocationSample[] = SYNTHETIC_LOCATIONS.map((l, i) => ({\n id: `syn-location-${i}`,\n deviceToken: l.deviceToken,\n latitude: l.latitude,\n longitude: l.longitude,\n elevation: l.elevation,\n accuracy: l.accuracy,\n speed: l.speed,\n heading: l.heading,\n occurredTime: new Date(now - i * 30_000).toISOString(),\n }));\n sink.next({\n kind: 'positions',\n deviceTokens: locations.map((l) => l.deviceToken),\n locations,\n });\n };\n\n emit();\n const timer = setInterval(emit, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // isDatasourceAvailable \u2014 preview always resolves data (it generates it), so every\n // datasource is \"available\"; an author previewing a template never sees the\n // deleted-device state.\n async isDatasourceAvailable(): Promise<boolean> {\n return true;\n }\n\n // \u2500\u2500 WidgetActions (preview stubs) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Preview shows action controls (so an author sees the real layout), so can() is\n // always true; the actions themselves are no-ops \u2014 preview never mutates the backend.\n can(): boolean {\n return true;\n }\n\n async acknowledgeAlarm(): Promise<void> {\n // no-op in preview\n }\n\n async clearAlarm(): Promise<void> {\n // no-op in preview\n }\n\n async sendCommand(): Promise<CommandDispatch> {\n // no-op in preview \u2014 return a stub dispatch token so the widget's optimistic UI works.\n // Preview never refuses: there is no enqueue gate behind it to decide a rejection,\n // and inventing one would be a verdict nothing actually reached.\n return { status: 'sent', token: 'syn-dispatch' };\n }\n\n // disposeAll stops every live stream (e.g. when preview is turned off). Individual\n // widget disposers already clear their own timer; this is the belt-and-braces\n // teardown for the whole source.\n disposeAll(): void {\n for (const timer of this.timers) clearInterval(timer);\n this.timers.clear();\n }\n\n private valueFor(name: string, tMs: number, walk: Map<string, number>): number {\n const span = this.max - this.min;\n const phase = (hashName(name) % 1000) / 1000; // 0..1 of a cycle\n switch (this.generator) {\n case 'ramp': {\n // Sawtooth: fraction of the period (offset per name), rising min\u2192max.\n const frac = ((tMs / this.periodMs + phase) % 1 + 1) % 1;\n return this.min + span * frac;\n }\n case 'random-walk': {\n const prev = walk.get(name) ?? this.min + span / 2;\n const next = clamp(prev + (Math.random() - 0.5) * span * 0.1, this.min, this.max);\n walk.set(name, next);\n return next;\n }\n case 'sine':\n default: {\n const angle = 2 * Math.PI * (tMs / this.periodMs + phase);\n return this.min + span * (0.5 + 0.5 * Math.sin(angle));\n }\n }\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The board-recording format: one captured run of a board, as a plain JSON document a\n// RecordedDataSource can play back. This file owns the SHAPE and the parser; playback is in\n// recorded.ts. The document is produced by the Sitepulse recording converter\n// (demos/sitepulse-unity/tools/recording_to_board.py), whose module docstring is the\n// writer's side of this contract; the two must change together.\n//\n// \uD83D\uDD34 THE PARSER IS FAIL-CLOSED, AND THAT IS ITS WHOLE JOB. A recording is data produced by\n// another tool and published as a static file, so the reader is the last place a bad one\n// can be stopped before it is shown to a person as \"what the platform reported\". An\n// unknown key, a wrong kind or version, a ragged column, an unsorted time axis, a device or\n// metric index out of range, or a channel claim the document cannot honour is a thrown\n// RecordingFormatError naming the path, never a best-effort read. In particular an unknown\n// key is refused rather than ignored: an ignored key is a field somebody believed was being\n// honoured.\n//\n// Layout (formatVersion 1). Every time is `t`/`tMs`, in milliseconds from the START of the\n// run, as the recording viewer APPLIED the update (the first moment a viewer could have\n// seen it). Measurements are COLUMNS (one entry per row, parallel arrays): `d` indexes\n// `devices`, `n` indexes `channels.measurements`, `t` is the time, `v` the value and `s` is\n// 1 for a row the platform itself seeded at the start of the run. Alarms are a fold over\n// history: `snapshots` (the whole alarm set at a moment) plus `events` (one alarm's new\n// state), with ids re-minted as alarm-N. An excerpt carries `excerpt{fromMs,toMs}`: it\n// holds only the measurement rows of that window, but ALL alarm history up to its end.\n//\n// What a recording does NOT hold is declared, not implied: `channels` says which channels\n// were recorded at all. This version records no positions and no command data\n// (`channels.locations` and `channels.commands` are false and `locations` is empty), and a\n// player must answer those channels with \"not in this recording\" rather than with empty\n// data, which would read as a fact about the machines.\n\nexport const RECORDING_FORMAT_VERSION = 1;\nexport const RECORDING_KIND = 'sitepulse-board-recording';\n\nconst ALARM_STATES = ['ACTIVE', 'CLEARED'] as const;\nconst ALARM_SEVERITIES = ['CRITICAL', 'MAJOR', 'MINOR', 'WARNING'] as const;\n\nexport class RecordingFormatError extends Error {\n constructor(\n public readonly path: string,\n reason: string,\n ) {\n super(`invalid recording at ${path}: ${reason}`);\n this.name = 'RecordingFormatError';\n }\n}\n\n// One segment of the platform's simulation clock: from `fromMs` on, it ran in `mode` at\n// `scale` times real time (1 = real time). Disclosed by the host; playback does not use it.\nexport interface RecordedClockSegment {\n fromMs: number;\n mode: string;\n scale: number;\n}\n\nexport interface RecordedDevice {\n id: string;\n token: string;\n kind: string;\n}\n\nexport interface RecordedChannels {\n measurements: string[];\n alarms: boolean;\n // Always false in this version: the format carries no positions and no command data.\n locations: false;\n commands: false;\n}\n\nexport interface RecordedChapter {\n tMs: number;\n name: string;\n note: string;\n}\n\n// The measurement rows, as parallel columns sorted by `t`.\nexport interface RecordedMeasurementColumns {\n d: number[];\n n: number[];\n t: number[];\n v: number[];\n s: Array<0 | 1>;\n}\n\n// One alarm as the recording saw it. `occ` is the platform's own time for THIS state: when\n// the alarm was raised for an ACTIVE row, when it cleared for a CLEARED row. `ack` is\n// present only when the alarm was acknowledged. `dev` is a device token.\nexport interface RecordedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: (typeof ALARM_STATES)[number];\n sev: (typeof ALARM_SEVERITIES)[number];\n occ: string;\n ack?: true;\n}\n\n// A snapshot is the whole alarm set as the recorder's query returned it. `requestedAtMs` is\n// when that query was SENT (it answers at `tMs`); an event applied after it is newer than\n// anything the answer can say about that alarm. `total` always equals the alarms listed: a\n// truncated answer cannot be played as if it were complete.\nexport interface RecordedAlarmSnapshot {\n tMs: number;\n requestedAtMs: number;\n total: number;\n alarms: RecordedAlarm[];\n}\n\nexport interface RecordedAlarmEvent extends RecordedAlarm {\n tMs: number;\n}\n\nexport interface BoardRecording {\n kind: typeof RECORDING_KIND;\n formatVersion: typeof RECORDING_FORMAT_VERSION;\n runId: string;\n startedAtUtc: string;\n durationMs: number;\n platformVersion: string;\n instance: string;\n tenant: string;\n build: { gitSha: string; sdkCommit: string };\n clock: RecordedClockSegment[];\n devices: RecordedDevice[];\n channels: RecordedChannels;\n board: { path: string; sourceCommit: string; sha256: string };\n sourceHashes: Record<string, string>;\n converter: { version: number };\n chapters: RecordedChapter[];\n excerpt?: { fromMs: number; toMs: number };\n measurements: RecordedMeasurementColumns;\n alarms: { snapshots: RecordedAlarmSnapshot[]; events: RecordedAlarmEvent[] };\n locations: [];\n}\n\n// ---- helpers that read a parsed recording ---------------------------------\n\n// The span a player may cover: the whole run, or the excerpt's window.\nexport function recordingBounds(rec: BoardRecording): { fromMs: number; toMs: number } {\n return rec.excerpt ? { ...rec.excerpt } : { fromMs: 0, toMs: rec.durationMs };\n}\n\n// The wall-clock instant (epoch ms) a recording offset corresponds to. Offsets are\n// measured in real elapsed time from the run's start, so this is the start plus the offset;\n// an accelerated SIMULATION clock changes what the platform's own timestamps say, not how\n// long the recording took (see simulationScaleAt for disclosing that).\nexport function recordedWallTimeMs(rec: BoardRecording, tMs: number): number {\n return Date.parse(rec.startedAtUtc) + tMs;\n}\n\n// The simulation clock's scale at a recording offset, or null before the first segment\n// begins (the recorder had not yet reported its clock).\nexport function simulationScaleAt(rec: BoardRecording, tMs: number): { mode: string; scale: number } | null {\n let hit: RecordedClockSegment | null = null;\n for (const seg of rec.clock) {\n if (seg.fromMs <= tMs) hit = seg;\n }\n return hit ? { mode: hit.mode, scale: hit.scale } : null;\n}\n\n// ---- parser ---------------------------------------------------------------\n\ntype Obj = Record<string, unknown>;\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// strict returns `v` as an object after checking it carries every required key and no key\n// outside required + optional.\nfunction strict(v: unknown, path: string, required: readonly string[], optional: readonly string[] = []): Obj {\n if (!isObj(v)) throw new RecordingFormatError(path, 'expected an object');\n const allowed = new Set([...required, ...optional]);\n for (const key of Object.keys(v)) {\n if (!allowed.has(key)) throw new RecordingFormatError(`${path}.${key}`, 'unknown key');\n }\n for (const key of required) {\n if (!Object.prototype.hasOwnProperty.call(v, key)) {\n throw new RecordingFormatError(`${path}.${key}`, 'missing required key');\n }\n }\n return v;\n}\n\nfunction str(v: unknown, path: string): string {\n if (typeof v !== 'string' || v === '') throw new RecordingFormatError(path, 'expected a non-empty string');\n return v;\n}\n\nfunction bool(v: unknown, path: string): boolean {\n if (typeof v !== 'boolean') throw new RecordingFormatError(path, 'expected a boolean');\n return v;\n}\n\nfunction num(v: unknown, path: string): number {\n if (typeof v !== 'number' || !Number.isFinite(v)) throw new RecordingFormatError(path, 'expected a finite number');\n return v;\n}\n\nfunction arr(v: unknown, path: string): unknown[] {\n if (!Array.isArray(v)) throw new RecordingFormatError(path, 'expected an array');\n return v;\n}\n\nfunction oneOf<T extends string>(v: unknown, path: string, allowed: readonly T[]): T {\n if (typeof v !== 'string' || !(allowed as readonly string[]).includes(v)) {\n throw new RecordingFormatError(path, `expected one of ${allowed.join(', ')}`);\n }\n return v as T;\n}\n\nfunction matching(v: unknown, path: string, re: RegExp, what: string): string {\n if (typeof v !== 'string' || !re.test(v)) throw new RecordingFormatError(path, `expected ${what}`);\n return v;\n}\n\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst HEX40 = /^[0-9a-f]{40}$/;\nconst HEX_SHORT = /^[0-9a-f]{7,40}$/;\n\nconst INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?Z$/;\n\n// Strict: a date that does not exist (2026-02-30) is refused, not rolled over to March.\nfunction instant(v: unknown, path: string): string {\n const s = str(v, path);\n const m = INSTANT.exec(s);\n if (m) {\n const [y, mo, d, h, mi, sec] = m.slice(1, 7).map(Number);\n const t = new Date(Date.UTC(y, mo - 1, d, h, mi, sec));\n if (\n t.getUTCFullYear() === y && t.getUTCMonth() === mo - 1 && t.getUTCDate() === d &&\n t.getUTCHours() === h && t.getUTCMinutes() === mi && t.getUTCSeconds() === sec\n ) {\n return s;\n }\n }\n throw new RecordingFormatError(path, 'expected an RFC 3339 UTC instant ending in Z that exists on the calendar');\n}\n\n// Orders two instants as parsed times, not as strings: \"...:10Z\" is BEFORE \"...:10.5Z\"\n// although it sorts after it as text. Fractions compare to the full digits given (the\n// platform writes up to 7), not just to the millisecond Date can hold.\nexport function compareInstants(a: string, b: string): number {\n const pa = INSTANT.exec(a);\n const pb = INSTANT.exec(b);\n if (!pa || !pb) throw new RangeError(`compareInstants: not an instant: ${pa ? b : a}`);\n const whole = (m: RegExpExecArray): number => Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`);\n const wa = whole(pa);\n const wb = whole(pb);\n if (wa !== wb) return wa < wb ? -1 : 1;\n const fa = (pa[7] ?? '').padEnd(12, '0');\n const fb = (pb[7] ?? '').padEnd(12, '0');\n return fa < fb ? -1 : fa > fb ? 1 : 0;\n}\n\nfunction column<T>(v: unknown, path: string, item: (x: unknown, p: string) => T, length?: number): T[] {\n const a = arr(v, path);\n if (length !== undefined && a.length !== length) {\n throw new RecordingFormatError(path, `length ${a.length} does not match the time column (${length})`);\n }\n return a.map((x, i) => item(x, `${path}[${i}]`));\n}\n\nfunction nonNegative(v: unknown, path: string): number {\n const n = num(v, path);\n if (n < 0) throw new RecordingFormatError(path, 'must not be negative');\n return n;\n}\n\nexport function parseBoardRecording(input: unknown): BoardRecording {\n const root = strict(\n input,\n '$',\n [\n 'kind', 'formatVersion', 'runId', 'startedAtUtc', 'durationMs', 'platformVersion', 'instance', 'tenant',\n 'build', 'clock', 'devices', 'channels', 'board', 'sourceHashes', 'converter', 'chapters',\n 'measurements', 'alarms', 'locations',\n ],\n ['excerpt'],\n );\n\n if (root.kind !== RECORDING_KIND) {\n throw new RecordingFormatError('$.kind', `expected ${JSON.stringify(RECORDING_KIND)}`);\n }\n if (root.formatVersion !== RECORDING_FORMAT_VERSION) {\n throw new RecordingFormatError(\n '$.formatVersion',\n `unsupported version ${JSON.stringify(root.formatVersion)}; this reader understands ${RECORDING_FORMAT_VERSION}`,\n );\n }\n const durationMs = num(root.durationMs, '$.durationMs');\n if (durationMs <= 0) throw new RecordingFormatError('$.durationMs', 'must be positive');\n\n const b = strict(root.build, '$.build', ['gitSha', 'sdkCommit']);\n const build = {\n gitSha: matching(b.gitSha, '$.build.gitSha', HEX_SHORT, 'a lowercase hex commit id'),\n sdkCommit: matching(b.sdkCommit, '$.build.sdkCommit', HEX_SHORT, 'a lowercase hex commit id'),\n };\n\n const clock = column(root.clock, '$.clock', (x, p): RecordedClockSegment => {\n const o = strict(x, p, ['fromMs', 'mode', 'scale']);\n const scale = num(o.scale, `${p}.scale`);\n if (scale <= 0) throw new RecordingFormatError(`${p}.scale`, 'must be positive');\n return { fromMs: nonNegative(o.fromMs, `${p}.fromMs`), mode: str(o.mode, `${p}.mode`), scale };\n });\n clock.forEach((seg, i) => {\n if (i > 0 && seg.fromMs <= clock[i - 1].fromMs) throw new RecordingFormatError(`$.clock[${i}].fromMs`, 'must increase');\n });\n\n const devices = column(root.devices, '$.devices', (x, p): RecordedDevice => {\n const o = strict(x, p, ['id', 'token', 'kind']);\n return { id: str(o.id, `${p}.id`), token: str(o.token, `${p}.token`), kind: str(o.kind, `${p}.kind`) };\n });\n const tokens = new Set(devices.map((d) => d.token));\n if (tokens.size !== devices.length || new Set(devices.map((d) => d.id)).size !== devices.length) {\n throw new RecordingFormatError('$.devices', 'duplicate device id or token');\n }\n\n const ch = strict(root.channels, '$.channels', ['measurements', 'alarms', 'locations', 'commands']);\n if (ch.locations !== false) {\n throw new RecordingFormatError('$.channels.locations', 'this version records no positions; must be false');\n }\n if (ch.commands !== false) {\n throw new RecordingFormatError('$.channels.commands', 'this version records no command data; must be false');\n }\n const channels: RecordedChannels = {\n measurements: column(ch.measurements, '$.channels.measurements', str),\n alarms: bool(ch.alarms, '$.channels.alarms'),\n locations: false,\n commands: false,\n };\n if (new Set(channels.measurements).size !== channels.measurements.length) {\n throw new RecordingFormatError('$.channels.measurements', 'duplicate measurement name');\n }\n\n const bd = strict(root.board, '$.board', ['path', 'sourceCommit', 'sha256']);\n const board = {\n path: str(bd.path, '$.board.path'),\n sourceCommit: matching(bd.sourceCommit, '$.board.sourceCommit', HEX40, 'a full lowercase hex commit id'),\n sha256: matching(bd.sha256, '$.board.sha256', HEX64, 'a lowercase hex SHA-256'),\n };\n\n const sh = root.sourceHashes;\n if (!isObj(sh)) throw new RecordingFormatError('$.sourceHashes', 'expected an object');\n const sourceHashes: Record<string, string> = {};\n for (const key of Object.keys(sh)) {\n // Assigning '__proto__' on a plain object swaps its prototype instead of adding a key.\n if (key === '__proto__') throw new RecordingFormatError(`$.sourceHashes.${key}`, 'reserved key');\n sourceHashes[key] = matching(sh[key], `$.sourceHashes.${key}`, HEX64, 'a lowercase hex SHA-256');\n }\n\n const cv = strict(root.converter, '$.converter', ['version']);\n const converter = { version: num(cv.version, '$.converter.version') };\n\n let excerpt: { fromMs: number; toMs: number } | undefined;\n if (root.excerpt !== undefined) {\n const e = strict(root.excerpt, '$.excerpt', ['fromMs', 'toMs']);\n excerpt = { fromMs: nonNegative(e.fromMs, '$.excerpt.fromMs'), toMs: nonNegative(e.toMs, '$.excerpt.toMs') };\n if (excerpt.fromMs >= excerpt.toMs || excerpt.toMs > durationMs) {\n throw new RecordingFormatError('$.excerpt', 'must be a non-empty window inside the run');\n }\n }\n const lo = excerpt ? excerpt.fromMs : 0;\n const hi = excerpt ? excerpt.toMs : durationMs;\n\n const chapters = column(root.chapters, '$.chapters', (x, p): RecordedChapter => {\n const o = strict(x, p, ['tMs', 'name', 'note']);\n const tMs = num(o.tMs, `${p}.tMs`);\n if (tMs < 0 || tMs > durationMs) throw new RecordingFormatError(`${p}.tMs`, 'is outside the run');\n if (excerpt && (tMs < lo || tMs > hi)) throw new RecordingFormatError(`${p}.tMs`, 'is outside the excerpt window');\n return { tMs, name: str(o.name, `${p}.name`), note: str(o.note, `${p}.note`) };\n });\n chapters.forEach((c, i) => {\n if (i > 0 && c.tMs < chapters[i - 1].tMs) throw new RecordingFormatError(`$.chapters[${i}].tMs`, 'chapters must not decrease in time');\n });\n\n const m = strict(root.measurements, '$.measurements', ['d', 'n', 't', 'v', 's']);\n const t = column(m.t, '$.measurements.t', num);\n let prev = lo;\n t.forEach((x, i) => {\n if (x < lo || x > hi) throw new RecordingFormatError(`$.measurements.t[${i}]`, `${x} is outside [${lo}, ${hi}]`);\n if (x < prev) throw new RecordingFormatError(`$.measurements.t[${i}]`, 'time column must not decrease');\n prev = x;\n });\n const index = (name: string, limit: number) => (x: unknown, p: string): number => {\n const n = num(x, p);\n if (!Number.isInteger(n) || n < 0 || n >= limit) throw new RecordingFormatError(p, `${name} index out of range`);\n return n;\n };\n const measurements: RecordedMeasurementColumns = {\n d: column(m.d, '$.measurements.d', index('device', devices.length), t.length),\n n: column(m.n, '$.measurements.n', index('measurement', channels.measurements.length), t.length),\n t,\n v: column(m.v, '$.measurements.v', num, t.length),\n s: column(\n m.s,\n '$.measurements.s',\n (x, p): 0 | 1 => {\n if (x !== 0 && x !== 1) throw new RecordingFormatError(p, 'expected 0 or 1');\n return x;\n },\n t.length,\n ),\n };\n\n const al = strict(root.alarms, '$.alarms', ['snapshots', 'events']);\n const alarm = (o: Obj, p: string): RecordedAlarm => {\n const id = matching(o.id, `${p}.id`, /^alarm-\\d+$/, 'a re-minted id (alarm-N)');\n const dev = str(o.dev, `${p}.dev`);\n if (!tokens.has(dev)) throw new RecordingFormatError(`${p}.dev`, `device '${dev}' is not in $.devices`);\n if (o.ack !== undefined && o.ack !== true) throw new RecordingFormatError(`${p}.ack`, 'present only when true');\n const out: RecordedAlarm = {\n id,\n dev,\n key: str(o.key, `${p}.key`),\n metric: str(o.metric, `${p}.metric`),\n state: oneOf(o.state, `${p}.state`, ALARM_STATES),\n sev: oneOf(o.sev, `${p}.sev`, ALARM_SEVERITIES),\n occ: instant(o.occ, `${p}.occ`),\n };\n if (o.ack === true) out.ack = true;\n return out;\n };\n const ALARM_KEYS = ['id', 'dev', 'key', 'metric', 'state', 'sev', 'occ'];\n const checkTime = (tMs: number, p: string): number => {\n if (tMs < 0 || tMs > hi) throw new RecordingFormatError(p, `${tMs} is outside [0, ${hi}]`);\n return tMs;\n };\n const snapshots = column(al.snapshots, '$.alarms.snapshots', (x, p): RecordedAlarmSnapshot => {\n const o = strict(x, p, ['tMs', 'requestedAtMs', 'total', 'alarms']);\n const alarms = column(o.alarms, `${p}.alarms`, (a, ap) => alarm(strict(a, ap, ALARM_KEYS, ['ack']), ap));\n const ids = new Set<string>();\n alarms.forEach((a, i) => {\n if (ids.has(a.id)) throw new RecordingFormatError(`${p}.alarms[${i}].id`, `duplicate alarm ${a.id} in one snapshot`);\n ids.add(a.id);\n });\n // A snapshot that lists fewer alarms than its total is a truncated answer. Folding it\n // as the whole set would erase every alarm it left out.\n const total = nonNegative(o.total, `${p}.total`);\n if (total !== alarms.length) {\n throw new RecordingFormatError(`${p}.total`, `is ${total} but ${alarms.length} alarms are listed (truncated snapshot)`);\n }\n const tMs = checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`);\n const requestedAtMs = nonNegative(o.requestedAtMs, `${p}.requestedAtMs`);\n if (!Number.isInteger(requestedAtMs)) throw new RecordingFormatError(`${p}.requestedAtMs`, 'expected an integer');\n if (requestedAtMs > tMs) throw new RecordingFormatError(`${p}.requestedAtMs`, 'a query cannot be sent after its answer');\n return { tMs, requestedAtMs, total, alarms };\n });\n const events = column(al.events, '$.alarms.events', (x, p): RecordedAlarmEvent => {\n const o = strict(x, p, [...ALARM_KEYS, 'tMs'], ['ack']);\n return { ...alarm(o, p), tMs: checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`) };\n });\n for (const [name, list] of [['snapshots', snapshots], ['events', events]] as const) {\n list.forEach((e, i) => {\n if (i > 0 && e.tMs < list[i - 1].tMs) throw new RecordingFormatError(`$.alarms.${name}[${i}].tMs`, 'must not decrease');\n });\n }\n // An alarm id names one alarm on one device for the whole recording.\n const deviceOf = new Map<string, string>();\n const everyAlarm: Array<[string, RecordedAlarm]> = [\n ...snapshots.flatMap((s, i) => s.alarms.map((a, j): [string, RecordedAlarm] => [`$.alarms.snapshots[${i}].alarms[${j}]`, a])),\n ...events.map((e, i): [string, RecordedAlarm] => [`$.alarms.events[${i}]`, e]),\n ];\n for (const [path, a] of everyAlarm) {\n const seen = deviceOf.get(a.id);\n if (seen !== undefined && seen !== a.dev) {\n throw new RecordingFormatError(`${path}.dev`, `alarm ${a.id} was on '${seen}' and cannot move to '${a.dev}'`);\n }\n deviceOf.set(a.id, a.dev);\n }\n if (!channels.alarms && (snapshots.length > 0 || events.length > 0)) {\n throw new RecordingFormatError('$.alarms', 'history present but $.channels.alarms is false');\n }\n\n if (!Array.isArray(root.locations) || root.locations.length !== 0) {\n throw new RecordingFormatError('$.locations', 'this version carries no positions; must be an empty array');\n }\n\n const rec: BoardRecording = {\n kind: RECORDING_KIND,\n formatVersion: RECORDING_FORMAT_VERSION,\n runId: str(root.runId, '$.runId'),\n startedAtUtc: instant(root.startedAtUtc, '$.startedAtUtc'),\n durationMs,\n platformVersion: str(root.platformVersion, '$.platformVersion'),\n instance: str(root.instance, '$.instance'),\n tenant: str(root.tenant, '$.tenant'),\n build,\n clock,\n devices,\n channels,\n board,\n sourceHashes,\n converter,\n chapters,\n measurements,\n alarms: { snapshots, events },\n locations: [],\n };\n if (excerpt) rec.excerpt = excerpt;\n return rec;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// RecordedDataSource \u2014 plays one recorded run (recording.ts) through the SAME\n// WidgetDataSource contract the live DashboardHub and the SyntheticDataSource implement,\n// so the dashboard renderer and every widget consume it unchanged.\n//\n// It is deliberately NOT a mode of SyntheticDataSource. Synthetic data is canned and\n// labelled synthetic; this is observed data, and folding the two together would blur the\n// one boundary a viewer relies on. It also implements WidgetDataSource ONLY, not\n// WidgetActions: a recording cannot acknowledge, clear or send anything, so no write\n// control may render (the alarm table shows its action buttons only when an actions seam\n// exists).\n//\n// \u2500\u2500 Time \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A RecordedClock owns the cursor (play / pause / seek / rate). A source VIEW follows the\n// clock forward: as the cursor passes a row's `t` the row is delivered to the widgets\n// that asked for it.\n//\n// \uD83D\uDD34 A SEEK ENDS A VIEW; IT DOES NOT REWIND ONE. Widget streams only append (a chart\n// window, a \"latest\" card keep what they were given), so a backwards jump cannot be\n// retracted through a sink. The host therefore builds a NEW RecordedDataSource over the\n// same parsed recording after every seek: the renderer sees a new data source, every\n// widget re-subscribes and resets its buffer, and the new view back-fills the preceding\n// window at the new cursor. The old view goes quiet on the seek event (it never emits\n// another row, in either direction) and `isStale` reports it, so a host that forgets to\n// swap it freezes visibly in a test instead of replaying the wrong moment.\n//\n// \u2500\u2500 What the recording does not hold \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A channel the recording did not capture raises NotInRecordingError through the sink's\n// error path. It NEVER answers with an empty snapshot: an empty map would read as \"no\n// machine has a position\" when the truth is \"this recording has no positions\". The widget\n// frame already renders a sink error as \"Data unavailable\" plus the error text, so the\n// message below is what a viewer reads.\n//\n// No network: this module touches no fetch, socket or timer of its own beyond the clock's\n// optional frame ticker, and the tests trap any attempt.\n\nimport type {\n AlarmSnapshot,\n AlarmStreamSink,\n AlarmSubscription,\n CommandStreamSink,\n CommandSubscription,\n DeviceResolver,\n LocationStreamSink,\n LocationSubscription,\n WidgetDataSource,\n WidgetStreamSink,\n} from './hub';\nimport type {\n AlarmRow,\n AnchorTarget,\n DatasourceSelector,\n EntityCandidateLister,\n EntityListKind,\n MeasurementSample,\n} from './types';\nimport {\n compareInstants,\n recordingBounds,\n type BoardRecording,\n type RecordedAlarm,\n type RecordedAlarmEvent,\n type RecordedAlarmSnapshot,\n} from './recording';\n\n// How far back from the cursor a fresh subscription back-fills measurement rows. Raw rows,\n// not buckets: a recording has no aggregate to show honestly.\nexport const RECORDED_HISTORY_WINDOW_MS = 60 * 60 * 1000;\n\n// ---- the typed \"not in this recording\" error ------------------------------\n\nexport type RecordedChannel = 'measurements' | 'alarms' | 'locations' | 'commands';\n\nexport class NotInRecordingError extends Error {\n readonly channel: RecordedChannel;\n readonly detail: string;\n\n constructor(channel: RecordedChannel, detail = '') {\n super(`Not in this recording${detail ? `: ${detail}` : ''}`);\n this.name = 'NotInRecordingError';\n this.channel = channel;\n this.detail = detail;\n }\n\n // The widget error frame prints String(error). The default would read\n // \"NotInRecordingError: Not in this recording\", which says the name twice; the message\n // alone is what a viewer should see.\n toString(): string {\n return this.message;\n }\n}\n\n// ---- the replay clock -----------------------------------------------------\n\nexport const PLAYBACK_RATES = [1, 4, 16] as const;\nexport type PlaybackRate = (typeof PLAYBACK_RATES)[number];\n\n// 'tick' = the cursor advanced during playback; 'seek' = the cursor was moved (any\n// direction); 'state' = play / pause / rate changed with the cursor where it was.\nexport interface ClockEvent {\n timeMs: number;\n reason: 'tick' | 'seek' | 'state';\n}\n\n// A frame source for the clock. The default drives requestAnimationFrame (or a timer where\n// there is none); a test injects its own, or drives the clock by hand with advance().\nexport interface ClockTicker {\n // Start calling onFrame(wallDeltaMs) until the returned stopper is called.\n start(onFrame: (wallDeltaMs: number) => void): () => void;\n}\n\nfunction defaultTicker(): ClockTicker {\n return {\n start(onFrame) {\n const now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n let last = now();\n const step = (): void => {\n const t = now();\n const dt = t - last;\n last = t;\n onFrame(dt);\n };\n if (typeof requestAnimationFrame === 'function') {\n let handle = 0;\n let stopped = false;\n const loop = (): void => {\n if (stopped) return;\n step();\n handle = requestAnimationFrame(loop);\n };\n handle = requestAnimationFrame(loop);\n return () => {\n stopped = true;\n if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);\n };\n }\n const timer = setInterval(step, 100);\n return () => clearInterval(timer);\n },\n };\n}\n\nexport class RecordedClock {\n // The end of the playable span, and its beginning (0 for a whole run; an excerpt starts\n // at its window).\n readonly durationMs: number;\n readonly minMs: number;\n private time: number;\n private isPlaying = false;\n private playbackRate: PlaybackRate = 1;\n private stopTicker: (() => void) | null = null;\n private readonly ticker: ClockTicker;\n private readonly listeners = new Set<(ev: ClockEvent) => void>();\n\n constructor(durationMs: number, options: { ticker?: ClockTicker; startMs?: number; minMs?: number } = {}) {\n if (!Number.isFinite(durationMs) || durationMs <= 0) {\n throw new RangeError(`RecordedClock: durationMs must be positive and finite, got ${durationMs}`);\n }\n this.durationMs = durationMs;\n this.minMs = options.minMs ?? 0;\n if (!Number.isFinite(this.minMs) || this.minMs < 0 || this.minMs >= durationMs) {\n throw new RangeError(`RecordedClock: minMs must be in [0, ${durationMs}), got ${this.minMs}`);\n }\n this.ticker = options.ticker ?? defaultTicker();\n this.time = this.clampTime(options.startMs ?? this.minMs);\n }\n\n get timeMs(): number {\n return this.time;\n }\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get rate(): PlaybackRate {\n return this.playbackRate;\n }\n\n get ended(): boolean {\n return this.time >= this.durationMs;\n }\n\n // play starts the cursor moving. At the end of the run it does nothing: playback stops\n // there (no loop seam) and a host that wants to start over seeks to 0 first.\n play(): void {\n if (this.isPlaying || this.ended) return;\n this.isPlaying = true;\n this.stopTicker = this.ticker.start((dt) => this.advance(dt));\n this.emit('state');\n }\n\n pause(): void {\n if (!this.isPlaying) return;\n this.halt();\n this.emit('state');\n }\n\n // seek moves the cursor, clamped to [minMs, durationMs]. Playback state is unchanged, except\n // that seeking to the very end stops it (there is nothing left to play).\n seek(tMs: number): void {\n if (!Number.isFinite(tMs)) throw new RangeError(`RecordedClock.seek: not a finite time: ${tMs}`);\n this.time = this.clampTime(tMs);\n if (this.isPlaying && this.ended) this.halt();\n this.emit('seek');\n }\n\n setRate(rate: PlaybackRate): void {\n if (!(PLAYBACK_RATES as readonly number[]).includes(rate)) {\n throw new RangeError(`RecordedClock.setRate: ${rate} is not one of ${PLAYBACK_RATES.join(', ')}`);\n }\n this.playbackRate = rate;\n this.emit('state');\n }\n\n // advance moves the cursor by wallDeltaMs of real time (scaled by the playback rate).\n // The ticker calls this each frame; a test calls it directly. It does nothing while\n // paused, and stops at the end of the run.\n advance(wallDeltaMs: number): void {\n if (!this.isPlaying) return;\n if (!Number.isFinite(wallDeltaMs) || wallDeltaMs < 0) {\n throw new RangeError(`RecordedClock.advance: expected a non-negative finite delta, got ${wallDeltaMs}`);\n }\n this.time = this.clampTime(this.time + wallDeltaMs * this.playbackRate);\n if (this.ended) this.halt();\n this.emit('tick');\n }\n\n subscribe(listener: (ev: ClockEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private halt(): void {\n this.isPlaying = false;\n this.stopTicker?.();\n this.stopTicker = null;\n }\n\n private clampTime(t: number): number {\n return t < this.minMs ? this.minMs : t > this.durationMs ? this.durationMs : t;\n }\n\n private emit(reason: ClockEvent['reason']): void {\n for (const listener of [...this.listeners]) listener({ timeMs: this.time, reason });\n }\n}\n\n// ---- helpers --------------------------------------------------------------\n\n// First index whose value is >= x (the array is non-decreasing).\nfunction lowerBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] < x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// First index whose value is > x.\nfunction upperBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] <= x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// createRecordedClock builds a clock covering exactly what the recording can play: the\n// whole run, or an excerpt's window (starting at its beginning).\nexport function createRecordedClock(rec: BoardRecording, options: { ticker?: ClockTicker } = {}): RecordedClock {\n const { fromMs, toMs } = recordingBounds(rec);\n return new RecordedClock(toMs, { minMs: fromMs, ticker: options.ticker });\n}\n\n// ---- anchors, resolver and lister -----------------------------------------\n\n// The recording does not carry the anchor a board is bound to; it carries the devices. The\n// host names the anchors (taken from the board definition it plays) whose members are the\n// run's devices. Every device of a Sitepulse run is a member of the one site CUSTOMER, so\n// that anchor expands to ALL of the run's devices. Exactly one anchor may be declared, and\n// it must be a customer: an area or asset anchor would claim every device is in it, which\n// the recording cannot know. An anchor that was not declared is unknown and is refused,\n// never answered with an empty set. Declaring nothing declares no anchor at all.\nexport interface RecordedSiteOptions {\n siteAnchors?: readonly AnchorTarget[];\n}\n\nfunction sameAnchor(a: AnchorTarget, b: AnchorTarget): boolean {\n return a.relationship === b.relationship && a.targetType === b.targetType && a.targetToken === b.targetToken;\n}\n\n// The one declared site anchor, or null when none was declared. Anything else is refused\n// when the source, resolver or lister is built, not at some later lookup.\nfunction declaredSite(options: RecordedSiteOptions): AnchorTarget | null {\n const anchors = options.siteAnchors;\n if (anchors === undefined) return null;\n if (anchors.length !== 1) {\n throw new Error(`RecordedSiteOptions.siteAnchors must declare exactly one anchor, got ${anchors.length}`);\n }\n if (anchors[0].targetType !== 'customer') {\n throw new Error(\n `RecordedSiteOptions.siteAnchors must be a customer anchor, got '${anchors[0].targetType}': ` +\n 'a recording cannot say that every device belongs to an area or an asset',\n );\n }\n return anchors[0];\n}\n\nfunction anchorMembers(rec: BoardRecording, site: AnchorTarget | null, anchor: AnchorTarget): string[] {\n if (!site || !sameAnchor(site, anchor)) {\n throw new Error(\n `anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`,\n );\n }\n return rec.devices.map((d) => d.token);\n}\n\n// createRecordedResolver answers the hub's DeviceResolver questions from the recording: a\n// site anchor expands to the run's devices, and a device exists if the run has it. An\n// anchor the host did not declare THROWS (an empty answer would read as \"the site had no\n// machines\").\nexport function createRecordedResolver(rec: BoardRecording, options: RecordedSiteOptions = {}): DeviceResolver {\n const devices = new Set(rec.devices.map((d) => d.token));\n const site = declaredSite(options);\n return {\n devicesForAnchor: async (anchor) => anchorMembers(rec, site, anchor),\n deviceExists: async (token) => devices.has(token),\n };\n}\n\n// createRecordedLister backs a root entity-selector's candidate list from the recording.\n//\n// \uD83D\uDD34 WITHOUT A LISTER THE ENTITY SELECTOR SAYS \"Selection is available on the live\n// dashboard.\" A replay must never say that. The lister is what turns the widget's\n// selection on, so a host that mounts a recording passes this one.\n//\n// Devices are listed by token (the recording carries no display names). The other kinds\n// list the declared site anchors of that type; a kind with none yields an empty list,\n// which is true of the recording.\nexport function createRecordedLister(rec: BoardRecording, options: RecordedSiteOptions = {}): EntityCandidateLister {\n const site = declaredSite(options);\n return async (kind: EntityListKind) => {\n if (kind === 'device') return rec.devices.map((d) => ({ token: d.token, name: null }));\n return site && site.targetType === kind ? [{ token: site.targetToken, name: null }] : [];\n };\n}\n\n// ---- the recording, indexed -----------------------------------------------\n\ninterface Series {\n device: string;\n name: string;\n // Row indices into the measurement columns, ascending in time, and the same rows' times.\n rows: number[];\n times: number[];\n}\n\n// One alarm record on the merged history timeline. A snapshot REPLACES the whole alarm set;\n// an event sets one alarm. At equal times a snapshot comes first.\ntype TimelineEntry =\n | { tMs: number; snapshot: RecordedAlarmSnapshot }\n | { tMs: number; event: RecordedAlarmEvent };\n\ninterface Indexed {\n byDevice: Map<string, Series[]>;\n timeline: TimelineEntry[];\n timelineTimes: number[];\n}\n\n// A view is rebuilt after every seek, so the index is built once per parsed recording.\nconst indexCache = new WeakMap<BoardRecording, Indexed>();\n\nfunction indexOf(rec: BoardRecording): Indexed {\n const cached = indexCache.get(rec);\n if (cached) return cached;\n\n const bySeries = new Map<string, Series>();\n const byDevice = new Map<string, Series[]>();\n const { d, n, t } = rec.measurements;\n for (let i = 0; i < t.length; i++) {\n const key = d[i] * rec.channels.measurements.length + n[i];\n let s = bySeries.get(String(key));\n if (!s) {\n s = { device: rec.devices[d[i]].token, name: rec.channels.measurements[n[i]], rows: [], times: [] };\n bySeries.set(String(key), s);\n const list = byDevice.get(s.device);\n if (list) list.push(s);\n else byDevice.set(s.device, [s]);\n }\n s.rows.push(i);\n s.times.push(t[i]);\n }\n\n const timeline: TimelineEntry[] = [\n ...rec.alarms.snapshots.map((snapshot): TimelineEntry => ({ tMs: snapshot.tMs, snapshot })),\n ...rec.alarms.events.map((event): TimelineEntry => ({ tMs: event.tMs, event })),\n ];\n // Array.prototype.sort is stable: snapshots were listed first, so they stay first at ties.\n timeline.sort((a, b) => a.tMs - b.tMs);\n\n const indexed: Indexed = { byDevice, timeline, timelineTimes: timeline.map((e) => e.tMs) };\n indexCache.set(rec, indexed);\n return indexed;\n}\n\n// An alarm as the fold holds it.\ninterface FoldedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: string;\n sev: string;\n raised: string | null;\n cleared: string | null;\n ack: boolean;\n // The recording time this state is known as of: the event's time, or for an alarm taken\n // from a snapshot, when that snapshot's query was sent.\n asOfMs: number;\n}\n\n// ---- the data source ------------------------------------------------------\n\ninterface Subscription {\n // Called with the new cursor whenever the clock ticks forward.\n advance(cursor: number): void;\n dispose(): void;\n}\n\ninterface DeviceNames {\n deviceToken: string;\n names: Set<string>;\n}\n\nexport class RecordedDataSource implements WidgetDataSource {\n private readonly rec: BoardRecording;\n private readonly site: AnchorTarget | null;\n private readonly index: Indexed;\n private readonly startMs: number;\n private readonly devices: ReadonlySet<string>;\n private readonly subs = new Set<Subscription>();\n private readonly unlisten: () => void;\n private cursor: number;\n private stale = false;\n\n // `clock` is the shared replay clock; the view starts at its current time. `options`\n // names the site anchors this recording's devices belong to (see RecordedSiteOptions).\n constructor(rec: BoardRecording, clock: RecordedClock, options: RecordedSiteOptions = {}) {\n this.rec = rec;\n this.site = declaredSite(options);\n this.index = indexOf(rec);\n this.cursor = clock.timeMs;\n this.startMs = Date.parse(rec.startedAtUtc);\n this.devices = new Set(rec.devices.map((d) => d.token));\n this.unlisten = clock.subscribe((ev) => this.onClock(ev));\n }\n\n // True once the clock has been seeked since this view was built. A stale view emits\n // nothing further; the host builds a new view at the new cursor.\n get isStale(): boolean {\n return this.stale;\n }\n\n private onClock(ev: ClockEvent): void {\n if (this.stale) return;\n if (ev.reason === 'seek') {\n this.stale = true;\n this.unlisten();\n return;\n }\n if (ev.reason !== 'tick') return;\n this.cursor = ev.timeMs;\n for (const sub of [...this.subs]) sub.advance(this.cursor);\n }\n\n private iso(offsetMs: number): string {\n return new Date(this.startMs + offsetMs).toISOString();\n }\n\n // open tracks a subscription and returns its disposer. `start` runs once, in a\n // microtask, so the disposer is in the caller's hands before anything is emitted (the\n // contract every source keeps: a synchronous disposer, and nothing emitted after it).\n // `advance` runs on each forward tick after that.\n private open(handlers: {\n start(cursor: number, isDisposed: () => boolean): void;\n advance(cursor: number, isDisposed: () => boolean): void;\n }): () => void {\n let disposed = false;\n let started = false;\n const isDisposed = (): boolean => disposed;\n const sub: Subscription = {\n advance: (cursor) => {\n if (started && !disposed) handlers.advance(cursor, isDisposed);\n },\n dispose: () => {\n disposed = true;\n this.subs.delete(sub);\n },\n };\n this.subs.add(sub);\n queueMicrotask(() => {\n if (disposed || this.stale) return;\n started = true;\n handlers.start(this.cursor, isDisposed);\n });\n return sub.dispose;\n }\n\n // Resolve a selector to the devices it names, synchronously and from the recording.\n // Mirrors the hub: unbound is zero devices, an unresolved slot is a loud error.\n private resolve(ds: DatasourceSelector): DeviceNames[] {\n switch (ds.kind) {\n case 'device':\n return [{ deviceToken: ds.deviceToken, names: new Set(ds.measurements) }];\n case 'anchor':\n return anchorMembers(this.rec, this.site, ds.anchor).map((deviceToken) => ({\n deviceToken,\n names: new Set(ds.measurements),\n }));\n case 'unbound':\n return [];\n case 'slot':\n throw new Error(\n `dashboard slot '${ds.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(`dashboard selector kind '${ds.kind}' is not supported yet`);\n }\n }\n\n // Report a failure on a sink, asynchronously and only if the subscription is still live.\n private failLater(err: unknown, sink: { error?: (e: unknown) => void }): () => void {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) sink.error?.(err);\n });\n return () => {\n disposed = true;\n };\n }\n\n // \u2500\u2500 measurements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n let groups: DeviceNames[];\n try {\n groups = this.resolve(datasource);\n for (const g of groups) {\n for (const name of g.names) {\n if (!this.rec.channels.measurements.includes(name)) {\n throw new NotInRecordingError('measurements', `no '${name}' series was recorded`);\n }\n }\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // Each selected series keeps its own next-row position, so a tick delivers exactly the\n // rows the cursor has newly passed.\n const picked: Array<{ s: Series; next: number }> = [];\n for (const g of groups) {\n for (const s of this.index.byDevice.get(g.deviceToken) ?? []) {\n if (g.names.size === 0 || g.names.has(s.name)) picked.push({ s, next: 0 });\n }\n }\n\n const { t, v } = this.rec.measurements;\n const flush = (cursor: number, isDisposed: () => boolean): void => {\n const due: Array<{ t: number; sample: MeasurementSample }> = [];\n for (const p of picked) {\n const { s } = p;\n while (p.next < s.times.length && s.times[p.next] <= cursor) {\n const row = s.rows[p.next++];\n due.push({\n t: t[row],\n sample: {\n id: `${s.device}|${s.name}|${row}`,\n deviceToken: s.device,\n eventType: 0,\n // The time the viewer applied the row: the recording holds no separate\n // occurred time for measurements.\n occurredTime: this.iso(t[row]),\n name: s.name,\n value: v[row],\n classifier: null,\n },\n });\n }\n }\n // Chronological across series (stable for equal times), so a widget's window and a\n // \"latest\" card agree on what arrived last.\n due.sort((a, b) => a.t - b.t);\n for (const d of due) {\n if (isDisposed()) return;\n sink.next(d.sample);\n }\n };\n\n return this.open({\n // Back-fill: rows from the preceding window up to the cursor, then play on.\n start: (cursor, isDisposed) => {\n for (const p of picked) p.next = lowerBound(p.s.times, cursor - RECORDED_HISTORY_WINDOW_MS);\n flush(cursor, isDisposed);\n },\n advance: flush,\n });\n }\n\n // \u2500\u2500 alarms \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n let scope: Set<string> | null = null; // null = tenant-wide (no datasource)\n try {\n if (!this.rec.channels.alarms) throw new NotInRecordingError('alarms');\n if (subscription.datasource) {\n scope = new Set(this.resolve(subscription.datasource).map((g) => g.deviceToken));\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // The fold is monotone because a view only moves forward: apply each history entry\n // once, in order. A snapshot replaces the set; an event sets one alarm.\n const { timeline, timelineTimes } = this.index;\n const alarms = new Map<string, FoldedAlarm>();\n let applied = 0;\n let lastSignature: string | null = null;\n\n const apply = (a: RecordedAlarm, asOfMs: number): void => {\n const prior = alarms.get(a.id);\n alarms.set(a.id, {\n id: a.id,\n dev: a.dev,\n key: a.key,\n metric: a.metric,\n state: a.state,\n sev: a.sev,\n // `occ` is the time of THIS state: the raise time for an ACTIVE row, the clear time\n // for a CLEARED one. A cleared alarm keeps the raise time its ACTIVE row gave it\n // (null if the history never showed it being raised).\n raised: a.state === 'ACTIVE' ? a.occ : (prior?.raised ?? null),\n cleared: a.state === 'CLEARED' ? a.occ : null,\n // Each record's own `ack` decides; acknowledgement is not carried over from an\n // earlier record of the same alarm.\n ack: a.ack === true,\n asOfMs,\n });\n };\n\n const emit = (cursor: number, isDisposed: () => boolean): void => {\n const upTo = upperBound(timelineTimes, cursor);\n for (; applied < upTo; applied++) {\n const entry = timeline[applied];\n if ('snapshot' in entry) {\n // A snapshot is the answer to a query sent at requestedAtMs, so it knows nothing\n // of an alarm that changed after that: those keep the event's (newer) state, even\n // when the snapshot omits or contradicts them. Everything else is replaced.\n const { requestedAtMs } = entry.snapshot;\n const newer = [...alarms.values()].filter((a) => a.asOfMs > requestedAtMs);\n alarms.clear();\n for (const a of entry.snapshot.alarms) apply(a, requestedAtMs);\n for (const a of newer) alarms.set(a.id, a);\n } else {\n apply(entry.event, entry.event.tMs);\n }\n }\n const matches = [...alarms.values()].filter(\n (a) =>\n (scope === null || scope.has(a.dev)) &&\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.sev === subscription.severity) &&\n (subscription.acknowledged == null || a.ack === subscription.acknowledged),\n );\n // Newest first by raise time, compared as instants (not as text); the id breaks ties\n // so the order is deterministic.\n const when = (a: FoldedAlarm): string | null => a.raised ?? a.cleared;\n const byTime = (a: FoldedAlarm, b: FoldedAlarm): number => {\n const wa = when(a);\n const wb = when(b);\n if (wa === null || wb === null) return wa === wb ? 0 : wa === null ? 1 : -1;\n return compareInstants(wb, wa);\n };\n matches.sort((a, b) => byTime(a, b) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n const page = matches.slice(0, subscription.pageSize);\n // Whole snapshots, re-emitted only when the fold actually changed.\n const signature = JSON.stringify([matches.length, page]);\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n const snapshot: AlarmSnapshot = { alarms: page.map(toAlarmRow), total: matches.length };\n sink.next(snapshot);\n };\n\n return this.open({ start: emit, advance: emit });\n }\n\n // \u2500\u2500 commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // The recording holds no command data, so this channel is never available.\n subscribeCommands(_subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n return this.failLater(new NotInRecordingError('commands'), sink);\n }\n\n // \u2500\u2500 locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // This format version records no positions. A selector that names no location series\n // asks for nothing, and one that resolves to no device has nothing to place: both get the\n // empty snapshot, as from the hub. Naming a location series for devices that exist is a\n // request for data the recording does not have, which is an error, never an empty map.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n const ds = subscription.datasource;\n let tokens: string[] = [];\n if (ds?.location) {\n try {\n tokens = this.resolve(ds).map((g) => g.deviceToken);\n } catch (err) {\n return this.failLater(err, sink);\n }\n if (tokens.length > 0) {\n return this.failLater(new NotInRecordingError('locations', 'no positions were recorded'), sink);\n }\n }\n return this.open({\n start: (_cursor, isDisposed) => {\n if (!isDisposed()) sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n },\n advance: () => {},\n });\n }\n\n // \u2500\u2500 availability \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A device selector is available iff the run has that device. Anchors, unbound slots and\n // no datasource have a legitimate empty state and are available, as on the hub.\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n if (datasource?.kind === 'device' && datasource.deviceToken) return this.devices.has(datasource.deviceToken);\n return true;\n }\n\n // Stop every subscription and leave the clock. Call when the view is replaced.\n disposeAll(): void {\n this.unlisten();\n for (const sub of [...this.subs]) sub.dispose();\n }\n}\n\nfunction toAlarmRow(a: FoldedAlarm): AlarmRow {\n return {\n token: a.id,\n originatorType: 'device',\n originatorToken: a.dev,\n alarmKey: a.key,\n metricKey: a.metric,\n state: a.state,\n acknowledged: a.ack,\n severity: a.sev,\n raisedTime: a.raised,\n clearedTime: a.cleared,\n // The recording notes THAT an alarm was acknowledged, not when or by whom.\n acknowledgedTime: null,\n acknowledgedBy: null,\n // The stored alarm's last value is not recorded, and a nearby measurement would\n // invent the integrator's state. Null renders as an em dash.\n lastValue: null,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Pure state transforms for the canvas editor. Kept free of React/DOM so the\n// editing logic (move, resize, delete, reorder, retitle, add) is unit-testable;\n// the editor UI (console + the reference /dash app) wires these to react-rnd and\n// the save mutation. Lives here \u2014 next to parse/serialize \u2014 because it is the\n// definition's edit lifecycle, not app-specific glue (ADR-039 authoring in the\n// console).\n//\n// The editor edits the 'base' breakpoint boxes only; per-breakpoint responsive\n// editing is deferred. Boxes are stored as CSS-Grid span placements (col/colSpan/\n// row/rowSpan); the canvas measures its rendered column width and maps boxes to/from\n// pixels (gridBoxToPx / pxToGridBox) so react-rnd drag+resize snap to real grid lines.\n\nimport { BASE_BREAKPOINT, generateWidgetId } from './definition';\nimport type {\n CanvasGrid,\n CanvasSizing,\n DashboardDefinition,\n ResolvedWidgetInstance,\n WidgetBox,\n WidgetInstance,\n} from './types';\nimport type { WidgetDescriptor } from './widget-descriptor';\n\n// The base box a widget is placed by in the editor.\nexport function baseBox(widget: WidgetInstance): WidgetBox {\n return widget.layout[BASE_BREAKPOINT];\n}\n\n// setWidgetBox replaces a widget's base box, returning a new definition.\nexport function setWidgetBox(def: DashboardDefinition, id: string, box: WidgetBox): DashboardDefinition {\n return {\n ...def,\n widgets: def.widgets.map((w) =>\n w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: box } } : w,\n ),\n };\n}\n\n// deleteWidget removes a widget by id.\nexport function deleteWidget(def: DashboardDefinition, id: string): DashboardDefinition {\n return { ...def, widgets: def.widgets.filter((w) => w.id !== id) };\n}\n\n// bringToFront raises a widget above the others (z = current max + 1).\nexport function bringToFront(def: DashboardDefinition, id: string): DashboardDefinition {\n const widget = def.widgets.find((w) => w.id === id);\n if (!widget) return def;\n const box = baseBox(widget);\n // The highest z among the OTHER widgets. Strictly above them all \u2192 already on\n // top (no bump \u2014 this also covers a lone widget at z=0). On a tie, bump past it.\n const maxOther = Math.max(-Infinity, ...def.widgets.filter((w) => w.id !== id).map((w) => baseBox(w).z));\n if (box.z > maxOther) return def;\n return setWidgetBox(def, id, { ...box, z: maxOther + 1 });\n}\n\n// setTitle updates the dashboard title.\nexport function setTitle(def: DashboardDefinition, title: string): DashboardDefinition {\n return { ...def, title };\n}\n\n// setCanvasGrid merges a partial grid change (columns / gap / rowHeight) into the\n// canvas, returning a new definition. It owns the grid invariants (parse-time\n// clamping is asymmetric \u2014 a live edit never round-trips through parse): columns and\n// rowHeight floor to >=1 integers so the renderer never emits `repeat(0,\u2026)` or a\n// fractional/`repeat(12.5,\u2026)` template (which CSS drops wholesale). When columns\n// SHRINKS, existing widgets that now overrun the grid are clamped back inside it \u2014\n// otherwise they'd land in implicit `auto` tracks past the fluid columns (0-width /\n// horizontal overflow in the viewer).\nexport function setCanvasGrid(def: DashboardDefinition, patch: Partial<CanvasGrid>): DashboardDefinition {\n const grid = { ...def.canvas.grid, ...patch };\n if (patch.columns !== undefined) grid.columns = Math.max(1, Math.round(patch.columns));\n if (patch.rowHeight !== undefined) grid.rowHeight = Math.max(1, Math.round(patch.rowHeight));\n const widgets =\n grid.columns < def.canvas.grid.columns ? def.widgets.map((w) => clampWidgetColumns(w, grid.columns)) : def.widgets;\n return { ...def, widgets, canvas: { ...def.canvas, grid } };\n}\n\n// clampWidgetColumns pulls every breakpoint box of a widget back inside `columns`\n// (col <= columns-1, colSpan <= columns-col), leaving rows untouched.\nfunction clampWidgetColumns(widget: WidgetInstance, columns: number): WidgetInstance {\n const layout: WidgetInstance['layout'] = {};\n for (const [bp, box] of Object.entries(widget.layout)) {\n const col = Math.min(box.col, columns - 1);\n layout[bp] = { ...box, col, colSpan: Math.min(box.colSpan, columns - col) };\n }\n return { ...widget, layout };\n}\n\n// setCanvasSizing replaces the container-sizing knob (fill / fixed width / fixed height).\nexport function setCanvasSizing(def: DashboardDefinition, sizing: CanvasSizing): DashboardDefinition {\n return { ...def, canvas: { ...def.canvas, sizing } };\n}\n\n// updateWidget replaces the widget with the matching id, returning a new definition.\nexport function updateWidget(def: DashboardDefinition, id: string, next: WidgetInstance): DashboardDefinition {\n return { ...def, widgets: def.widgets.map((w) => (w.id === id ? next : w)) };\n}\n\n// addWidget appends a new widget of the described kind, placed on top (z = max existing\n// z + 1) at a sensible default base box, datasource left undefined. Its starting options\n// are the descriptor's own `defaults` \u2014 the widget's declaration, not a table in the editor\n// keyed by widget id. Returns the new definition and the new widget's id so the caller can\n// select it.\n//\n// The new widget records the registration it was authored against: its release, its settings\n// version and its SDK generation (`widget: { version, optionsVersion, sdkApiVersion }`). For\n// an extension that record is what lets the next load match it to the installed package at\n// all \u2014 a registered extension stored without it is incomplete and parses to a placeholder.\n// `optionsVersion` and `sdkApiVersion` are the compatibility key (with the id), so both are\n// recorded: a reference without the SDK generation could never be refused by a host that has\n// moved to a newer one. `version` is the provenance the editor's version notice compares.\nexport function addWidget(\n def: DashboardDefinition,\n descriptor: Pick<WidgetDescriptor, 'id' | 'defaults' | 'version' | 'optionsVersion' | 'sdkApiVersion'>,\n): { definition: DashboardDefinition; id: string } {\n const maxZ = def.widgets.reduce((m, w) => Math.max(m, baseBox(w).z), 0);\n const id = generateWidgetId();\n // A sensible starter tile on a high-res grid: a third of a 24-col canvas, 4 rows.\n const box: WidgetBox = { col: 0, colSpan: 8, row: 0, rowSpan: 4, z: maxZ + 1 };\n const common = {\n id,\n widget: {\n version: descriptor.version,\n optionsVersion: descriptor.optionsVersion,\n sdkApiVersion: descriptor.sdkApiVersion,\n },\n layout: { [BASE_BREAKPOINT]: box },\n options: { ...descriptor.defaults },\n };\n // The id came from a registered descriptor, so it names either a built-in or a namespaced\n // extension; which one is the registry's knowledge, and this transform does not branch on it.\n const widget = { ...common, type: descriptor.id } as ResolvedWidgetInstance;\n return { definition: { ...def, widgets: [...def.widgets, widget] }, id };\n}\n\n// A pixel rectangle react-rnd reports after a drag/resize.\nexport interface PixelRect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n// The rendered geometry of one grid, in pixels \u2014 what the canvas measures so it can\n// map span boxes to/from the pixel rects react-rnd works in. `colWidth` is the width\n// of one fractional column at the current container width; the gaps are the gutters.\nexport interface GridGeometry {\n colWidth: number;\n colGap: number;\n rowHeight: number;\n rowGap: number;\n}\n\n// gridBoxToPx renders a span box to a pixel rect at the current grid geometry. The\n// stride between track starts is (track + gap); a span of N covers N tracks and the\n// N-1 gaps between them. The signed `offset` (overlap escape hatch) is added last.\nexport function gridBoxToPx(box: WidgetBox, geom: GridGeometry): PixelRect {\n const colStride = geom.colWidth + geom.colGap;\n const rowStride = geom.rowHeight + geom.rowGap;\n const dx = box.offset?.x ?? 0;\n const dy = box.offset?.y ?? 0;\n return {\n x: box.col * colStride + dx,\n y: box.row * rowStride + dy,\n w: box.colSpan * geom.colWidth + (box.colSpan - 1) * geom.colGap,\n h: box.rowSpan * geom.rowHeight + (box.rowSpan - 1) * geom.rowGap,\n };\n}\n\n// pxToGridBox snaps a pixel rect back to a span box, preserving z and any existing\n// offset (drag/resize move on the grid; offset is a hand-edited fine nudge the editor\n// doesn't clobber). Clamped so a widget can't leave the canvas (col,row >= 0) or\n// vanish (spans >= 1); when `columns` is given, also clamped to the RIGHT edge\n// (col <= columns-1, colSpan <= columns-col) so a boundary/offset drag can't commit a\n// box that overruns the grid into implicit tracks. The pixel rect is un-offset first\n// so the snap is grid-relative.\nexport function pxToGridBox(\n px: PixelRect,\n geom: GridGeometry,\n z: number,\n offset?: { x: number; y: number },\n columns?: number,\n): WidgetBox {\n const colStride = Math.max(1, geom.colWidth + geom.colGap);\n const rowStride = Math.max(1, geom.rowHeight + geom.rowGap);\n const x = px.x - (offset?.x ?? 0);\n const y = px.y - (offset?.y ?? 0);\n let col = Math.max(0, Math.round(x / colStride));\n let colSpan = Math.max(1, Math.round((px.w + geom.colGap) / colStride));\n if (columns !== undefined) {\n col = Math.min(col, columns - 1);\n colSpan = Math.min(colSpan, columns - col);\n }\n const box: WidgetBox = {\n col,\n colSpan,\n row: Math.max(0, Math.round(y / rowStride)),\n rowSpan: Math.max(1, Math.round((px.h + geom.rowGap) / rowStride)),\n z,\n };\n if (offset) box.offset = offset;\n return box;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The concrete DeviceResolver \u2014 the device-management-backed implementation of the\n// interface DashboardHub injects. It is the one place the dashboard runtime couples\n// to device-management's schema (the Hub itself couples to event-management's\n// measurementStream), so the widget/renderer layers above stay purely presentational.\n//\n// devicesForAnchor \u2014 entityRelationships filtered to (source=device, target=anchor)\n// \u2192 each relationship's source token is a member device (the Hub\n// opens one measurementStream per token, per ADR-044).\n//\n// Results are memoized per-anchor for the resolver's lifetime: a dashboard resolves\n// the same handful of anchors repeatedly (re-mounts, re-renders), and the membership\n// is stable for a viewing session.\n\nimport { gql } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport {\n DEVICES_BY_TOKEN,\n DEVICES_FOR_ANCHOR,\n type DevicesByTokenResult,\n type EntityRelationshipsResult,\n} from './queries';\nimport type { AnchorTarget } from './types';\n\n// A generous page size for anchor membership \u2014 Phase 1 dashboards anchor to areas\n// with tens of devices, not thousands; server-side aggregation is Phase 2.\nconst ANCHOR_PAGE_SIZE = 500;\n\nexport function createDeviceResolver(): DeviceResolver {\n const anchorCache = new Map<string, Promise<string[]>>();\n // Memoize existence per token for the resolver's lifetime: many widgets on a\n // dashboard bind the same device, and a device's existence is stable for a viewing\n // session (a delete mid-session is rare and the next mount re-checks).\n const existsCache = new Map<string, Promise<boolean>>();\n\n return {\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]> {\n const key = `${anchor.relationship}|${anchor.targetType}|${anchor.targetToken}`;\n let pending = anchorCache.get(key);\n if (!pending) {\n pending = gql('device-management', DEVICES_FOR_ANCHOR, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n sourceType: 'device',\n targetType: anchor.targetType,\n target: anchor.targetToken,\n relationshipType: anchor.relationship,\n },\n })\n .then((r: EntityRelationshipsResult) =>\n r.entityRelationships.results.map((rel) => rel.source.token),\n )\n .catch((err) => {\n anchorCache.delete(key);\n throw err;\n });\n anchorCache.set(key, pending);\n }\n return pending;\n },\n\n // deviceExists caches only a POSITIVE result (a live device is stable for the\n // session); a negative and an error both drop the entry so a later check re-queries.\n // ADR-042 frees a token on delete, so a deleted-then-recreated device must be able to\n // recover on a long-lived viewer (the availability hook re-checks on a timer while a\n // widget shows unavailable) rather than staying stuck \"gone\" for the whole session.\n // The in-flight promise is still shared, so concurrent checks for one token coalesce.\n // (Batching distinct tokens into one devicesByToken call is a deferred optimization \u2014\n // Phase-1 dashboards bind a handful of devices.)\n deviceExists(deviceToken: string): Promise<boolean> {\n let pending = existsCache.get(deviceToken);\n if (!pending) {\n pending = gql('device-management', DEVICES_BY_TOKEN, { tokens: [deviceToken] })\n .then((r: DevicesByTokenResult) => {\n const exists = r.devicesByToken.some((d) => d.token === deviceToken);\n if (!exists) existsCache.delete(deviceToken);\n return exists;\n })\n .catch((err) => {\n // A failure is inconclusive, not \"gone\": drop the entry so the next check\n // retries, and rethrow so the caller fails open (renders available).\n existsCache.delete(deviceToken);\n throw err;\n });\n existsCache.set(deviceToken, pending);\n }\n return pending;\n },\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// History seeding \u2014 turns event-management's bucketedMeasurements aggregates into\n// MeasurementSamples so a chart shows recent history immediately instead of\n// filling in only from live data. Each bucket becomes one synthetic sample at the\n// bucket start carrying its average; the live tail then extends it.\n//\n// Seeds `device` selectors only (single numeric id). Anchor (multi-device) history\n// seeding is deferred \u2014 the live stream still populates anchor charts.\n\nimport { gql } from '@devicechain/client';\n\nimport { BUCKETED_MEASUREMENTS } from './queries';\nimport type { MeasurementSample, WidgetInstance } from './types';\n\nexport interface HistoryWindow {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n}\n\n// The default backfill: the last hour bucketed per minute (~60 points) \u2014 enough to\n// give a chart shape on load without a heavy query.\nexport function defaultHistoryWindow(): HistoryWindow {\n const now = Date.now();\n return {\n startTime: new Date(now - 60 * 60 * 1000).toISOString(),\n endTime: new Date(now).toISOString(),\n intervalSeconds: 60,\n };\n}\n\n// fetchWidgetHistory returns seed samples for one widget, or [] when it has no\n// device datasource (label/image, an anchor selector, an unbound slot). Never rejects\n// into the render path \u2014 a failed backfill just yields an empty seed and the live stream\n// still fills the widget.\n//\n// It takes the widget with its CONCRETE datasource \u2014 a slot already resolved through the\n// bindings (resolveWidgetDatasource), as the renderer passes it \u2014 so the seed is for\n// exactly the device the widget's live stream is for. A raw slot seeds nothing.\nexport async function fetchWidgetHistory(\n widget: WidgetInstance,\n window: HistoryWindow,\n): Promise<MeasurementSample[]> {\n const ds = widget.datasource;\n if (!ds || ds.kind !== 'device') return [];\n\n try {\n // measurementStream and bucketedMeasurements are keyed by device token (ADR-044),\n // so the token goes straight into the criteria \u2014 no token\u2192id hop.\n const deviceToken = ds.deviceToken;\n\n // Seed each requested measurement (or all, when the widget lists none).\n const names: Array<string | undefined> = ds.measurements.length ? ds.measurements : [undefined];\n const pages = await Promise.all(\n names.map((name) =>\n gql('event-management', BUCKETED_MEASUREMENTS, {\n criteria: {\n deviceToken,\n name,\n startTime: window.startTime,\n endTime: window.endTime,\n intervalSeconds: window.intervalSeconds,\n },\n }).then((r) => r.bucketedMeasurements),\n ),\n );\n\n return pages\n .flat()\n .filter((b) => b.avg != null)\n .map((b) => ({\n id: `${deviceToken}-${b.name}-${b.bucketStart}`,\n deviceToken,\n eventType: 0,\n occurredTime: b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n }))\n .sort((a, b) => (a.occurredTime! < b.occurredTime! ? -1 : 1));\n } catch {\n return [];\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The option-schema vocabulary and the engine that checks an options bag against one.\n//\n// A widget's stored `options` is `Record<string, unknown>`: dashboard-management keeps a\n// definition as opaque JSON and the definition parser does not look inside it. That leaves\n// every option key owned by NOTHING unless something declares it, and this module is the\n// language the declaration is written in. It is React-free and knows no widget type, so a\n// headless validator, the widget SDK and the built-in widget package all read one engine \u2014\n// the built-in table (WIDGET_OPTIONS in @devicechain/widgets) is just one schema among the\n// ones a registry can hold.\n//\n// The schema is JSON-serializable data. Rules that span more than one option are therefore\n// declared as `constraints` (data), never as functions.\n\n// ---- Spec vocabulary --------------------------------------------------------\n\n// `required` marks an option whose ABSENCE leaves the widget with nothing to show.\n// The renderer never throws on a missing option \u2014 an image with no `url` renders \"No\n// image URL\", a command-button with no `commandName` renders \"No command configured\" \u2014\n// which is deliberate graceful degradation for a hand-edited definition, but is exactly\n// the failure a generated dashboard must not ship. So `required` is an AUTHORING\n// completeness contract, not a claim that the renderer rejects the option's absence.\n//\n// Absent here means absent AS THE RENDERER SEES IT, which for a required string includes\n// the EMPTY string: every one of the four required built-in options is read into a\n// truthiness test (`url ? \u2026 : 'No image URL'`, `!commandName`, `!target`), so `''`\n// produces the same placeholder as an omitted key. Treating `''` as present would let a\n// generated dashboard carrying `\"url\": \"\"` validate clean and render nothing.\ninterface OptionSpecBase {\n // What the option does. Short: this feeds the config panel's help text and the\n // published \"available widgets\" page.\n doc: string;\n required?: true;\n // Portable default text for the option's label and help. A host that cannot translate\n // an option it has never heard of falls back to these, so a third-party option is\n // understandable without a host message catalog. Optional so existing schemas stay valid.\n label?: string;\n help?: string;\n // Message ids a host MAY look up in its own catalog before falling back to the text\n // above. Built-ins declare today's console keys here; the host resolves them.\n labelId?: string;\n helpId?: string;\n // Names a SPECIALIZED authoring workflow for this option, chosen by the descriptor and never\n // inferred from the option's key. Absent means the generic field the spec kind implies.\n //\n // OPEN vocabulary, deliberately: any non-empty string registers, and a host that does not\n // implement the named workflow renders the generic field for that option. A closed list\n // here would make every package that names a newer control unregistrable on an older\n // host. Name a package's own controls in a namespace ('acme/valve-curve'); the built-in\n // names below are unprefixed.\n control?: string;\n}\n\n// The specialized workflows the DeviceChain console implements \u2014 what a built-in may name\n// and expect honoured. Not a validation whitelist: a control outside this list is valid and\n// simply falls back. `command-vocabulary` picks a device command and bakes its key, label and\n// parameters together; `slot-target` picks one of the dashboard's slots; `basemap` marks the\n// raster tile source/credit pair.\nexport const HOST_OPTION_CONTROLS = ['command-vocabulary', 'slot-target', 'basemap'] as const;\nexport type HostOptionControl = (typeof HOST_OPTION_CONTROLS)[number];\n\nexport interface StringOptionSpec extends OptionSpecBase {\n kind: 'string';\n // 'json' additionally requires the string to parse as JSON. Used for the command\n // button's baked parameterSchema, where a malformed schema is silently swallowed\n // (parseParameterSchema returns []) and renders as a bare Send button.\n format?: 'json';\n}\n\nexport interface NumberOptionSpec extends OptionSpecBase {\n kind: 'number';\n min?: number;\n max?: number;\n integer?: true;\n}\n\nexport interface BooleanOptionSpec extends OptionSpecBase {\n kind: 'boolean';\n}\n\nexport interface EnumOptionSpec<V extends string = string> extends OptionSpecBase {\n kind: 'enum';\n values: readonly V[];\n // Portable text and optional host message ids for the choices and for the \"unset\" entry,\n // by the same lookup-then-fall-back rule as `label`. Absent, a choice shows its raw value.\n valueLabels?: Readonly<Record<string, string>>;\n valueLabelIds?: Readonly<Record<string, string>>;\n placeholder?: string;\n placeholderId?: string;\n}\n\nexport type OptionSpec = StringOptionSpec | NumberOptionSpec | BooleanOptionSpec | EnumOptionSpec;\n\nexport type WidgetOptionSpecs = Record<string, OptionSpec>;\n\n// A rule spanning more than one option, as DATA. `greater-than` requires the value of\n// `key` to be strictly greater than the value of `than`, and is only judged when both are\n// finite numbers (an absent or mistyped value is reported by its own spec, not twice).\nexport interface GreaterThanConstraint<K extends string = string> {\n kind: 'greater-than';\n key: K;\n than: K;\n}\n\nexport type OptionConstraint<K extends string = string> = GreaterThanConstraint<K>;\n\n// ---- Derived compile-time types ---------------------------------------------\n\n// The value type a spec accepts. Enum first: an EnumOptionSpec's values carry the literal\n// union, which is the whole point of declaring one.\nexport type SpecValue<S> = S extends { kind: 'enum'; values: readonly (infer V)[] }\n ? V extends string\n ? V\n : never\n : S extends { kind: 'number' }\n ? number\n : S extends { kind: 'boolean' }\n ? boolean\n : S extends { kind: 'string' }\n ? string\n : never;\n\nexport type RequiredOptionKeys<S> = {\n [K in keyof S]: S[K] extends { required: true } ? K : never;\n}[keyof S];\n\n// The typed options bag a schema describes: every declared key at its declared type,\n// required ones mandatory.\nexport type OptionsOf<S> = {\n [K in RequiredOptionKeys<S>]: SpecValue<S[K]>;\n} & {\n [K in Exclude<keyof S, RequiredOptionKeys<S>>]?: SpecValue<S[K]>;\n};\n\n// A descriptor's starting values: every declared key at its declared type, but EVERY key\n// optional. A fresh widget is allowed to be authoring-incomplete \u2014 an image has no `url`\n// until the author supplies one \u2014 so `required` cannot bind the defaults.\nexport type DefaultsOf<S> = { [K in keyof S]?: SpecValue<S[K]> };\n\n// The declared keys of a given kind, for typed references from a binding policy. The `infer`\n// makes the test distribute over a union of specs, so the broad WidgetOptionSpecs (every\n// key a union of the four kinds) still names its number keys instead of collapsing to never.\nexport type OptionKeysOfKind<S, Kind extends OptionSpec['kind']> = {\n [K in keyof S & string]: S[K] extends infer X ? (X extends { kind: Kind } ? K : never) : never;\n}[keyof S & string];\n\n// ---- Runtime validation -----------------------------------------------------\n\nexport type OptionIssueCode =\n // a key the renderer for this widget type reads nothing from\n | 'unknown'\n // present, but the wrong JavaScript type \u2014 read back as undefined, rendered as default\n | 'type'\n // a string/number outside the option's declared legal values or bounds\n | 'enum'\n | 'range'\n // a `required` option absent, so the widget renders its \"not configured\" placeholder\n | 'missing'\n // a format:'json' string that does not parse\n | 'json'\n // a rule spanning more than one option (see OptionConstraint)\n | 'invariant';\n\nexport interface OptionIssue {\n // The id of the widget whose schema the bag was checked against (a built-in type or a\n // namespaced extension id). NOT a widget instance id.\n widgetId: string;\n // The offending option key. For a constraint, the key the fix belongs on.\n key: string;\n code: OptionIssueCode;\n message: string;\n}\n\nexport interface ValidateOptionsContext {\n constraints?: readonly OptionConstraint[];\n // Names the widget in messages and on each issue.\n label: string;\n}\n\n// hasOwnProperty.call rather than `in` or Object.hasOwn: every specs table inherits\n// Object.prototype, so `in` reports 'toString', 'constructor' and friends as declared.\n// A stored definition arrives via JSON.parse, which makes '__proto__' an ordinary own key\n// \u2014 a shape a real definition can carry. (And the package targets ES2020.)\nexport function hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\n// validateOptions reports every way an options bag disagrees with its schema. An empty\n// result means every key present is declared, at a value the schema accepts, and no\n// required option is missing.\n//\n// It is deliberately a REPORT rather than a throw: the renderer is tolerant by design and\n// must stay that way for a hand-edited definition, so the strictness belongs to whoever\n// produces definitions (fixtures, generators, the config panel) and to the tests that\n// gate them. Callers decide which codes are fatal \u2014 a stored dashboard carrying an\n// 'unknown' leftover is untidy; a generated one carrying 'missing' is broken.\nexport function validateOptions(\n specs: WidgetOptionSpecs,\n options: Record<string, unknown> | undefined,\n context: ValidateOptionsContext,\n): OptionIssue[] {\n const { label: widgetId } = context;\n const issues: OptionIssue[] = [];\n const bag = options ?? {};\n\n for (const [key, spec] of Object.entries(specs)) {\n const value = bag[key];\n // '' counts as absent for a required option \u2014 see OptionSpecBase.\n if (value === undefined || (spec.required && value === '')) {\n if (spec.required) {\n issues.push({ widgetId, key, code: 'missing', message: `${key} is required but not set` });\n }\n continue;\n }\n issues.push(...checkValue(widgetId, key, spec, value));\n }\n\n for (const key of Object.keys(bag)) {\n if (hasOwn(specs, key)) continue;\n issues.push({\n widgetId,\n key,\n code: 'unknown',\n message: `${key} is not read by a ${widgetId} widget`,\n });\n }\n\n for (const constraint of context.constraints ?? []) {\n const issue = checkConstraint(constraint, bag);\n if (issue) issues.push({ widgetId, ...issue });\n }\n\n return issues;\n}\n\nfunction checkConstraint(\n constraint: OptionConstraint,\n bag: Record<string, unknown>,\n): Pick<OptionIssue, 'key' | 'code' | 'message'> | undefined {\n switch (constraint.kind) {\n case 'greater-than': {\n const value = bag[constraint.key];\n const than = bag[constraint.than];\n if (typeof value !== 'number' || typeof than !== 'number') return undefined;\n if (!Number.isFinite(value) || !Number.isFinite(than)) return undefined;\n if (value > than) return undefined;\n return {\n key: constraint.key,\n code: 'invariant',\n message: `${constraint.key} (${value}) must be greater than ${constraint.than} (${than})`,\n };\n }\n }\n}\n\nfunction checkValue(widgetId: string, key: string, spec: OptionSpec, value: unknown): OptionIssue[] {\n const issue = (code: OptionIssueCode, message: string): OptionIssue[] => [{ widgetId, key, code, message }];\n\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (spec.format === 'json' && !parsesAsJson(value)) {\n return issue('json', `${key} must be valid JSON`);\n }\n return [];\n\n case 'number':\n // Number.isFinite mirrors optNumber: NaN and \u00B1Infinity survive JSON round-trips\n // through some producers and are read back as undefined by the renderer.\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return issue('type', `${key} must be a finite number, got ${describe(value)}`);\n }\n if (spec.integer && !Number.isInteger(value)) {\n return issue('range', `${key} must be a whole number, got ${value}`);\n }\n if (spec.min !== undefined && value < spec.min) {\n return issue('range', `${key} must be at least ${spec.min}, got ${value}`);\n }\n if (spec.max !== undefined && value > spec.max) {\n return issue('range', `${key} must be at most ${spec.max}, got ${value}`);\n }\n return [];\n\n case 'boolean':\n // optBoolean treats anything but literal `true` as false, so a wrong type here is a\n // silently-disabled feature rather than a visible error.\n if (typeof value !== 'boolean') {\n return issue('type', `${key} must be a boolean, got ${describe(value)}`);\n }\n return [];\n\n case 'enum':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (!spec.values.includes(value)) {\n return issue('enum', `${key} must be one of ${spec.values.join(', ')}, got ${JSON.stringify(value)}`);\n }\n return [];\n }\n}\n\nfunction parsesAsJson(value: string): boolean {\n try {\n JSON.parse(value);\n return true;\n } catch {\n return false;\n }\n}\n\n// describe names the offending value's type in a message, quoting a string so an empty\n// one is visible and distinguishing null from an object.\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (typeof value === 'string') return `string ${JSON.stringify(value)}`;\n if (Array.isArray(value)) return 'an array';\n return `${typeof value} ${JSON.stringify(value)}`;\n}\n\n// numberSpec returns the declared bounds for a numeric option, so an authoring UI can\n// constrain its input from the schema rather than from a second, hand-written copy of the\n// same numbers.\n//\n// This exists because the console's numeric inputs were unbounded: a `precision` of\n// -1 is a legal number, reaches Number.prototype.toFixed, and throws a RangeError\n// DURING RENDER. formatValue clamps so the renderer survives, but the definition is\n// still wrong and the author had no way to know. A panel that reads the schema cannot\n// offer the value at all.\nexport function numberSpec(specs: WidgetOptionSpecs, key: string): NumberOptionSpec | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'number' ? spec : undefined;\n}\n\n// clampNumberOption folds a raw number into what a numeric option's schema allows, or\n// returns undefined when it is not a number at all.\n//\n// Separate from the input that calls it because the correction is the part worth\n// testing: an authoring UI's keystroke handling is awkward to exercise, and the\n// question that actually matters \u2014 what value gets STORED for a given input \u2014 is a\n// pure function of the schema and the number.\nexport function clampNumberOption(spec: NumberOptionSpec | undefined, value: number): number | undefined {\n if (!Number.isFinite(value)) return undefined;\n if (!spec) return value;\n let next = spec.integer ? Math.trunc(value) : value;\n if (spec.min !== undefined) next = Math.max(spec.min, next);\n if (spec.max !== undefined) next = Math.min(spec.max, next);\n return next;\n}\n\n// enumValues returns the legal values of an enum option, so an authoring UI can build its\n// dropdown from the schema instead of a private copy of the same list.\n//\n// A dropdown offering a value the schema rejects lets a panel author a definition the\n// validator refuses; one MISSING a value silently removes a filter an author could\n// previously express. Both are invisible without a shared source.\nexport function enumValues(specs: WidgetOptionSpecs, key: string): readonly string[] | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'enum' ? spec.values : undefined;\n}\n\n// applyOptionPatch is the VALIDATED write a registered editor is given: it returns the\n// options bag with `patch` applied, or throws on a write the schema cannot hold.\n//\n// An editor is developer code running in the host page, so a mistake in it should be loud at\n// the moment it is made, not a half-configured widget discovered at publish. The rules:\n// - a key the schema does not declare is refused (an editor cannot grow the stored bag);\n// - a value of the wrong kind, an enum value outside `values`, or a `format: 'json'`\n// string that does not parse, is refused;\n// - a number is folded into its bounds, as the generic field does;\n// - `undefined`, an empty string, and `false` DROP the key (an unset option reads as its\n// default, and an opt-in flag is absent-when-off), as the generic fields do.\n// Keys the patch does not name \u2014 including ones outside the schema that an older release\n// stored \u2014 are carried through untouched.\nexport function applyOptionPatch(\n specs: WidgetOptionSpecs,\n options: Readonly<Record<string, unknown>> | undefined,\n patch: Readonly<Record<string, unknown>>,\n label = 'widget',\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...(options ?? {}) };\n for (const [key, value] of Object.entries(patch)) {\n if (!hasOwn(specs, key)) throw new Error(`${label}: \"${key}\" is not a declared option`);\n const spec = specs[key];\n if (value === undefined || value === '' || (spec.kind === 'boolean' && value === false)) {\n delete next[key];\n continue;\n }\n const refuse = (expected: string): never => {\n throw new TypeError(`${label}: option \"${key}\" takes ${expected}, got ${JSON.stringify(value)}`);\n };\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') refuse('a string');\n // A `format: 'json'` option is judged as the option validator judges it: a string\n // that does not parse is refused here rather than flagged later at publish.\n if (spec.format === 'json' && !parsesAsJson(value as string)) refuse('a JSON string');\n next[key] = value;\n break;\n case 'boolean':\n if (typeof value !== 'boolean') refuse('a boolean');\n next[key] = value;\n break;\n case 'number': {\n if (typeof value !== 'number') refuse('a finite number');\n const clamped = clampNumberOption(spec, value as number);\n if (clamped === undefined) refuse('a finite number');\n next[key] = clamped;\n break;\n }\n case 'enum':\n if (typeof value !== 'string' || !spec.values.includes(value)) {\n refuse(`one of ${spec.values.map((v) => JSON.stringify(v)).join(', ')}`);\n }\n next[key] = value;\n break;\n }\n }\n return next;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The portable half of a widget registration: a plain-data DESCRIPTOR naming what a widget\n// is, which data channel feeds it, what options it owns and how its data is bound. A\n// descriptor holds no React component, callback, import URL, token or executable code \u2014\n// it is JSON-serializable by construction (and checked to be so at runtime, see\n// validateWidgetDescriptor), because the definition parser and the headless validators\n// must be able to read one without loading, or trusting, any widget implementation.\n//\n// The trusted React implementation that goes with a descriptor lives in\n// @devicechain/widget-sdk; this module is the React-free vocabulary both sides share.\n\nimport type {\n AlarmStreamState,\n CommandStreamState,\n LocationStreamState,\n MeasurementStreamState,\n} from './channel-snapshots';\nimport type { DefaultsOf, OptionConstraint, OptionKeysOfKind, WidgetOptionSpecs } from './option-schema';\nimport { WIDGET_TYPES, type WidgetType } from './types';\n\n// ---- Channels ---------------------------------------------------------------\n\n// A widget's data channel: which hub subscription binds it. `selection` and `none` open no\n// data channel at all \u2014 a selection widget drives a slot from the ambient candidate\n// provider, and a `none` widget is static.\nexport type WidgetChannel = 'measurement' | 'alarm' | 'control' | 'selection' | 'location' | 'none';\n\n// The resolved data a component on channel C receives. `null` marks the empty channel.\nexport type ChannelSnapshot<C extends WidgetChannel> = C extends 'measurement'\n ? MeasurementStreamState\n : C extends 'alarm'\n ? AlarmStreamState\n : C extends 'control'\n ? CommandStreamState\n : C extends 'location'\n ? LocationStreamState\n : null;\n\n// ---- Ids --------------------------------------------------------------------\n\nexport type BuiltinWidgetId = WidgetType;\n// `scope/name`, e.g. 'acme/tank-level'. Namespace ownership is an operator convention, not\n// proof of publisher identity.\nexport type NamespacedWidgetId = `${string}/${string}`;\nexport type WidgetId = BuiltinWidgetId | NamespacedWidgetId;\n\nconst BUILTIN_IDS: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\nexport function isBuiltinWidgetId(id: unknown): id is BuiltinWidgetId {\n return typeof id === 'string' && BUILTIN_IDS.has(id);\n}\n\nconst NAMESPACED_ID = /^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9-]*$/;\n\nexport function isNamespacedWidgetId(id: unknown): id is NamespacedWidgetId {\n return typeof id === 'string' && NAMESPACED_ID.test(id);\n}\n\n// ---- Datasource -------------------------------------------------------------\n\nexport type DatasourceSelectorKind = 'device' | 'anchor' | 'slot';\n\n// Whether a widget binds a datasource, and which selector kinds its author may pick.\n// `none`/`selection` widgets must be `{mode:'none'}`; a control widget's command target\n// keeps its device-only requirement, so it offers only device and slot.\nexport type DatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly DatasourceSelectorKind[] };\n\ntype ControlDatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly ('device' | 'slot')[] };\n\nexport type DatasourcePolicyFor<C extends WidgetChannel> = C extends 'none' | 'selection'\n ? { mode: 'none' }\n : C extends 'control'\n ? ControlDatasourcePolicy\n : DatasourcePolicy;\n\n// ---- Binding policy ---------------------------------------------------------\n\n// A constant, or a reference to a declared option key. References are by NAME and checked\n// against the option's kind \u2014 the descriptor never carries a function or a query string.\nexport interface ConstBinding<T> {\n const: T;\n}\n\nexport interface NumberOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'number'>;\n default?: number;\n min?: number;\n}\n\nexport interface StringOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'string' | 'enum'>;\n default?: string;\n}\n\n// The alarm acknowledged filter is the STRING 'true'/'false' (see the built-in\n// `acknowledged` option), so it references an enum option whose legal values are a subset of\n// those two. The kind is checked at compile time; the value set at registration.\nexport interface AckOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'enum'>;\n default?: 'true' | 'false';\n}\n\ntype PageSize<S extends WidgetOptionSpecs> = ConstBinding<number> | NumberOptionRef<S>;\n\nexport type ChannelBindingPolicy<C extends WidgetChannel, S extends WidgetOptionSpecs = WidgetOptionSpecs> =\n C extends 'measurement'\n ? { window?: NumberOptionRef<S> }\n : C extends 'alarm'\n ? {\n pageSize: PageSize<S>;\n state?: StringOptionRef<S>;\n severity?: StringOptionRef<S>;\n acknowledged?: AckOptionRef<S>;\n }\n : C extends 'control'\n ? { pageSize: PageSize<S> }\n : Record<string, never>;\n\n// ---- Interactions -----------------------------------------------------------\n\n// The host callbacks a widget asks for. Declaring one is a request, intersected with what\n// the host supports and the viewer is allowed \u2014 not a security boundary.\nexport interface WidgetInteractionDeclaration {\n alarmActions?: true;\n commandSend?: true;\n selection?: true;\n candidates?: true;\n}\n\n// `alarmActions` only makes sense on the alarm channel and `commandSend` only on control.\n// `'alarm' extends C` (rather than `C extends 'alarm'`) keeps the broad, unparameterized\n// descriptor able to name every key.\nexport type InteractionsFor<C extends WidgetChannel> = { selection?: true; candidates?: true } & ('alarm' extends C\n ? { alarmActions?: true }\n : unknown) &\n ('control' extends C ? { commandSend?: true } : unknown);\n\n// ---- Display ----------------------------------------------------------------\n\nexport interface WidgetDisplay {\n title: string;\n description?: string;\n category?: string;\n // Message ids a host may resolve in its own catalog before falling back to the text above.\n titleId?: string;\n categoryId?: string;\n}\n\n// ---- The descriptor ---------------------------------------------------------\n\nexport const SDK_API_VERSION = 1;\n\nexport interface WidgetDescriptor<\n C extends WidgetChannel = WidgetChannel,\n S extends WidgetOptionSpecs = WidgetOptionSpecs,\n> {\n id: WidgetId;\n // Strict MAJOR.MINOR.PATCH of this widget's release. Provenance, not a compatibility\n // key: a differing version still renders.\n version: string;\n // The contract generation, independent of platform and widget versions.\n sdkApiVersion: 1;\n // The persisted settings shape. Bump when stored options stop meaning the same thing.\n optionsVersion: number;\n display: WidgetDisplay;\n channel: C;\n datasource: DatasourcePolicyFor<C>;\n binding: ChannelBindingPolicy<C, S>;\n // The option schema this widget owns.\n options: S;\n // Rules spanning more than one option, as data.\n constraints?: readonly OptionConstraint<OptionKeysOfKind<S, 'number'>>[];\n // Starting values for a freshly added widget. Every key optional \u2014 see DefaultsOf.\n defaults: DefaultsOf<S>;\n interactions: InteractionsFor<C>;\n}\n\n// defineWidgetDescriptor is an identity that exists for inference: it infers the channel\n// and the schema from the literal, so a binding reference, a constraint or a default that\n// disagrees with the schema is a compile error. The registry-facing helper for a full\n// widget (descriptor + component) is `defineWidget` in @devicechain/widget-sdk.\nexport function defineWidgetDescriptor<const C extends WidgetChannel, const S extends WidgetOptionSpecs>(\n descriptor: WidgetDescriptor<C, S>,\n): WidgetDescriptor<C, S> {\n return descriptor;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The React-free catalog of widget descriptors: what the definition parser and the\n// headless validators see of a widget registry. Constructing one validates EVERY\n// descriptor and the set as a whole, reports every problem together, and returns a deeply\n// frozen, independent instance \u2014 no module state, no global registration, so two hosts on\n// one page can hold two catalogs that do not affect each other.\n\nimport { validateOptions, hasOwn, type OptionIssue, type WidgetOptionSpecs } from './option-schema';\nimport {\n SDK_API_VERSION,\n isBuiltinWidgetId,\n isNamespacedWidgetId,\n type WidgetChannel,\n type WidgetDescriptor,\n} from './widget-descriptor';\n\nexport interface DescriptorIssue {\n // The widget id the issue is about, or `#<index>` when the descriptor has no usable id.\n id: string;\n // Where in the descriptor, e.g. `defaults.max` or `binding.window.option`. Empty for an\n // issue about the descriptor as a whole.\n path: string;\n message: string;\n}\n\nexport interface WidgetCatalog {\n has(id: string): boolean;\n get(id: string): WidgetDescriptor | undefined;\n // Descriptors in the order they were supplied.\n list(): readonly WidgetDescriptor[];\n}\n\nexport class WidgetCatalogError extends Error {\n readonly issues: readonly DescriptorIssue[];\n\n constructor(issues: readonly DescriptorIssue[], subject = 'widget catalog') {\n super(describeIssues(subject, issues));\n this.name = 'WidgetCatalogError';\n this.issues = Object.freeze([...issues]);\n }\n}\n\nexport function describeIssues(subject: string, issues: readonly DescriptorIssue[]): string {\n const lines = issues.map((i) => ` - ${i.id}${i.path ? `.${i.path}` : ''}: ${i.message}`);\n return `invalid ${subject} (${issues.length} problem${issues.length === 1 ? '' : 's'}):\\n${lines.join('\\n')}`;\n}\n\n// ---- Descriptor validation --------------------------------------------------\n\nconst CHANNELS: readonly WidgetChannel[] = ['measurement', 'alarm', 'control', 'selection', 'location', 'none'];\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\nconst SELECTOR_KINDS = ['device', 'anchor', 'slot'];\n\n// The keys each channel's binding policy may carry, mirroring ChannelBindingPolicy.\nconst BINDING_KEYS: Record<WidgetChannel, readonly string[]> = {\n measurement: ['window'],\n alarm: ['pageSize', 'state', 'severity', 'acknowledged'],\n control: ['pageSize'],\n location: [],\n selection: [],\n none: [],\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!isRecord(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nconst join = (base: string, key: string): string => (base ? `${base}.${key}` : key);\n\n// validateWidgetDescriptor checks one descriptor at runtime and returns every problem it\n// finds \u2014 the same invariants the types express, because JSON from a package manifest or a\n// JS caller bypasses the compiler. Accepts `unknown` for that reason.\nexport function validateWidgetDescriptor(descriptor: unknown): DescriptorIssue[] {\n const rawId = isRecord(descriptor) && typeof descriptor.id === 'string' ? descriptor.id : '';\n return collectDescriptorIssues(descriptor, rawId || '#?');\n}\n\nfunction collectDescriptorIssues(d: unknown, id: string): DescriptorIssue[] {\n const issues: DescriptorIssue[] = [];\n const add = (path: string, message: string): void => {\n issues.push({ id, path, message });\n };\n\n if (!isPlainObject(d)) {\n add('', 'a descriptor must be a plain object');\n return issues;\n }\n\n jsonOnly(d, '', add, new Set());\n\n // id \u2014 shape and the reserved rule (a trusted-code convention, not an enforcement).\n if (typeof d.id !== 'string' || d.id === '') {\n add('id', 'id must be a non-empty string');\n } else if (!isBuiltinWidgetId(d.id) && !isNamespacedWidgetId(d.id)) {\n add('id', `id ${JSON.stringify(d.id)} must be a reserved built-in id or a namespaced \"scope/name\" id`);\n }\n\n if (typeof d.version !== 'string' || !SEMVER.test(d.version)) {\n add('version', `version must be MAJOR.MINOR.PATCH, got ${JSON.stringify(d.version)}`);\n }\n if (d.sdkApiVersion !== SDK_API_VERSION) {\n add('sdkApiVersion', `unsupported sdkApiVersion ${JSON.stringify(d.sdkApiVersion)}; this build supports ${SDK_API_VERSION}`);\n }\n if (typeof d.optionsVersion !== 'number' || !Number.isInteger(d.optionsVersion) || d.optionsVersion < 1) {\n add('optionsVersion', `optionsVersion must be a positive integer, got ${JSON.stringify(d.optionsVersion)}`);\n }\n\n if (!isRecord(d.display)) {\n add('display', 'display must be an object');\n } else {\n if (typeof d.display.title !== 'string' || d.display.title === '') {\n add('display.title', 'display.title must be a non-empty string');\n }\n // A host renders these as text and groups its add menu by category, so a non-string\n // here is a crash (or an \"[object Object]\" heading) in someone else's console.\n for (const text of ['description', 'category', 'titleId', 'categoryId'] as const) {\n const value = d.display[text];\n if (value !== undefined && typeof value !== 'string') add(`display.${text}`, `display.${text} must be a string`);\n }\n }\n\n const channel = CHANNELS.find((c) => c === d.channel);\n if (!channel) add('channel', `channel must be one of ${CHANNELS.join(', ')}, got ${JSON.stringify(d.channel)}`);\n\n const specs = checkOptions(d.options, add);\n const constraints = checkConstraints(d.constraints, specs, add);\n\n if (specs) checkDefaults(d.defaults, specs, constraints, typeof d.id === 'string' ? d.id : id, add);\n else if (!isRecord(d.defaults)) add('defaults', 'defaults must be an object');\n\n checkDatasource(d.datasource, channel, add);\n if (channel) checkBinding(d.binding, channel, specs, add);\n checkInteractions(d.interactions, channel, add);\n\n return issues;\n}\n\ntype Add = (path: string, message: string) => void;\n\n// Descriptor values must survive JSON.stringify unchanged: no function, symbol, bigint,\n// undefined, NaN/Infinity or non-plain object (a class instance, Date, Map\u2026).\nfunction jsonOnly(value: unknown, path: string, add: Add, ancestors: Set<object>): void {\n const here = path || '(descriptor)';\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return;\n case 'number':\n if (!Number.isFinite(value)) add(path, `${here} is ${value}; descriptors hold JSON values only`);\n return;\n case 'undefined':\n add(path, `${here} is undefined; omit the key instead (descriptors hold JSON values only)`);\n return;\n case 'function':\n case 'symbol':\n case 'bigint':\n add(path, `${here} is a ${typeof value}; descriptors hold JSON values only`);\n return;\n }\n if (value === null) return;\n const obj = value as object;\n if (ancestors.has(obj)) {\n add(path, `${here} is a circular reference; descriptors hold JSON values only`);\n return;\n }\n ancestors.add(obj);\n if (Array.isArray(obj)) {\n obj.forEach((item, i) => jsonOnly(item, `${path}[${i}]`, add, ancestors));\n } else if (!isPlainObject(obj)) {\n add(path, `${here} is not a plain object; descriptors hold JSON values only`);\n } else {\n if (Object.getOwnPropertySymbols(obj).length > 0) add(path, `${here} has a symbol key; descriptors hold JSON values only`);\n for (const [key, item] of Object.entries(obj)) jsonOnly(item, join(path, key), add, ancestors);\n }\n ancestors.delete(obj);\n}\n\n// Returns the schema when it is structurally sound enough to check references and defaults\n// against, else undefined (the faults are already reported).\nfunction checkOptions(options: unknown, add: Add): WidgetOptionSpecs | undefined {\n if (!isRecord(options)) {\n add('options', 'options must be an object mapping option keys to specs');\n return undefined;\n }\n let sound = true;\n for (const [key, spec] of Object.entries(options)) {\n const at = `options.${key}`;\n if (!isRecord(spec)) {\n add(at, 'an option spec must be an object');\n sound = false;\n continue;\n }\n if (typeof spec.doc !== 'string') {\n add(`${at}.doc`, 'doc must be a string');\n }\n for (const text of ['label', 'help', 'labelId', 'helpId', 'placeholder', 'placeholderId'] as const) {\n if (spec[text] !== undefined && typeof spec[text] !== 'string') add(`${at}.${text}`, `${text} must be a string`);\n }\n // Any non-empty string: a control this host does not implement is a fallback to the\n // generic field, not a fault (see OptionSpecBase.control).\n if (spec.control !== undefined && (typeof spec.control !== 'string' || spec.control === '')) {\n add(`${at}.control`, `control must be a non-empty string, got ${JSON.stringify(spec.control)}`);\n }\n switch (spec.kind) {\n case 'string':\n case 'boolean':\n break;\n case 'number':\n for (const bound of ['min', 'max'] as const) {\n if (spec[bound] !== undefined && typeof spec[bound] !== 'number') add(`${at}.${bound}`, `${bound} must be a number`);\n }\n break;\n case 'enum':\n if (!Array.isArray(spec.values) || spec.values.length === 0 || !spec.values.every((v) => typeof v === 'string')) {\n add(`${at}.values`, 'an enum option needs a non-empty list of string values');\n sound = false;\n }\n for (const table of ['valueLabels', 'valueLabelIds'] as const) {\n const labels = spec[table];\n if (labels === undefined) continue;\n if (!isRecord(labels)) {\n add(`${at}.${table}`, `${table} must be an object mapping enum values to strings`);\n continue;\n }\n for (const [value, label] of Object.entries(labels)) {\n if (typeof label !== 'string') add(`${at}.${table}.${value}`, `${table} entries must be strings`);\n else if (Array.isArray(spec.values) && !spec.values.includes(value)) {\n add(`${at}.${table}.${value}`, `${JSON.stringify(value)} is not one of this option's values`);\n }\n }\n }\n break;\n default:\n add(`${at}.kind`, `kind must be string, number, boolean or enum, got ${JSON.stringify(spec.kind)}`);\n sound = false;\n }\n }\n return sound ? (options as unknown as WidgetOptionSpecs) : undefined;\n}\n\nfunction checkConstraints(\n constraints: unknown,\n specs: WidgetOptionSpecs | undefined,\n add: Add,\n): { kind: 'greater-than'; key: string; than: string }[] {\n if (constraints === undefined) return [];\n if (!Array.isArray(constraints)) {\n add('constraints', 'constraints must be a list');\n return [];\n }\n const valid: { kind: 'greater-than'; key: string; than: string }[] = [];\n constraints.forEach((c, i) => {\n const at = `constraints[${i}]`;\n if (!isRecord(c) || c.kind !== 'greater-than') {\n add(`${at}.kind`, `constraint kind must be \"greater-than\", got ${JSON.stringify(isRecord(c) ? c.kind : c)}`);\n return;\n }\n let ok = true;\n for (const field of ['key', 'than'] as const) {\n const name = c[field];\n if (typeof name !== 'string') {\n add(`${at}.${field}`, `${field} must name a declared option`);\n ok = false;\n } else if (specs && (!hasOwn(specs, name) || specs[name].kind !== 'number')) {\n add(`${at}.${field}`, `${field} ${JSON.stringify(name)} must name a declared number option`);\n ok = false;\n }\n }\n if (ok) valid.push({ kind: 'greater-than', key: c.key as string, than: c.than as string });\n });\n return valid;\n}\n\nfunction checkDefaults(\n defaults: unknown,\n specs: WidgetOptionSpecs,\n constraints: { kind: 'greater-than'; key: string; than: string }[],\n id: string,\n add: Add,\n): void {\n if (!isRecord(defaults)) {\n add('defaults', 'defaults must be an object');\n return;\n }\n // 'missing' is not a defaults fault: a fresh widget may be authoring-incomplete (an image\n // has no url until the author gives one), so `required` binds a SAVED widget, not the\n // starting point.\n const found: OptionIssue[] = validateOptions(specs, defaults, { constraints, label: id }).filter(\n (issue) => issue.code !== 'missing',\n );\n for (const issue of found) add(`defaults.${issue.key}`, issue.message);\n}\n\nfunction checkDatasource(datasource: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(datasource)) {\n add('datasource', 'datasource must be an object');\n return;\n }\n const mode = datasource.mode;\n if (mode !== 'none' && mode !== 'optional' && mode !== 'required') {\n add('datasource.mode', `mode must be none, optional or required, got ${JSON.stringify(mode)}`);\n return;\n }\n if ((channel === 'none' || channel === 'selection') && mode !== 'none') {\n add('datasource.mode', `a ${channel}-channel widget opens no data channel, so its datasource mode must be \"none\"`);\n }\n if (mode === 'none') {\n if (datasource.selectors !== undefined) add('datasource.selectors', 'a datasource mode of \"none\" takes no selectors');\n return;\n }\n const selectors = datasource.selectors;\n if (!Array.isArray(selectors) || selectors.length === 0) {\n add('datasource.selectors', `a datasource mode of \"${mode}\" needs a non-empty list of selector kinds`);\n return;\n }\n selectors.forEach((s, i) => {\n if (typeof s !== 'string' || !SELECTOR_KINDS.includes(s)) {\n add(`datasource.selectors[${i}]`, `selector must be one of ${SELECTOR_KINDS.join(', ')}, got ${JSON.stringify(s)}`);\n } else if (channel === 'control' && s !== 'device' && s !== 'slot') {\n add(`datasource.selectors[${i}]`, 'a command target must be a device, so a control widget accepts only device and slot selectors');\n }\n });\n}\n\nfunction checkBinding(binding: unknown, channel: WidgetChannel, specs: WidgetOptionSpecs | undefined, add: Add): void {\n if (!isRecord(binding)) {\n add('binding', 'binding must be an object');\n return;\n }\n const allowed = BINDING_KEYS[channel];\n for (const key of Object.keys(binding)) {\n if (!allowed.includes(key)) add(`binding.${key}`, `a ${channel}-channel widget has no binding policy \"${key}\"`);\n }\n if (allowed.includes('pageSize') && binding.pageSize === undefined) {\n add('binding.pageSize', `a ${channel}-channel widget must declare binding.pageSize`);\n }\n\n const ref = (key: string, accept: (spec: WidgetOptionSpecs[string]) => boolean, expect: string, allowConst: boolean): void => {\n const policy = binding[key];\n if (policy === undefined) return;\n const at = `binding.${key}`;\n if (!isRecord(policy)) {\n add(at, `${key} must be an object`);\n return;\n }\n const hasConst = policy.const !== undefined;\n const hasOption = policy.option !== undefined;\n if (hasConst) {\n if (!allowConst) add(`${at}.const`, `${key} takes an option reference, not a constant`);\n else if (typeof policy.const !== 'number' || !Number.isInteger(policy.const) || policy.const < 1) {\n add(`${at}.const`, `const must be a positive integer, got ${JSON.stringify(policy.const)}`);\n }\n if (hasOption) add(at, `${key} must be a constant or an option reference, not both`);\n return;\n }\n if (!hasOption) {\n add(at, `${key} must be a constant or an option reference`);\n return;\n }\n const name = policy.option;\n if (typeof name !== 'string') {\n add(`${at}.option`, 'option must name a declared option key');\n } else if (specs) {\n if (!hasOwn(specs, name)) add(`${at}.option`, `option ${JSON.stringify(name)} is not a declared option key`);\n else if (!accept(specs[name])) add(`${at}.option`, `option ${JSON.stringify(name)} must be ${expect}`);\n }\n if (policy.min !== undefined && typeof policy.min !== 'number') add(`${at}.min`, 'min must be a number');\n if (policy.default !== undefined) {\n const wantsNumber = key === 'window' || key === 'pageSize';\n if (typeof policy.default !== (wantsNumber ? 'number' : 'string')) {\n add(`${at}.default`, `default must be a ${wantsNumber ? 'number' : 'string'}`);\n }\n }\n };\n\n const isNumber = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'number';\n const isText = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'string' || s.kind === 'enum';\n const isBoolEnum = (s: WidgetOptionSpecs[string]): boolean =>\n s.kind === 'enum' && s.values.every((v) => v === 'true' || v === 'false');\n\n ref('window', isNumber, 'a number option', false);\n ref('pageSize', isNumber, 'a number option', true);\n ref('state', isText, 'a string or enum option', false);\n ref('severity', isText, 'a string or enum option', false);\n ref('acknowledged', isBoolEnum, 'an enum option whose values are only \"true\" and \"false\"', false);\n}\n\nfunction checkInteractions(interactions: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(interactions)) {\n add('interactions', 'interactions must be an object');\n return;\n }\n const only: Record<string, WidgetChannel> = { alarmActions: 'alarm', commandSend: 'control' };\n for (const [key, value] of Object.entries(interactions)) {\n const at = `interactions.${key}`;\n if (key !== 'alarmActions' && key !== 'commandSend' && key !== 'selection' && key !== 'candidates') {\n add(at, `unknown interaction \"${key}\"`);\n } else if (value !== true) {\n add(at, `${key} must be true when declared (omit it otherwise)`);\n } else if (channel && only[key] && only[key] !== channel) {\n add(at, `${key} is only available to ${only[key]}-channel widgets, not ${channel}`);\n }\n }\n}\n\n// ---- Catalog ----------------------------------------------------------------\n\n// Freezes in place, recursively. Idempotent, so a table shared between registries (the\n// built-in option specs are) is simply frozen again. Descriptors hold JSON values only, so\n// this never meets a function or a cycle.\n//\n// It descends even into an object that is ALREADY frozen: Object.freeze is shallow, so a\n// caller's `Object.freeze(descriptor)` says nothing about `descriptor.defaults`, and\n// stopping at the first frozen object would leave those children mutable.\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n for (const child of Object.values(value)) deepFreeze(child);\n }\n return value;\n}\n\n// createWidgetCatalog validates every descriptor and the set (duplicate ids), then throws\n// ONE WidgetCatalogError carrying every issue, or returns a sealed catalog.\nexport function createWidgetCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const issues = collectCatalogIssues(descriptors);\n if (issues.length > 0) throw new WidgetCatalogError(issues);\n return sealCatalog(descriptors);\n}\n\n// The issues for a set of descriptors: each descriptor's own, plus duplicate ids. A pure\n// report \u2014 it cannot produce a catalog. Exposed so the widget registry can report descriptor\n// and implementation faults together before it builds anything.\nexport function collectCatalogIssues(descriptors: readonly unknown[]): DescriptorIssue[] {\n if (!Array.isArray(descriptors)) {\n return [{ id: '#?', path: '', message: 'descriptors must be an array' }];\n }\n const issues: DescriptorIssue[] = [];\n const seen = new Map<string, number>();\n descriptors.forEach((d, index) => {\n const id = isRecord(d) && typeof d.id === 'string' && d.id !== '' ? d.id : `#${index}`;\n issues.push(...collectDescriptorIssues(d, id));\n if (id.startsWith('#')) return;\n const first = seen.get(id);\n if (first === undefined) seen.set(id, index);\n else issues.push({ id, path: 'id', message: `duplicate widget id (first registered at position ${first}, again at ${index})` });\n });\n return issues;\n}\n\n// Builds the sealed catalog from descriptors ALREADY validated by collectCatalogIssues.\n// Module-private on purpose: the only way to obtain a catalog is createWidgetCatalog, which\n// validates first, so the published contract has no unvalidated path to a catalog.\nfunction sealCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const byId = new Map<string, WidgetDescriptor>();\n for (const d of descriptors) byId.set(d.id, deepFreeze(d));\n const ordered: readonly WidgetDescriptor[] = Object.freeze([...byId.values()]);\n return Object.freeze({\n has: (id: string) => byId.has(id),\n get: (id: string) => byId.get(id),\n list: () => ordered,\n });\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["isRecord", "ownGet", "sameBinding", "rows", "gql", "gql", "gql", "gql", "gql", "gql", "SEMVER", "isRecord"]
7
7
  }