@devicechain/dashboards 0.14.0-0

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.
@@ -0,0 +1,7 @@
1
+ {
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 message: string | 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 message\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' | 'message'\n >\n> = [\n { severity: 'CRITICAL', state: 'ACTIVE', acknowledged: false, alarmKey: 'over-temperature', metricKey: 'temperature', lastValue: 87.4, originatorToken: 'thermostat-01', message: 'Temperature above 85\u00B0C' },\n { severity: 'MAJOR', state: 'ACTIVE', acknowledged: true, alarmKey: 'low-battery', metricKey: 'battery', lastValue: 12, originatorToken: 'sensor-14', message: 'Battery below 15%' },\n { severity: 'MINOR', state: 'ACTIVE', acknowledged: false, alarmKey: 'humidity-high', metricKey: 'humidity', lastValue: 78, originatorToken: 'sensor-03', message: 'Relative humidity above threshold' },\n { severity: 'WARNING', state: 'CLEARED', acknowledged: true, alarmKey: 'signal-weak', metricKey: 'rssi', lastValue: -89, originatorToken: 'gateway-02', message: 'Weak uplink signal' },\n { severity: 'INDETERMINATE', state: 'ACTIVE', acknowledged: false, alarmKey: 'self-test', metricKey: 'status', lastValue: null, originatorToken: 'device-99', message: null },\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 message: a.message,\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"]
7
+ }