@devicechain/dashboards 0.27.0-rc.1 → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +44 -17
- package/dist/index.js.map +2 -2
- package/dist/internal/alarm-doc.d.ts +2 -2
- package/dist/queries.d.ts +1 -0
- package/dist/types.d.ts +1 -0
- package/package.json +3 -3
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/command-status.ts", "../src/types.ts", "../src/grid.ts", "../src/is-record.ts", "../src/legacy-grid.ts", "../src/legacy-window.ts", "../src/time-range.ts", "../src/widget-compat.ts", "../src/definition.ts", "../src/bindings.ts", "../src/candidates.ts", "../src/context.ts", "../src/viewer-bindings.ts", "../src/deep-link.ts", "../src/slots.ts", "../src/entity-lister.ts", "../src/queries.ts", "../src/same-label.ts", "../src/connection.ts", "../src/event-window.ts", "../src/hub.ts", "../src/internal/alarm-doc.ts", "../src/internal/command-doc.ts", "../src/internal/location-doc.ts", "../src/internal/presence-doc.ts", "../src/members.ts", "../src/internal/measurement-doc.ts", "../src/synthetic.ts", "../src/recording.ts", "../src/recorded.ts", "../src/widget-descriptor.ts", "../src/layout-lint.ts", "../src/editor-model.ts", "../src/editor-history.ts", "../src/editor-clipboard.ts", "../src/resolver.ts", "../src/history.ts", "../src/option-schema.ts", "../src/widget-catalog.ts", "../src/reflow.ts"],
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The command-delivery lifecycle vocabulary, as the frontend sees it.\n//\n// \uD83D\uDD34 THIS IS THE ONE COPY. It lives in `dashboards` rather than `widgets` because\n// `widgets` depends on `dashboards` (never the reverse), so this is the lowest layer\n// both the widget package and the console can reach. It previously existed as three\n// hand-maintained sets \u2014 the console's DeviceCommandsPanel, the widgets' command-status\n// helper, and an ad-hoc expression in the synthetic preview \u2014 which is exactly how the\n// console kept offering a Cancel button on an already-cancelled command: a status was\n// added to the service and only some of the copies learned about it.\n//\n// The service declares no GraphQL enum for status, so it crosses the wire as a plain\n// string and an UNRECOGNIZED value must stay survivable: nothing here throws on one. What\n// an unknown status MEANS differs per question, though \u2014 see the two predicates at the\n// bottom, which answer for it in opposite directions on purpose.\n//\n// \uD83D\uDD34 \"IN FLIGHT\" AND \"CANCELLABLE\" ARE NOT THE SAME SET, and reading one off the other is\n// the bug this file is shaped to prevent. SENT is in flight (the device has not answered\n// yet) but NOT cancellable: the command is already at the device, and letting the platform\n// call it off would only make it discard the real answer that is on its way back.\n\n// Every lifecycle state the command-delivery service can persist. Ordered as a command\n// travels: the non-terminal states first, then the terminal ones.\nexport const COMMAND_STATUSES = [\n // \u2500\u2500 Non-terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Accepted; awaiting its first dispatch decision. Genuinely transient.\n 'QUEUED',\n // The platform is deliberately WITHHOLDING dispatch because the device is known\n // absent. This is where an offline fleet's backlog accumulates \u2014 it can sit for days,\n // and it is the honest answer to \"why hasn't my command arrived?\".\n 'HELD',\n // Dispatched toward a device believed reachable; awaiting its response.\n 'SENT',\n // Published, and it went NOWHERE: the device turned out not to be reachable, so the\n // transport had nothing to hand it to. The platform still holds the command and will\n // deliver it when the device next wakes. Distinct from HELD (where dispatch was never\n // attempted, because the device was already known absent) and from TIMEOUT (where the\n // command reached a device that then said nothing).\n 'PARKED',\n // \u2500\u2500 Terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // The device answered.\n 'SUCCESSFUL',\n 'FAILED',\n // Dispatched, never answered in time.\n 'TIMEOUT',\n // The TTL elapsed before it ever went out.\n 'EXPIRED',\n // An operator or tenant called it off. Distinct from EXPIRED because it is a\n // different ACTOR, not a different outcome. Cancellation used to write EXPIRED, so\n // BOTH values appear in real data \u2014 historical rows are not backfilled.\n 'CANCELLED',\n] as const;\n\nexport type CommandStatus = (typeof COMMAND_STATUSES)[number];\n\n// The states a command can never leave: no further transition is permitted, so nothing\n// more will happen to it. Terminal implies not cancellable; the converse does NOT hold.\nexport const TERMINAL_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'SUCCESSFUL',\n 'FAILED',\n 'TIMEOUT',\n 'EXPIRED',\n 'CANCELLED',\n]);\n\n// The states from which the platform will still accept a cancellation \u2014 a POSITIVE list,\n// mirroring the service's own gate. All three are states in which the command has not\n// reached the device: QUEUED (not yet dispatched), HELD (dispatch withheld), PARKED\n// (published into a void and waiting for the device to wake). Cancelling any of them\n// takes back something nobody else is holding.\n//\n// SENT is deliberately absent. It is in flight, so !isTerminalCommandStatus('SENT') is\n// true \u2014 which is exactly why a cancel control must not be gated on that expression.\nexport const CANCELLABLE_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'QUEUED',\n 'HELD',\n 'PARKED',\n]);\n\n// \uD83D\uDD34 THE TWO PREDICATES ANSWER FOR AN UNRECOGNIZED STATUS IN OPPOSITE DIRECTIONS, on\n// purpose. Status crosses the wire as a plain string with no GraphQL enum behind it, so a\n// value this build has never heard of is always reachable \u2014 a newer service, a hand-edited\n// row \u2014 and each question has its own safe side:\n//\n// isTerminalCommandStatus negative membership \u21D2 unknown reads NON-terminal, i.e.\n// STILL IN FLIGHT. A status the service adds is one it added\n// because something is still happening to the command, and\n// the cost of guessing wrong is only a row that lingers in an\n// \"outstanding\" list instead of settling.\n//\n// isCancellableCommandStatus positive membership \u21D2 unknown reads NOT CANCELLABLE. This\n// file used to argue the other way \u2014 that refusing to offer\n// cancel for a state we do not recognize would strand a live\n// command \u2014 and that argument is now false. The service gates\n// cancellation on the same positive list, and \uD83D\uDD34 OUTSIDE THAT\n// LIST IT DOES NOT REFUSE: it SUCCEEDS and returns the command\n// UNCHANGED. So a Cancel button offered on an unknown status is\n// a no-op that answers the click with a SUCCESS TOAST for a\n// command nobody cancelled \u2014 a worse outcome than an error,\n// because the operator has no way to tell. The command is\n// stranded either way; this way we do not also claim otherwise.\n// A host that offers the button anyway must therefore check the\n// status that CAME BACK, not the fact that the call resolved.\nexport function isTerminalCommandStatus(status: string): boolean {\n return TERMINAL_COMMAND_STATUSES.has(status);\n}\n\n// isCancellableCommandStatus reports whether a cancel request would be accepted. Gate every\n// cancel control on THIS, never on !isTerminalCommandStatus \u2014 the two differ by SENT, and\n// by every status added after this build shipped.\nexport function isCancellableCommandStatus(status: string): boolean {\n return CANCELLABLE_COMMAND_STATUSES.has(status);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The dashboard-definition contract (ADR-039). These types are the canonical\n// shape of the JSON document that dashboard-management stores opaquely \u2014 the\n// backend only validates it is well-formed JSON, so this package owns the shape.\n// Kept deliberately fluid pre-GA. See research/dashboard-phase1-design-2026-07.md.\n\n// ---- Datasource selectors ---------------------------------------------------\n//\n// A selector is a tagged union (discriminated on `kind`). The Hub resolves\n// `device` and `anchor`, and treats `unbound` as zero devices. A `slot` never reaches\n// it: the renderer resolves each slot through the settled bindings first (to a device,\n// an anchor, or `unbound`). The remaining kinds are reserved \u2014 present so definitions\n// stay forward-compatible, but the Hub rejects them until implemented.\n\n// ---- Location series selection (ADR-078 decision 9) -------------------------\n//\n// A selector's `measurements: string[]` names NAMED SCALAR SERIES, and a position is\n// not one of those: it is an atomic triple on its own event class and its own\n// hypertable, deliberately never a metric. Naming a location through `measurements`\n// would therefore be a lie the whole way down \u2014 the hub would resolve it against the\n// measurement stream, which carries no coordinates at all.\n//\n// So a selector names a location series in its own field. The vocabulary is CLOSED\n// rather than free text: a device has exactly one position track, so there is nothing\n// to disambiguate by name, and an open string would invite an author to type a\n// measurement name here and get silence.\n//\n// 'latest' \u2014 the last-known position, read from device-state's O(1) projection.\n//\n// A second member ('track', the position history from event-management) is the shape\n// this exists to leave room for; the map widget reads 'latest' only.\nexport type LocationSeries = 'latest';\n\n// The location series a selector names. An OBJECT rather than a bare string so a\n// future member can carry its own parameters (a track's window) without changing\n// every selector again.\nexport interface LocationSelection {\n series: LocationSeries;\n}\n\n// device \u2014 one device's measurements (latest-card, gauge, single-series chart), and/or\n// its position when the selector names a location series.\nexport interface DeviceSelector {\n kind: 'device';\n deviceToken: string;\n measurements: string[];\n // Additive and OPTIONAL, deliberately: every dashboard definition stored before the\n // location channel existed carries no `location`, and must keep parsing and\n // rendering exactly as it did. Absence means \"this selector names no location\n // series\", which is the correct reading of every one of them.\n location?: LocationSelection;\n}\n\n// The dimension a dashboard aggregates over \u2014 a tracked relationship to a\n// customer / area / asset. An anchor is not an opaque alias: it is a graph edge\n// the platform already resolves events against.\nexport interface AnchorTarget {\n relationship: string;\n targetType: 'customer' | 'area' | 'asset';\n targetToken: string;\n}\n\nexport interface AnchorAggregation {\n window: string; // e.g. '1m'\n fn: 'avg' | 'min' | 'max' | 'sum' | 'count';\n}\n\n// anchor \u2014 an anchor dimension aggregated over its member devices.\nexport interface AnchorSelector {\n kind: 'anchor';\n anchor: AnchorTarget;\n measurements: string[];\n location?: LocationSelection;\n // RESERVED (Phase 2): server-side aggregation of the member devices into one\n // series. Phase 1 resolves an anchor to its member devices and streams each\n // one's raw samples; the Hub does NOT read this field yet. Present so a\n // definition authored against the final shape still round-trips.\n aggregation?: AnchorAggregation;\n}\n\n// ---- Reserved selectors (Phase 2) -------------------------------------------\n// Schema-valid and part of the union so a stored definition never fails to parse,\n// but the Hub throws \"not supported yet\" until Phase 2 wires them.\n\nexport interface DevicesSelector {\n kind: 'devices';\n deviceTokens: string[];\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport interface RelatedTraversalSelector {\n kind: 'relatedTraversal';\n from: string;\n relationship: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// slot \u2014 a NAMED reference into the dashboard's `slots` section, resolved to a\n// concrete entity at MOUNT by the host's binding manifest (ADR-039 runtime\n// binding). This makes a definition a reusable TEMPLATE: the widget names the\n// entity role (`slot`) and the measurements it wants, and two mounts of the same\n// definition can bind the slot to two different devices. The RENDERER resolves it\n// through the settled bindings (a slot's defaultBinding, overridable by the host and\n// by a selection) before the data source sees it, so re-pointing a slot re-subscribes\n// only the widgets bound to it; an unbound slot becomes an `unbound` selector and\n// renders as an empty placeholder.\nexport interface SlotSelector {\n kind: 'slot';\n slot: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// unbound \u2014 a slot that has NO binding, as the renderer hands it to a data source.\n//\n// It is never authored: the renderer produces it when it resolves a widget's `slot`\n// selector through the settled bindings and finds nothing there (see\n// resolveWidgetDatasource). Every live channel resolves it to ZERO devices \u2014 no stream,\n// an empty alarm snapshot, no command target, no positions, and no availability check.\n//\n// \uD83D\uDD34 It is the OPPOSITE of an absent datasource, and the difference is not cosmetic. A\n// widget with no datasource at all is tenant-wide on the alarm channel, so an unbound slot\n// that collapsed into `undefined` would show every alarm in the tenant under a subtitle\n// that implies one machine. Unbound is therefore a resolved state of its own, never a\n// missing one.\n//\n// It carries the widget's series (measurement names, and the location series when one is\n// named) so an offline preview source, which never resolves a device, can still fabricate\n// the values a widget asks for. The live hub reads none of it.\nexport interface UnboundSelector {\n kind: 'unbound';\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport type DatasourceSelector =\n | DeviceSelector\n | AnchorSelector\n | DevicesSelector\n | RelatedTraversalSelector\n | SlotSelector\n | UnboundSelector;\n\n// ---- Canvas + widgets -------------------------------------------------------\n\n// The built-in widget types. Kept as a runtime array so parse/validation and the\n// widget registry share one source of truth (WidgetType is derived from it).\n//\n// Widgets fall into data CHANNELS (see WIDGET_CHANNEL in @devicechain/widgets): the\n// measurement widgets stream telemetry samples; the alarm widgets consume the raised-\n// alarm surface (ADR-041) via the hub's alarm channel; the control widget (command-\n// button) issues commands and tracks their delivery lifecycle via the command channel;\n// the map widget reads device positions via the location channel (ADR-078 decision 9).\n// A widget's channel decides which hook/registry the renderer binds it through.\nexport const WIDGET_TYPES = [\n 'timeseries-chart',\n 'latest-card',\n 'gauge',\n 'table',\n 'label',\n 'image',\n 'alarm-table',\n 'alarm-count',\n 'alarm-severity',\n 'command-button',\n 'entity-selector',\n 'map',\n 'linear-gauge',\n 'level-gauge',\n 'battery',\n 'status-light',\n 'signal-bars',\n 'compass',\n 'progress-ring',\n 'command-slider',\n 'command-toggle',\n 'stat-card',\n 'bar-chart',\n 'pie-chart',\n 'fleet-table',\n 'fleet-count',\n 'device-status',\n 'online-doughnut',\n 'history-table',\n] as const;\n\nexport type WidgetType = (typeof WIDGET_TYPES)[number];\n\n// ---- Placement --------------------------------------------------------------\n//\n// Every widget is placed by an explicit MODE, modelled on CSS `position` and CSS anchor\n// positioning, so whether a widget may overlap another is something it DECLARES rather than\n// something a check has to catch afterwards:\n//\n// grid aligned to cells and occupying a footprint. Two grid footprints never overlap: the\n// parser refuses a definition where they do, so no producer (the editor, an import,\n// a Go builder, the API) can store one. The only mode that takes part in collision,\n// push-reflow and adaptive reflow.\n// relative positioned against a HOST widget's live box, so it moves when the host moves or\n// reflows. It may sit over the host (a badge on a map) or beside it (a caption under\n// a chart). It never collides: overlapping is what the mode is for. It paints above\n// every grid widget.\n//\n// The union is OPEN: RESERVED_PLACEMENT_MODES names the modes a later release may add (`pinned`,\n// fixed to a canvas edge; `sticky`, a grid widget that stays visible while the board scrolls).\n// This release refuses them by name rather than mis-placing them as something else.\n//\n// There is no z-order and no pixel nudge: layering is the relative mode, and every distance is\n// in cells, so an arrangement survives every screen size.\n\n// A point on a box. For `anchor` it is a point on the HOST; for `self`, the point on the\n// relative widget that is placed there.\nexport const PLACEMENT_ANCHORS = [\n 'top-left',\n 'top',\n 'top-right',\n 'left',\n 'center',\n 'right',\n 'bottom-left',\n 'bottom',\n 'bottom-right',\n] as const;\nexport type PlacementAnchor = (typeof PLACEMENT_ANCHORS)[number];\n\nexport const PLACEMENT_MODES = ['grid', 'relative'] as const;\nexport type PlacementMode = (typeof PLACEMENT_MODES)[number];\n// Named so a definition from a release that implements them fails with a reason, not as a typo.\nexport const RESERVED_PLACEMENT_MODES = ['pinned', 'sticky'] as const;\n\n// A footprint on the grid. `col`/`row` are the 0-based start cell; `cols`/`rows` how many cells\n// it covers. Cells are square, so a span fixes the widget's SHAPE as well as its share.\nexport interface GridPlacement {\n mode: 'grid';\n col: number;\n row: number;\n cols: number;\n rows: number;\n}\n\n// A relative widget's own size: a span in cells (the grid's rule: n cells = n*cell +\n// (n-1)*gutter), or exactly the host's box.\nexport type RelativeSize = { cols: number; rows: number } | { fill: 'host' };\n\n// A widget placed against another. The geometry, in order: `anchor` picks a point on the host's\n// box, `self` the point on this widget placed there, and `offset` translates it. An offset is a\n// DISTANCE in cell PITCHES (one unit = live cell + gutter on that axis), so a whole-number offset\n// lands exactly on a grid line, and fractions are allowed: `{x: -0.25, y: 0.25}` with\n// anchor = self = 'top-right' insets a badge a quarter pitch from the host's corner at every\n// cell size. The host's area plays no part.\nexport interface RelativePlacement {\n mode: 'relative';\n // The id of the widget this one is anchored to. It must exist; a relative host may itself be\n // relative only when ITS host is a grid widget (one level of chaining, no deeper).\n host: string;\n anchor: PlacementAnchor;\n // Absent = the same point as `anchor`.\n self?: PlacementAnchor;\n offset?: { x: number; y: number };\n size: RelativeSize;\n // Paint order among the relative widgets on the same host. Absent = 0.\n layer?: number;\n}\n\nexport type Placement = GridPlacement | RelativePlacement;\n\n// A widget's placement per breakpoint, keyed by breakpoint name. `base` is required; a widget\n// with no entry for a breakpoint shows its `base` placement there.\n//\n// \uD83D\uDD11 A BREAKPOINT LAYOUT IS AN AUTHORED ARRANGEMENT AT ITS OWN COLUMN COUNT. Placements at a\n// breakpoint are written in the column count canvas.breakpoints declares for it\n// (breakpointColumns; `base` is always canvas.grid.columns), so a phone arrangement pinned at 4\n// columns stays a 4-column arrangement. While the container is wide enough for the active\n// breakpoint's column count at minCellSize, that arrangement renders as authored and takes\n// precedence over the automatic reflow; only a container too narrow even for it reflows it.\n// A board authored only at `base` simply reflows.\nexport type WidgetLayout = Record<string, Placement>;\n\n// ---- Frames -----------------------------------------------------------------\n//\n// How a widget's card looks. `canvas.frame` sets the board's default and `widget.frame`\n// overrides it field by field (resolveFrame). Every colour, radius and spacing is a TOKEN, never\n// a free value: the parser refuses `#f00`, so dark mode, the ISA-101 theme and tenant branding\n// stay coherent (a border of `sev-critical` is fine; a hex code is not).\n//\n// The border and padding sit INSIDE the widget's footprint, so changing them never moves the\n// layout.\n\n// The colour tokens a host defines, and the only colours a definition may name.\n// @devicechain/widget-sdk's ThemeVar is this list.\nexport const THEME_COLOR_TOKENS = [\n 'background',\n 'foreground',\n 'card',\n 'card-foreground',\n 'muted',\n 'muted-foreground',\n 'primary',\n 'primary-foreground',\n 'border',\n 'destructive',\n 'chart-1',\n 'chart-2',\n 'chart-3',\n 'chart-4',\n 'chart-5',\n 'sev-critical',\n 'sev-major',\n 'sev-minor',\n 'sev-warning',\n] as const;\nexport type ThemeColorToken = (typeof THEME_COLOR_TOKENS)[number];\n\n// The spacing steps, in px at density 1; every one multiplies by the density scale. `space-0`\n// is zero, so a seamless tiled board is `gutter: 'space-0'` (or `gutter: 0`).\nexport const SPACE_TOKEN_PX = {\n 'space-0': 0,\n 'space-1': 4,\n 'space-2': 8,\n 'space-3': 12,\n 'space-4': 16,\n} as const;\nexport type SpaceTokenName = keyof typeof SPACE_TOKEN_PX;\n\nexport const FRAME_VARIANTS = ['card', 'flat', 'none'] as const;\nexport type FrameVariant = (typeof FRAME_VARIANTS)[number];\nexport const FRAME_RADII = ['none', 'sm', 'md', 'lg'] as const;\nexport type FrameRadius = (typeof FRAME_RADII)[number];\nexport const FRAME_BORDER_WIDTHS = [0, 1, 2, 3] as const;\nexport type FrameBorderWidth = (typeof FRAME_BORDER_WIDTHS)[number];\nexport const FRAME_HEADERS = ['auto', 'show', 'hide'] as const;\nexport type FrameHeader = (typeof FRAME_HEADERS)[number];\n\nexport interface FrameBorder {\n width?: FrameBorderWidth;\n radius?: FrameRadius;\n color?: ThemeColorToken;\n}\n\n// Every field optional: an absent field inherits (widget, then canvas, then the variant's preset).\n// variant card = border + background + header (the default); flat = a card with no border;\n// none = frameless (no border, transparent, no header), the usual choice for a\n// relative badge or caption.\n// header auto = the size-aware rules (the subtitle, then the title, give way as the widget\n// gets short); show = always; hide = never (the title stays the frame's aria-label).\nexport interface WidgetFrameSpec {\n variant?: FrameVariant;\n border?: FrameBorder;\n padding?: SpaceTokenName;\n background?: ThemeColorToken | 'transparent';\n header?: FrameHeader;\n}\n\n// A frame with every field decided: what the renderer hands the widget frame.\nexport interface ResolvedFrame {\n variant: FrameVariant;\n border: Required<FrameBorder>;\n padding: SpaceTokenName;\n background: ThemeColorToken | 'transparent';\n header: FrameHeader;\n}\n\n// The type a widget is given when its stored type is not one this build knows \u2014 one a\n// newer release added. Deliberately NOT a member of WIDGET_TYPES: it is never authored,\n// never offered in an add menu, and has no row in any per-type table. See\n// UnknownWidgetInstance.\nexport const UNKNOWN_WIDGET_TYPE = 'unknown-widget';\nexport type UnknownWidgetType = typeof UNKNOWN_WIDGET_TYPE;\n\n// The implementation a widget was authored against, stored beside its type as `widget`.\n// `version` is provenance (the widget release the author used); `optionsVersion` and\n// `sdkApiVersion` are the compatibility key together with the id \u2014 see checkWidgetCompatibility.\n// A definition predating this field is a legacy built-in and carries none.\nexport interface WidgetImplementationRef {\n version: string;\n optionsVersion: number;\n sdkApiVersion?: number;\n}\n\n// A namespaced id (`scope/name`) a registered extension package owns. Kept apart from\n// WidgetType so the built-in union stays exhaustive for the compiler.\nexport type ExtensionWidgetType = `${string}/${string}`;\n\n// A widget of a type this build knows how to render.\nexport interface KnownWidgetInstance {\n id: string;\n type: WidgetType;\n // Absent on a legacy (unversioned) built-in.\n widget?: WidgetImplementationRef;\n layout: WidgetLayout;\n // label/image widgets carry no datasource.\n datasource?: DatasourceSelector;\n // Widget-specific options (series colors, unit, thresholds, \u2026). Owned by the\n // widget package; opaque here.\n options?: Record<string, unknown>;\n // This widget's frame overrides, field by field over canvas.frame. Absent = the canvas's.\n frame?: WidgetFrameSpec;\n raw?: undefined;\n reason?: undefined;\n}\n\n// A widget from an installed extension package, recognized through the catalog the\n// definition was parsed with: registered, versioned, and compatible (optionsVersion and\n// SDK generation). Same envelope as a built-in; the id is a namespaced one.\nexport interface ExtensionWidgetInstance {\n id: string;\n type: ExtensionWidgetType;\n widget: WidgetImplementationRef;\n layout: WidgetLayout;\n datasource?: DatasourceSelector;\n options?: Record<string, unknown>;\n frame?: WidgetFrameSpec;\n raw?: undefined;\n reason?: undefined;\n}\n\n// Why a stored widget could not be resolved to an installed implementation.\n// unregistered - no registration for the id in the catalog (or the type is unknown)\n// unversioned - a registered extension stored without version metadata\n// invalid-metadata - `widget` is present but malformed, or an extension widget uses a\n// top-level field name the format reserves (`raw`, `reason`)\n// options-version - the stored settings shape differs from the installed one\n// sdk-incompatible - authored against a different SDK contract generation\nexport type UnresolvedWidgetCode =\n | 'unregistered'\n | 'unversioned'\n | 'invalid-metadata'\n | 'options-version'\n | 'sdk-incompatible';\n\nexport interface UnresolvedWidgetReason {\n code: UnresolvedWidgetCode;\n // A human-readable, specific explanation (names the id and the versions involved).\n message: string;\n}\n\n// A widget that cannot be resolved to an installed implementation. The parser keeps the\n// stored object VERBATIM in `raw`, and the serializer writes `raw` back, so a host on an\n// older release or without the package that loads a board and saves it does not delete or\n// rewrite the widget it cannot read.\n//\n// `id` and `layout` are the only parsed fields, because every widget has them and the\n// canvas needs them to place, select, move and delete the placeholder. An edit to\n// either is written back into `raw` on save; nothing else about the widget is ever\n// read or changed. It carries no datasource and no options: whatever the widget\n// binds lives in `raw`, where no hub channel can mistake it for a selector this build\n// understands. `reason` says why it is unresolved (derived at parse time, never stored).\nexport interface UnknownWidgetInstance {\n id: string;\n type: UnknownWidgetType;\n layout: WidgetLayout;\n raw: Record<string, unknown>;\n reason?: UnresolvedWidgetReason;\n widget?: undefined;\n datasource?: undefined;\n options?: undefined;\n frame?: undefined;\n}\n\nexport type WidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance | UnknownWidgetInstance;\n\n// A widget some installed implementation resolved: a built-in or a registered extension, i.e.\n// everything but a placeholder. What an editor panel is typed against.\nexport type ResolvedWidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance;\n\nexport interface CanvasBackground {\n color?: string | null;\n imageUrl?: string | null;\n}\n\n// A length on the canvas: px at density 1 (multiplied by the density scale, like every size\n// token), or a spacing token.\nexport type GridLength = number | SpaceTokenName;\n\n// The canvas grid (the Android home-screen model): square cells of roughly constant size.\n//\n// columns the AUTHORED column count every `base` placement is written in.\n// minCellSize px at density 1. While (container width - padding - (columns-1)*gutter.x) /\n// columns is at least this, the authored layout renders as authored and the cells\n// STRETCH (a wall gets bigger cells, hence bigger text). Narrower than that, the\n// board reflows onto fewer live columns. The gutters come out of the cell, never\n// the other way round, so widening them can never push a cell below this size.\n// gutter the space between cells: one length, or one per axis.\n// padding the margin around the board: one length, or one per side.\n//\n// Cells are square: a row is as tall as the live cell is wide.\nexport interface CanvasGrid {\n columns: number;\n minCellSize: number;\n gutter: GridLength | { x: GridLength; y: GridLength };\n padding: GridLength | { top: GridLength; right: GridLength; bottom: GridLength; left: GridLength };\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// A breakpoint: the container width it starts at, and optionally the column count its layout is\n// authored in. A bare number is the width alone, and its layout is in canvas.grid.columns.\n// `base` starts at 0 and is always in canvas.grid.columns.\nexport type BreakpointSpec = number | { minWidth: number; columns: number };\n\n// Breakpoint name \u2192 spec. 'base' is required.\nexport type Breakpoints = Record<string, BreakpointSpec>;\n\n// How roomy the board renders: a scale applied to every size token (font, spacing) and to the\n// floor of the value-font curve. `comfortable` is the default and is not written when it is\n// the choice; a wall display picks `wall`. A viewer can override it (the /dash app's\n// `?density=`), so this is the AUTHORED default, not a fixed property of the board.\nexport const CANVAS_DENSITIES = ['compact', 'comfortable', 'wall'] as const;\nexport type CanvasDensity = (typeof CANVAS_DENSITIES)[number];\nexport const DEFAULT_CANVAS_DENSITY: CanvasDensity = 'comfortable';\nexport const DENSITY_SCALE: Readonly<Record<CanvasDensity, number>> = {\n compact: 0.9,\n comfortable: 1,\n wall: 1.5,\n};\n\nexport function isCanvasDensity(value: unknown): value is CanvasDensity {\n return typeof value === 'string' && (CANVAS_DENSITIES as readonly string[]).includes(value);\n}\n\nexport interface Canvas {\n background?: CanvasBackground;\n // Absent = `comfortable`.\n density?: CanvasDensity;\n grid: CanvasGrid;\n sizing: CanvasSizing;\n breakpoints: Breakpoints;\n // The board's default frame. Absent = a card.\n frame?: WidgetFrameSpec;\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 // A ROOT anchor slot's picker may list only the areas of one area type, or the assets of one\n // asset type (a Floor picker that does not offer buildings and plants). Absent = every one the\n // viewer may see. Ignored for a customer anchor, a device slot and a scoped slot.\n candidates?: SlotCandidates;\n}\n\n// The type filter a root anchor slot's picker applies, by the type's token.\nexport interface SlotCandidates {\n areaType?: string;\n assetType?: string;\n}\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 entities of one kind (token + optional name) the viewer may see,\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\n// The definition format this release writes. 2 is the square-cell grid with explicit placement\n// modes; 3 is the time span (a time-series widget draws a span, `windowSeconds`, not a sample\n// count, and the board carries a `timeRange`). A stored document below it is converted on load\n// (see ./legacy-grid and ./legacy-window).\nexport const CURRENT_SCHEMA_VERSION = 3;\n\n// The board's time range as authored: the span (seconds) its time-series widgets draw until a\n// viewer picks another. Absent = one hour (DEFAULT_TIME_RANGE_SECONDS).\nexport interface BoardTimeRange {\n defaultSeconds: number;\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 // The board's time range (D-1). Optional: a board that names none draws the default hour.\n timeRange?: BoardTimeRange;\n // Set by the parser, NEVER stored: the schemaVersion of the document this definition was\n // converted from, when it was stored in an older format and its layout was mapped onto the\n // current grid approximately. An editor shows a \"review the layout\" notice while it is set;\n // the serializer drops it, so the next save writes the current format and the notice goes.\n convertedFrom?: number;\n // Set by the parser, NEVER stored: the ids of the time-series widgets that carried a\n // sample-count window when the board was stored. A sample count has no span without a sampling\n // rate nobody recorded, so each of these now follows the board's time range instead; an editor\n // names them so the author can pin a `windowSeconds` where the old window mattered.\n convertedWindows?: string[];\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 // Set only on a point that is a bucket AVERAGE rather than a reading: the bucket's width in seconds.\n // Stored history is drawn (and exported) per bucket, and a point read live is the reading itself;\n // this is how a consumer tells them apart. Absent on a reading.\n bucketSeconds?: number;\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 // The originating device's type, present only on a row read for a subscription that asked for it\n // (AlarmSubscription.enrichDeviceType / deviceType). `undefined` means \"not asked for or not\n // resolvable\"; `null` means the originator is not a device. Additive: every other source omits it.\n deviceType?: AlarmDeviceType | null;\n}\n\nexport interface AlarmDeviceType {\n token: string;\n name: string | null;\n}\n\n// One device's connectivity, as device-state holds it: whether it is considered connected, and when\n// it last did anything. `lastActivityTime` is null for a device that has not produced an event.\n// A device with no state row at all has no DevicePresence (the service omits it).\nexport interface DevicePresence {\n deviceToken: string;\n active: boolean;\n lastActivityTime: string | null;\n // Seconds without activity after which an INFERRED device is marked inactive.\n inactivityTimeout: number;\n}\n\n// How many matching alarms are at one severity (a severity-count read).\nexport interface AlarmSeverityCount {\n severity: string;\n count: number;\n}\n\n// How many matching alarms are on devices of one type at one severity (a type-count read).\nexport interface AlarmTypeCount {\n deviceType: AlarmDeviceType;\n severity: string;\n count: number;\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// The square-cell grid's geometry, as pure functions: lengths, cell size, where a placement\n// lands, collisions between grid footprints, and the effective frame of a widget.\n//\n// One copy of the arithmetic, shared by everything that has to agree about where a widget is:\n// the parser (overlap), the legacy converter (mapping old boxes, then pushing collisions\n// apart), the editor (pixels <-> cells), the layout lint (a relative widget off the canvas) and\n// the renderers. Nothing here knows about React or the DOM.\n\nimport {\n SPACE_TOKEN_PX,\n type Breakpoints,\n type BreakpointSpec,\n type Canvas,\n type CanvasGrid,\n type GridLength,\n type GridPlacement,\n type Placement,\n type PlacementAnchor,\n type RelativePlacement,\n type ResolvedFrame,\n type SpaceTokenName,\n type WidgetFrameSpec,\n type WidgetInstance,\n type WidgetLayout,\n} from './types';\n\nexport const BASE_BREAKPOINT = 'base';\n\n// The grid a definition gets when it states none, and the values a partial grid is filled\n// from. 12 columns: it divides by 2, 3, 4 and 6, and at the 960 px a half-screen board gets, a\n// 12-column cell is ~73 px, comfortably above minCellSize, where 16 columns would already be\n// below it (52 px) and reflow. 56 px is the smallest cell a single-value widget reads in.\nexport const DEFAULT_GRID: Readonly<CanvasGrid> = Object.freeze({\n columns: 12,\n minCellSize: 56,\n gutter: 8,\n padding: 0,\n});\n\n// The narrowest a board reflows to: below this many live columns a widget would be a sliver.\nexport const PHONE_MIN_COLUMNS = 4;\n\nexport function isSpaceToken(v: unknown): v is SpaceTokenName {\n return typeof v === 'string' && Object.prototype.hasOwnProperty.call(SPACE_TOKEN_PX, v);\n}\n\n// The px a length stands for at a density scale.\nexport function gridLengthPx(len: GridLength, scale = 1): number {\n const base = typeof len === 'number' ? len : SPACE_TOKEN_PX[len];\n return base * scale;\n}\n\nexport interface Gutter {\n x: number;\n y: number;\n}\n\nexport interface Insets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function gutterPx(grid: CanvasGrid, scale = 1): Gutter {\n const g = grid.gutter;\n if (typeof g === 'object') return { x: gridLengthPx(g.x, scale), y: gridLengthPx(g.y, scale) };\n const v = gridLengthPx(g, scale);\n return { x: v, y: v };\n}\n\nexport function paddingPx(grid: CanvasGrid, scale = 1): Insets {\n const p = grid.padding;\n if (typeof p === 'object') {\n return {\n top: gridLengthPx(p.top, scale),\n right: gridLengthPx(p.right, scale),\n bottom: gridLengthPx(p.bottom, scale),\n left: gridLengthPx(p.left, scale),\n };\n }\n const v = gridLengthPx(p, scale);\n return { top: v, right: v, bottom: v, left: v };\n}\n\n// The live cell size for a container width at a column count: the width left after the padding\n// and the gutters, shared evenly. The gutters come out of the cell. Never below 0.\nexport function cellSizeAt(grid: CanvasGrid, containerWidth: number, columns = grid.columns, scale = 1): number {\n const pad = paddingPx(grid, scale);\n const g = gutterPx(grid, scale);\n return Math.max(0, (containerWidth - pad.left - pad.right - (columns - 1) * g.x) / columns);\n}\n\n// The container width at which a column count's cells are exactly `cell` px: the inverse of\n// cellSizeAt.\nexport function containerWidthFor(grid: CanvasGrid, cell: number, columns = grid.columns, scale = 1): number {\n const pad = paddingPx(grid, scale);\n return columns * cell + (columns - 1) * gutterPx(grid, scale).x + pad.left + pad.right;\n}\n\n// ---- Breakpoints -------------------------------------------------------------------\n\nexport function breakpointMinWidth(spec: BreakpointSpec): number {\n return typeof spec === 'number' ? spec : spec.minWidth;\n}\n\n// The column count a breakpoint's placements are authored in: its declared count, or the grid's.\nexport function breakpointColumns(canvas: Pick<Canvas, 'grid' | 'breakpoints'>, breakpoint: string): number {\n if (breakpoint === BASE_BREAKPOINT) return canvas.grid.columns;\n const spec = Object.prototype.hasOwnProperty.call(canvas.breakpoints, breakpoint)\n ? canvas.breakpoints[breakpoint]\n : undefined;\n return spec !== undefined && typeof spec === 'object' ? spec.columns : canvas.grid.columns;\n}\n\n// activeBreakpoint picks the breakpoint whose min width is the largest one that still fits the\n// width (falling back to 'base'). Deterministic given equal widths by preferring the wider min.\nexport function activeBreakpoint(breakpoints: Breakpoints, width: number): string {\n let best = BASE_BREAKPOINT;\n let bestWidth = -1;\n for (const [name, spec] of Object.entries(breakpoints)) {\n const minWidth = breakpointMinWidth(spec);\n if (width >= minWidth && minWidth > bestWidth) {\n best = name;\n bestWidth = minWidth;\n }\n }\n return best;\n}\n\n// The placement a widget shows at a breakpoint: its own entry, or its base one.\nexport function resolvePlacement(layout: WidgetLayout, breakpoint: string): Placement {\n return Object.prototype.hasOwnProperty.call(layout, breakpoint) ? layout[breakpoint] : layout[BASE_BREAKPOINT];\n}\n\n// Every breakpoint a set of widgets can be shown at: the declared ones, and any a widget carries\n// an entry for (an entry for an undeclared breakpoint is never shown, but it is still checked).\nexport function layoutBreakpoints(breakpoints: Breakpoints, widgets: readonly { layout: WidgetLayout }[]): string[] {\n const names = new Set<string>([BASE_BREAKPOINT, ...Object.keys(breakpoints)]);\n for (const w of widgets) for (const bp of Object.keys(w.layout)) names.add(bp);\n return [...names];\n}\n\n// ---- Grid footprints ---------------------------------------------------------------\n\nexport function gridFootprintsOverlap(a: GridPlacement, b: GridPlacement): boolean {\n return a.col < b.col + b.cols && b.col < a.col + a.cols && a.row < b.row + b.rows && b.row < a.row + a.rows;\n}\n\nexport interface PlacedGrid {\n id: string;\n placement: GridPlacement;\n}\n\n// pushDown resolves every collision among grid footprints by moving widgets DOWN, never sideways,\n// so each keeps its column and its span: the `first` widgets are placed where they are (in the\n// order given), then the rest in reading order (row, then column, then their order in the input),\n// each at its own row or, if that collides, at the first row below where it no longer does. This\n// is react-grid-layout's collision rule without the upward compaction, so nothing that was not in\n// the way moves. Returns the same placement object for every widget that did not move.\nexport function pushDown(items: readonly PlacedGrid[], first: readonly string[] = []): Map<string, GridPlacement> {\n const order = items.map((item, index) => ({ ...item, index }));\n const priority = new Map(first.map((id, i) => [id, i]));\n order.sort((a, b) => {\n const pa = priority.get(a.id);\n const pb = priority.get(b.id);\n if (pa !== undefined || pb !== undefined) return (pa ?? Infinity) - (pb ?? Infinity);\n return a.placement.row - b.placement.row || a.placement.col - b.placement.col || a.index - b.index;\n });\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n for (const item of order) {\n let p = item.placement;\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.row + q.rows)) };\n }\n placed.push(p);\n out.set(item.id, p);\n }\n return out;\n}\n\n// pushAside is how an EDIT settles the widgets around the one it changed, the Android launcher's\n// rule: the `first` widgets land exactly where they were put (resolved among themselves as\n// pushDown would), a widget their footprint now covers is DISPLACED, and every other widget stays\n// exactly where it is, so a gap the author left on purpose survives an unrelated edit. A displaced\n// widget keeps its column and its span and settles at the free row NEAREST the one it had,\n// searching up as well as down, so a widget dropped onto a neighbour swaps with it (the neighbour\n// takes the room the dragged one left) instead of shoving it further down the board; on a tie it\n// goes up. Moving up, it may not cover any widget, settled or not yet visited; moving down, it\n// displaces in turn whatever it lands on, which settles by the same rule. Nothing undisplaced is\n// ever pulled up into a gap. Deterministic, and the identity on a board with no collision; returns\n// the same placement object for every widget that did not move.\nexport function pushAside(items: readonly PlacedGrid[], first: readonly string[] = []): Map<string, GridPlacement> {\n const order = items.map((item, index) => ({ ...item, index }));\n const priority = new Map(first.map((id, i) => [id, i]));\n order.sort((a, b) => {\n const pa = priority.get(a.id);\n const pb = priority.get(b.id);\n if (pa !== undefined || pb !== undefined) return (pa ?? Infinity) - (pb ?? Infinity);\n return a.placement.row - b.placement.row || a.placement.col - b.placement.col || a.index - b.index;\n });\n const pending = new Map(order.map((item) => [item.id, item.placement]));\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n const clear = (p: GridPlacement, others: Iterable<GridPlacement>) => {\n for (const q of others) if (gridFootprintsOverlap(p, q)) return false;\n return true;\n };\n for (const item of order) {\n pending.delete(item.id);\n let p = item.placement;\n if (priority.has(item.id)) {\n // A widget the edit put somewhere goes exactly there, unless an earlier one of them is.\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.row + q.rows)) };\n }\n } else if (!clear(p, placed)) {\n const from = p.row;\n for (let k = 1; ; k++) {\n const up: GridPlacement = { ...p, row: from - k };\n if (up.row >= 0 && clear(up, placed) && clear(up, pending.values())) {\n p = up;\n break;\n }\n const down: GridPlacement = { ...p, row: from + k };\n if (clear(down, placed)) {\n p = down;\n break;\n }\n }\n }\n placed.push(p);\n out.set(item.id, p);\n }\n return out;\n}\n\n// firstFreeSlot is the first position, scanning rows top to bottom and columns left to right, at\n// which a cols x rows footprint fits inside `columns` without touching any of `occupied`. A\n// footprint wider than the grid is placed at column 0 (it overruns; the lint reports it).\nexport function firstFreeSlot(\n occupied: readonly GridPlacement[],\n cols: number,\n rows: number,\n columns: number,\n): { col: number; row: number } {\n const lastCol = Math.max(0, columns - cols);\n const bottom = occupied.reduce((m, p) => Math.max(m, p.row + p.rows), 0);\n for (let row = 0; row <= bottom; row++) {\n for (let col = 0; col <= lastCol; col++) {\n const probe: GridPlacement = { mode: 'grid', col, row, cols, rows };\n if (!occupied.some((q) => gridFootprintsOverlap(probe, q))) return { col, row };\n }\n }\n return { col: 0, row: bottom };\n}\n\n// ---- Rectangles --------------------------------------------------------------------\n\nexport interface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n// The grid's rule in px: n cells cover n*cell + (n-1)*gutter.\nexport function spanPx(n: number, cell: number, gutter: number): number {\n return n * cell + (n - 1) * gutter;\n}\n\n// Where a grid footprint lands, relative to the grid's top-left corner (inside the padding).\nexport function gridRect(p: GridPlacement, cell: number, gutter: Gutter): Rect {\n return {\n x: p.col * (cell + gutter.x),\n y: p.row * (cell + gutter.y),\n w: spanPx(p.cols, cell, gutter.x),\n h: spanPx(p.rows, cell, gutter.y),\n };\n}\n\n// The fraction across (fx) and down (fy) a box an anchor point sits at.\nexport function anchorFraction(anchor: PlacementAnchor): { fx: number; fy: number } {\n const fx = anchor.endsWith('left') ? 0 : anchor.endsWith('right') ? 1 : 0.5;\n const fy = anchor.startsWith('top') ? 0 : anchor.startsWith('bottom') ? 1 : 0.5;\n return { fx, fy };\n}\n\n// Where a relative widget lands, given its host's box: the host's `anchor` point, minus this\n// widget's `self` point, plus the offset in cell pitches.\nexport function relativeRect(p: RelativePlacement, host: Rect, cell: number, gutter: Gutter): Rect {\n const w = 'fill' in p.size ? host.w : spanPx(p.size.cols, cell, gutter.x);\n const h = 'fill' in p.size ? host.h : spanPx(p.size.rows, cell, gutter.y);\n const a = anchorFraction(p.anchor);\n const s = anchorFraction(p.self ?? p.anchor);\n return {\n x: host.x + a.fx * host.w - s.fx * w + (p.offset?.x ?? 0) * (cell + gutter.x),\n y: host.y + a.fy * host.h - s.fy * h + (p.offset?.y ?? 0) * (cell + gutter.y),\n w,\n h,\n };\n}\n\n// The box of every widget at a breakpoint, for a cell size: grid footprints first, then each\n// relative widget against its host (a host that is itself relative is resolved first; the parser\n// guarantees the chain is at most one level deep and that every host exists). A widget whose\n// host is missing (an unparsed definition) is left out rather than guessed.\nexport function layoutRects(\n widgets: readonly WidgetInstance[],\n breakpoint: string,\n cell: number,\n gutter: Gutter,\n): Map<string, Rect> {\n const rects = new Map<string, Rect>();\n const relatives: { id: string; p: RelativePlacement }[] = [];\n for (const w of widgets) {\n const p = resolvePlacement(w.layout, breakpoint);\n if (p.mode === 'grid') rects.set(w.id, gridRect(p, cell, gutter));\n else relatives.push({ id: w.id, p });\n }\n // Two passes reach one level of relative-on-relative.\n for (let pass = 0; pass < 2; pass++) {\n for (const { id, p } of relatives) {\n const host = rects.get(p.host);\n if (host && !rects.has(id)) rects.set(id, relativeRect(p, host, cell, gutter));\n }\n }\n return rects;\n}\n\n// ---- Frames ------------------------------------------------------------------------\n\n// What each variant looks like before any field is set.\nconst FRAME_PRESETS: Readonly<Record<ResolvedFrame['variant'], ResolvedFrame>> = {\n card: {\n variant: 'card',\n border: { width: 1, radius: 'md', color: 'border' },\n padding: 'space-3',\n background: 'card',\n header: 'auto',\n },\n flat: {\n variant: 'flat',\n border: { width: 0, radius: 'md', color: 'border' },\n padding: 'space-3',\n background: 'card',\n header: 'auto',\n },\n none: {\n variant: 'none',\n border: { width: 0, radius: 'none', color: 'border' },\n padding: 'space-0',\n background: 'transparent',\n header: 'hide',\n },\n};\n\n// resolveFrame decides every frame field for one widget. The canvas default and the widget's own\n// frame merge field by field (a widget that sets only `border.color` keeps the canvas's border\n// width); the merged variant picks the preset the remaining fields fall back to.\nexport function resolveFrame(canvasFrame: WidgetFrameSpec | undefined, widgetFrame: WidgetFrameSpec | undefined): ResolvedFrame {\n const c = canvasFrame ?? {};\n const w = widgetFrame ?? {};\n const variant = w.variant ?? c.variant ?? 'card';\n const preset = FRAME_PRESETS[variant];\n return {\n variant,\n border: { ...preset.border, ...c.border, ...w.border },\n padding: w.padding ?? c.padding ?? preset.padding,\n background: w.background ?? c.background ?? preset.background,\n header: w.header ?? c.header ?? preset.header,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// isRecord is the one \"plain JSON object\" test the parsers share: an object that is neither null\n// nor an array. One copy, because every module that reads a raw document needs it and each private\n// copy is shipped again in every bundle that parses a board.\nexport function isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The one-way conversion of a definition stored before schemaVersion 2 onto the square-cell grid.\n//\n// Before 2, a widget was a box on a grid of fluid columns (24 by default) and FIXED-PX rows (40\n// by default), layered by `z` and nudged by a pixel `offset`. Now cells are square, every\n// distance is in cells, and two grid widgets cannot overlap. There is no exact mapping between\n// the two (a fixed-px row is a different fraction of a square cell at every width), so this is\n// an APPROXIMATION, and it says so: the parser marks the result `convertedFrom`, and an editor\n// shows \"converted from the previous grid -- review the layout\" until the next save writes the\n// current format.\n//\n// The mapping:\n// columns start and end lines scale by newColumns / oldColumns and round, so two boxes that\n// met at a line still meet at one;\n// rows start and end lines go to px on the old grid (row * (rowHeight + gap)) and back to\n// cells at the reference width (1366 px, the common laptop), where a new cell is\n// (1366 - (columns-1) * gutter) / columns;\n// gutter the old gap, unchanged;\n// z, offset dropped: there is no layering on the grid, and a pixel nudge has no meaning in\n// cells. Overlap was the only thing either was for;\n// overlap whatever rounding (or the old z-stacking) leaves overlapping is pushed DOWN until it\n// fits, keeping every widget below the ones it was below (restack), so the converted\n// definition parses: the overlap refusal is not relaxed for converted documents.\n//\n// It works on the RAW document, before the parser sees it, so every widget is converted the same\n// way, a widget of a type this build does not know included: its stored object is kept verbatim\n// and written back on save, so its layout must already be in the current format.\n\nimport {\n BASE_BREAKPOINT,\n DEFAULT_GRID,\n cellSizeAt,\n gridFootprintsOverlap,\n gutterPx,\n} from './grid';\nimport { CURRENT_SCHEMA_VERSION, type CanvasGrid, type GridPlacement } from './types';\nimport { isRecord } from './is-record';\n\n// The format this conversion produces: the square-cell grid. Later formats are layered on by\n// their own conversions (./legacy-window), never folded into this one.\nexport const GRID_SCHEMA_VERSION = 2;\n\n// The width the old fixed-px rows are read at.\nexport const LEGACY_REFERENCE_WIDTH = 1366;\n\nconst LEGACY_DEFAULTS = { columns: 24, gap: 8, rowHeight: 40 };\n\nfunction num(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// The format a stored document is in: its schemaVersion when it states one. A document that\n// states none (no producer has written one without it, so it is hand-written or very old) is\n// read by its SHAPE: an old grid (`rowHeight`, `gap`), an old box (`colSpan`, `rowSpan`, `z`), or\n// a box with neither a `mode` nor `cols` anywhere makes it 1, and anything else is the current\n// format. Guessing 1 for every unversioned document would put a hand-written current one\n// through the converter and mangle it.\nexport function storedSchemaVersion(raw: Record<string, unknown>): number {\n if (typeof raw.schemaVersion === 'number' && Number.isFinite(raw.schemaVersion)) return raw.schemaVersion;\n const grid = isRecord(raw.canvas) && isRecord(raw.canvas.grid) ? raw.canvas.grid : {};\n if ('rowHeight' in grid || 'gap' in grid) return 1;\n const widgets = Array.isArray(raw.widgets) ? raw.widgets : [];\n for (const w of widgets) {\n if (!isRecord(w) || !isRecord(w.layout)) continue;\n for (const box of Object.values(w.layout)) {\n if (!isRecord(box)) continue;\n if ('colSpan' in box || 'rowSpan' in box || 'z' in box) return 1;\n // A box with neither a mode nor a span in cells is not a current placement (one with no\n // mode is a grid placement, and a grid placement needs `cols`): the old parser read it,\n // filling every field it left out, so the old format is the reading that opens.\n if (!('mode' in box) && !('cols' in box)) return 1;\n }\n }\n return CURRENT_SCHEMA_VERSION;\n}\n\ninterface LegacyGrid {\n columns: number;\n rowPitch: number;\n gutter: CanvasGrid['gutter'];\n}\n\nfunction legacyGrid(raw: unknown): LegacyGrid {\n const rec = isRecord(raw) ? raw : {};\n const columns = Math.max(1, Math.round(num(rec, 'columns', LEGACY_DEFAULTS.columns)));\n const rowHeight = Math.max(1, num(rec, 'rowHeight', LEGACY_DEFAULTS.rowHeight));\n let gapRow = LEGACY_DEFAULTS.gap;\n let gapCol = LEGACY_DEFAULTS.gap;\n if (typeof rec.gap === 'number' && Number.isFinite(rec.gap)) {\n gapRow = gapCol = Math.max(0, rec.gap);\n } else if (isRecord(rec.gap)) {\n gapRow = Math.max(0, num(rec.gap, 'row', LEGACY_DEFAULTS.gap));\n gapCol = Math.max(0, num(rec.gap, 'col', LEGACY_DEFAULTS.gap));\n }\n return {\n columns,\n rowPitch: rowHeight + gapRow,\n gutter: gapRow === gapCol ? gapCol : { x: gapCol, y: gapRow },\n };\n}\n\n// A widget's rows on the OLD grid (start line, end line) and its old start column.\ninterface OldSpan {\n row: number;\n end: number;\n col: number;\n}\n\ninterface StackItem {\n index: number;\n placement: GridPlacement;\n old: OldSpan;\n}\n\n// restack places the converted boxes of one breakpoint so that none overlaps and the old stacking\n// survives, returning only the ones that moved. In the old grid's reading order (row, column,\n// then document order), each widget goes at its converted row, but never above the bottom of a\n// widget it was BELOW on the old grid and still shares a column with, and then further down past\n// anything it would overlap. The first rule is what a plain push-down lacks: rounding collapses\n// short old rows onto one new row, the push then moves a widget down a long way, and a widget\n// that was under it -- placed later, colliding with nothing -- would stay where it was and end\n// up ABOVE it. Old boxes that overlapped (layered with z) have no above/below, so only the\n// collision rule applies to them.\nfunction restack(items: StackItem[]): Map<number, GridPlacement> {\n const order = [...items].sort((a, b) => a.old.row - b.old.row || a.old.col - b.old.col || a.index - b.index);\n const placed: StackItem[] = [];\n const moved = new Map<number, GridPlacement>();\n for (const item of order) {\n let p = item.placement;\n const sharesColumn = (q: GridPlacement) => p.col < q.col + q.cols && q.col < p.col + p.cols;\n const floor = placed\n .filter((q) => q.old.end <= item.old.row && sharesColumn(q.placement))\n .reduce((m, q) => Math.max(m, q.placement.row + q.placement.rows), 0);\n if (floor > p.row) p = { ...p, row: floor };\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q.placement));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.placement.row + q.placement.rows)) };\n }\n placed.push({ ...item, placement: p });\n if (p !== item.placement) moved.set(item.index, p);\n }\n return moved;\n}\n\n// convertLegacyDefinition returns the document in the current format. The input is not modified.\nexport function convertLegacyDefinition(raw: Record<string, unknown>): Record<string, unknown> {\n const canvasIn = isRecord(raw.canvas) ? raw.canvas : {};\n const old = legacyGrid(canvasIn.grid);\n const grid: CanvasGrid = { ...DEFAULT_GRID, gutter: old.gutter };\n const columns = grid.columns;\n const pitch = cellSizeAt(grid, LEGACY_REFERENCE_WIDTH) + gutterPx(grid).y;\n\n const toCols = (line: number) => Math.round((line * columns) / old.columns);\n const toRows = (line: number) => Math.round((line * old.rowPitch) / pitch);\n const convertBox = (box: Record<string, unknown>): GridPlacement => {\n const col = Math.max(0, Math.round(num(box, 'col', 0)));\n const colSpan = Math.max(1, Math.round(num(box, 'colSpan', 1)));\n const row = Math.max(0, Math.round(num(box, 'row', 0)));\n const rowSpan = Math.max(1, Math.round(num(box, 'rowSpan', 1)));\n const c0 = Math.min(toCols(col), columns - 1);\n const c1 = Math.min(Math.max(toCols(col + colSpan), c0 + 1), columns);\n const r0 = toRows(row);\n const r1 = Math.max(toRows(row + rowSpan), r0 + 1);\n return { mode: 'grid', col: c0, row: r0, cols: c1 - c0, rows: r1 - r0 };\n };\n\n // Each widget's old box per breakpoint is kept beside its conversion, because the push below\n // works from where a widget was on the OLD grid, not where rounding put it.\n const oldBoxes = new Map<number, Record<string, OldSpan>>();\n const widgetsIn = Array.isArray(raw.widgets) ? raw.widgets : [];\n const widgets = widgetsIn.map((w, i) => {\n if (!isRecord(w) || !isRecord(w.layout)) return w;\n const layout: Record<string, unknown> = {};\n const olds: Record<string, OldSpan> = {};\n for (const [bp, box] of Object.entries(w.layout)) {\n // An entry that is not a box (null, a number) was skipped by the old parser and is skipped\n // by the current one, which shows the base placement there instead. It is dropped here so\n // the push below sees the placement the parser checks.\n if (!isRecord(box) || bp === '__proto__') continue;\n layout[bp] = convertBox(box);\n const row = Math.max(0, Math.round(num(box, 'row', 0)));\n olds[bp] = { row, end: row + Math.max(1, Math.round(num(box, 'rowSpan', 1))), col: Math.max(0, Math.round(num(box, 'col', 0))) };\n }\n oldBoxes.set(i, olds);\n return { ...w, layout };\n });\n\n // Push apart whatever the rounding, or the old layering, left overlapping: base first, since\n // every other breakpoint inherits it, then each other breakpoint over what it shows. A widget\n // shows its own entry at a breakpoint, or its base one (resolvePlacement's rule, which is the\n // one the parser checks overlap by).\n const byBreakpoint = new Set<string>([BASE_BREAKPOINT]);\n for (const w of widgets) if (isRecord(w) && isRecord(w.layout)) for (const bp of Object.keys(w.layout)) byBreakpoint.add(bp);\n for (const bp of byBreakpoint) {\n const items: StackItem[] = [];\n widgets.forEach((w, i) => {\n if (!isRecord(w) || !isRecord(w.layout)) return;\n const layout = w.layout as Record<string, unknown>;\n const own = Object.prototype.hasOwnProperty.call(layout, bp);\n const p = (own ? layout[bp] : layout[BASE_BREAKPOINT]) as GridPlacement | undefined;\n const old = oldBoxes.get(i)?.[own ? bp : BASE_BREAKPOINT];\n if (p && isRecord(p) && p.mode === 'grid' && old) items.push({ index: i, placement: p, old });\n });\n for (const [index, moved] of restack(items)) {\n const w = widgets[index] as Record<string, unknown>;\n (w.layout as Record<string, unknown>)[bp] = moved;\n }\n }\n\n const breakpoints: Record<string, number> = {};\n if (isRecord(canvasIn.breakpoints)) {\n for (const [name, width] of Object.entries(canvasIn.breakpoints)) {\n if (typeof width === 'number' && Number.isFinite(width)) breakpoints[name] = width;\n }\n }\n return {\n ...raw,\n schemaVersion: GRID_SCHEMA_VERSION,\n canvas: { ...canvasIn, grid, breakpoints },\n widgets,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The one-way conversion of a definition stored before schemaVersion 3 onto the time span.\n//\n// Before 3, a `timeseries-chart` kept a rolling SAMPLE COUNT (`options.window`, default 300) and\n// every chart seeded a fixed hour of history whatever that count was. Now it draws a SPAN\n// (`options.windowSeconds`, or the board's time range when it names none).\n//\n// \uD83D\uDD34 There is no honest mapping from a count to a span. N samples cover N x (the sampling\n// interval) seconds, and the interval is a property of the device, not of the dashboard: 300\n// samples is five minutes from a device reporting every second and a day from one reporting every\n// five minutes. Any factor chosen here would be a guess painted as a conversion. So the rule is:\n//\n// - `options.window` is DROPPED from a time-series chart, and the chart follows the board's\n// time range (one hour unless the board names another -- which is exactly the window every\n// chart already seeded, so what the chart showed on load does not change);\n// - each such widget is FLAGGED (by position in the widget list), and the parser names them so an\n// editor can tell the author which charts to look at. A chart that was deliberately short or\n// long gets its span pinned by hand.\n//\n// The widget's stored options generation (`widget.optionsVersion`) is moved from 1 to 2 on a\n// time-series chart, because the options changed meaning; without it the chart would load as a\n// \"settings version differs\" placeholder. Other widgets keep their `window`: it is a memory bound\n// on their rolling buffer that only the chart ever drew, and is unchanged.\n//\n// Like the grid conversion it works on the RAW document and does not modify its input. It is\n// idempotent: its own output carries no `window` and no version-1 reference, so converting it\n// again changes nothing and flags nothing.\n\nimport { CURRENT_SCHEMA_VERSION } from './types';\nimport { isRecord } from './is-record';\n\nconst TIME_SERIES = 'timeseries-chart';\n\nexport interface WindowConversion {\n doc: Record<string, unknown>;\n // Indices into `doc.widgets` of the charts whose sample-count window was dropped.\n flagged: number[];\n}\n\nexport function convertSampleWindows(raw: Record<string, unknown>): WindowConversion {\n const widgetsIn = Array.isArray(raw.widgets) ? raw.widgets : [];\n const flagged: number[] = [];\n const widgets = widgetsIn.map((w, i) => {\n if (!isRecord(w) || w.type !== TIME_SERIES) return w;\n let out: Record<string, unknown> = w;\n if (isRecord(w.options) && Object.prototype.hasOwnProperty.call(w.options, 'window')) {\n const { window: _count, ...options } = w.options;\n out = { ...out, options };\n flagged.push(i);\n }\n if (isRecord(w.widget) && w.widget.optionsVersion === 1) {\n out = { ...out, widget: { ...w.widget, optionsVersion: 2 } };\n }\n return out;\n });\n return { doc: { ...raw, schemaVersion: CURRENT_SCHEMA_VERSION, widgets }, flagged };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { DashboardDefinition } from './types';\n\n// The board's time range: a SPAN (seconds back from \"now\"), never a sample count. A time-series\n// widget draws the last `spanSeconds`; the viewer may pick another span from the presets, and may\n// PAUSE, which freezes what the board shows at that instant. Everything here is pure -- the\n// clock is an argument -- so the window arithmetic, the bucket choice and the deep-link parsing\n// are testable without a DOM.\n\n// The presets a viewer picks from, shortest first. The id is also the deep-link token\n// (`?range=1h`), so it is a contract: renaming one breaks every saved link.\nexport const TIME_RANGE_PRESETS = [\n { id: '15m', seconds: 15 * 60 },\n { id: '1h', seconds: 60 * 60 },\n { id: '6h', seconds: 6 * 60 * 60 },\n { id: '24h', seconds: 24 * 60 * 60 },\n { id: '7d', seconds: 7 * 24 * 60 * 60 },\n] as const;\n\nexport type TimeRangePresetId = (typeof TIME_RANGE_PRESETS)[number]['id'];\n\n// The span a board uses when its definition names none. One hour is what every chart seeded\n// before the span was configurable, so a board that never set one reads as it always did.\nexport const DEFAULT_TIME_RANGE_SECONDS = 60 * 60;\n\n// The bounds of a span a definition may author: a minute to the longest preset. Outside them\n// the value is not a usable span (a zero-length window draws nothing; a year would ask the\n// backend for an unbounded read), so it is ignored, not clamped -- clamping would silently\n// turn a typo into a different window than anyone wrote.\nexport const MIN_TIME_RANGE_SECONDS = 60;\nexport const MAX_TIME_RANGE_SECONDS = 7 * 24 * 60 * 60;\n\nexport function isValidSpanSeconds(v: unknown): v is number {\n return (\n typeof v === 'number' &&\n Number.isInteger(v) &&\n v >= MIN_TIME_RANGE_SECONDS &&\n v <= MAX_TIME_RANGE_SECONDS\n );\n}\n\n// The board's range state, owned by the host (the viewer header) and read by the renderer.\n// `pausedAt` is the instant (epoch ms) the board froze at; it is set exactly while `paused`.\n//\n// ABSOLUTE ranges (a from-to window, for incident review and a shareable \"02:00 to 04:00\" link) are\n// an ADDITIVE variant of this same shape, not a new one: `absolute` is present only for them, and\n// when present the rolling fields are a faithful projection of the window as a FROZEN rolling\n// range -- `paused` is true, `pausedAt` is the window's end and `spanSeconds` its length. A widget\n// that only understands rolling ranges (every third-party widget written before this variant)\n// therefore computes exactly the absolute window, with its live tail already off, and needs no\n// change. A widget that wants to say \"this is a fixed window\" reads `absolute`.\nexport interface AbsoluteRange {\n // Epoch ms, whole seconds. fromMs < toMs.\n fromMs: number;\n toMs: number;\n}\n\nexport interface TimeRangeState {\n spanSeconds: number;\n paused: boolean;\n pausedAt?: number;\n absolute?: AbsoluteRange;\n}\n\n// Bounds of an ABSOLUTE window. A minute to 31 days: shorter draws nothing, longer is a bulk read\n// the bucketed history does not need to serve on a board. `from` may not predate 2000 (a typo in\n// the year, an epoch in seconds read as ms) and `to` may not lie in the future beyond a minute of\n// clock skew -- a window that has not happened yet has no history to show.\nexport const MIN_ABSOLUTE_SPAN_SECONDS = 60;\nexport const MAX_ABSOLUTE_SPAN_SECONDS = 31 * 24 * 60 * 60;\nexport const EARLIEST_ABSOLUTE_MS = Date.UTC(2000, 0, 1);\nexport const ABSOLUTE_FUTURE_SKEW_MS = 60 * 1000;\n\nexport type AbsoluteRangeProblem = 'unparseable' | 'order' | 'tooShort' | 'tooLong' | 'future' | 'tooEarly';\n\n// absoluteRangeProblem says why a from-to pair is not a usable window, or undefined when it is.\nexport function absoluteRangeProblem(fromMs: number, toMs: number, now: number): AbsoluteRangeProblem | undefined {\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) return 'unparseable';\n if (fromMs >= toMs) return 'order';\n if (fromMs < EARLIEST_ABSOLUTE_MS) return 'tooEarly';\n if (toMs > now + ABSOLUTE_FUTURE_SKEW_MS) return 'future';\n const seconds = (Math.floor(toMs / 1000) * 1000 - Math.floor(fromMs / 1000) * 1000) / 1000;\n if (seconds < MIN_ABSOLUTE_SPAN_SECONDS) return 'tooShort';\n if (seconds > MAX_ABSOLUTE_SPAN_SECONDS) return 'tooLong';\n return undefined;\n}\n\nexport function isAbsolute(state: TimeRangeState): boolean {\n return state.absolute !== undefined;\n}\n\n// absoluteRange builds the state for a window, or the problem that makes it unusable. Ends are cut\n// to whole seconds so the projected span is exact.\nexport function absoluteRange(\n fromMs: number,\n toMs: number,\n now: number = Date.now(),\n): { state: TimeRangeState } | { problem: AbsoluteRangeProblem } {\n const problem = absoluteRangeProblem(fromMs, toMs, now);\n if (problem) return { problem };\n const from = Math.floor(fromMs / 1000) * 1000;\n const to = Math.floor(toMs / 1000) * 1000;\n return {\n state: { spanSeconds: (to - from) / 1000, paused: true, pausedAt: to, absolute: { fromMs: from, toMs: to } },\n };\n}\n\n// leaveAbsolute returns to a LIVE rolling range of the same length (capped at the longest preset).\nexport function leaveAbsolute(state: TimeRangeState): TimeRangeState {\n if (!state.absolute) return state;\n return { spanSeconds: Math.min(state.spanSeconds, MAX_TIME_RANGE_SECONDS), paused: false };\n}\n\nexport function initialTimeRange(\n defaultSeconds: number | undefined,\n link: { spanSeconds?: number; paused?: boolean; absolute?: AbsoluteRange } = {},\n now: number = Date.now(),\n): TimeRangeState {\n if (link.absolute) {\n const made = absoluteRange(link.absolute.fromMs, link.absolute.toMs, now);\n if ('state' in made) return made.state;\n }\n const spanSeconds = link.spanSeconds ?? (isValidSpanSeconds(defaultSeconds) ? defaultSeconds : DEFAULT_TIME_RANGE_SECONDS);\n return link.paused ? { spanSeconds, paused: true, pausedAt: now } : { spanSeconds, paused: false };\n}\n\n// Freezing keeps the span and stamps the instant; unfreezing drops the stamp. Both are no-ops\n// when already in the asked state, so a double click cannot move `pausedAt` forward.\nexport function pauseRange(state: TimeRangeState, now: number): TimeRangeState {\n return state.paused ? state : { spanSeconds: state.spanSeconds, paused: true, pausedAt: now };\n}\n\nexport function resumeRange(state: TimeRangeState): TimeRangeState {\n if (state.absolute) return leaveAbsolute(state);\n return state.paused ? { spanSeconds: state.spanSeconds, paused: false } : state;\n}\n\n// A fixed window keeps its length: picking a span is done through leaveAbsolute (a preset click),\n// never by quietly rewriting a window someone chose.\nexport function withSpan(state: TimeRangeState, spanSeconds: number): TimeRangeState {\n if (state.absolute) return state;\n return isValidSpanSeconds(spanSeconds) ? { ...state, spanSeconds } : state;\n}\n\n// The instant the board's window ENDS at: now while live, the pause instant while frozen.\nexport function rangeEnd(state: TimeRangeState, now: number): number {\n return state.paused && state.pausedAt !== undefined ? state.pausedAt : now;\n}\n\nexport interface RangeWindow {\n startTime: string;\n endTime: string;\n}\n\n// from = end - span, to = end, as RFC 3339 instants.\nexport function rangeWindow(state: TimeRangeState, now: number): RangeWindow {\n const end = rangeEnd(state, now);\n return {\n startTime: new Date(end - state.spanSeconds * 1000).toISOString(),\n endTime: new Date(end).toISOString(),\n };\n}\n\n// The most buckets one history seed asks for per measurement. The backend refuses a read past\n// 10 000 buckets; 240 is far inside that, and it is what a chart can show: past a few hundred\n// points a line is smoothed by the pixels anyway, so asking for more only costs the read.\nexport const MAX_SEED_BUCKETS = 240;\n\n// The finest bucket a seed uses, and the base bucket of the pre-aggregated one-minute summaries.\nconst BASE_BUCKET_SECONDS = 60;\n\n// bucketSecondsFor picks the bucket width for a span: the smallest whole number of minutes that\n// keeps the read within MAX_SEED_BUCKETS. A multiple of 60 matters beyond tidiness -- the backend\n// serves it from the one-minute summaries instead of scanning raw readings, which is the\n// difference between a 7-day read being cheap and being a table scan. The result: 15m and 1h\n// stay at one minute; 6h is 2 min; 24h is 6 min; 7d is 42 min.\nexport function bucketSecondsFor(spanSeconds: number): number {\n const wanted = Math.ceil(spanSeconds / MAX_SEED_BUCKETS);\n return Math.max(BASE_BUCKET_SECONDS, Math.ceil(wanted / BASE_BUCKET_SECONDS) * BASE_BUCKET_SECONDS);\n}\n\n// The number of buckets a seed over `spanSeconds` reads, at the width bucketSecondsFor picks.\nexport function seedBucketCount(spanSeconds: number): number {\n return Math.ceil(spanSeconds / bucketSecondsFor(spanSeconds));\n}\n\n// ---- Deep link --------------------------------------------------------------\n\n// parseRangeParam reads `?range=`: a preset id, exactly. Anything else (an unknown id, an empty\n// value, `1H`, `3600`, a span in another unit) is undefined, so the caller keeps the board's\n// default. A link must never be able to ask for a window the control cannot show.\nexport function parseRangeParam(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n return TIME_RANGE_PRESETS.find((p) => p.id === value)?.seconds;\n}\n\n// parsePausedParam reads `?paused=`: only the literal `1` pauses. `true`, `yes`, `0`, an empty\n// value and garbage all mean \"not paused\", so a malformed link opens live rather than frozen\n// on an unannounced instant.\nexport function parsePausedParam(value: string | null | undefined): boolean {\n return value === '1';\n}\n\n// RFC 3339 with an EXPLICIT zone (`Z` or `+02:00`). A date-time with no zone is ambiguous -- whose\n// 02:00? -- and a shared link must mean the same window to everyone who opens it, so it is refused\n// rather than read in the opener's zone.\nconst RFC3339_WITH_ZONE = /^\\d{4}-\\d{2}-\\d{2}[Tt]\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?([Zz]|[+-]\\d{2}:\\d{2})$/;\n\nexport function parseInstantParam(value: string | null | undefined): number | undefined {\n if (!value || !RFC3339_WITH_ZONE.test(value)) return undefined;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? undefined : ms;\n}\n\nexport interface AbsoluteLinkRejection {\n reason: AbsoluteRangeProblem | 'incomplete';\n from: string;\n to: string;\n}\n\n// What a deep link's `from`/`to` came to: a usable window, or the reason it was ignored (with the\n// raw values, so the host can show them). Neither parameter present is neither.\nexport function parseAbsoluteParams(\n from: string | null | undefined,\n to: string | null | undefined,\n now: number = Date.now(),\n): { absolute?: AbsoluteRange; rejected?: AbsoluteLinkRejection } {\n if (from == null && to == null) return {};\n const raw = { from: from ?? '', to: to ?? '' };\n if (!from || !to) return { rejected: { reason: 'incomplete', ...raw } };\n const fromMs = parseInstantParam(from);\n const toMs = parseInstantParam(to);\n if (fromMs === undefined || toMs === undefined) return { rejected: { reason: 'unparseable', ...raw } };\n const made = absoluteRange(fromMs, toMs, now);\n if ('problem' in made) return { rejected: { reason: made.problem, ...raw } };\n return { absolute: made.state.absolute };\n}\n\n// The query values that reproduce a window: UTC instants, so the link means one thing everywhere.\nexport function absoluteToParams(a: AbsoluteRange): { from: string; to: string } {\n return { from: new Date(a.fromMs).toISOString(), to: new Date(a.toMs).toISOString() };\n}\n\nexport interface TimeRangeLink {\n spanSeconds?: number;\n paused?: boolean;\n absolute?: AbsoluteRange;\n // Present when `from`/`to` were given but unusable; the host shows a note and opens rolling.\n rejected?: AbsoluteLinkRejection;\n}\n\n// timeRangeFromSearch reads the rolling parameters, `range` and `paused`, from a query string.\n// Never throws. It ignores `from`/`to`, so what it returns for any address is what it returned\n// before fixed windows existed; a host that opens fixed windows reads timeRangeLinkFromSearch.\nexport function timeRangeFromSearch(search: string): { spanSeconds?: number; paused?: boolean } {\n let params: URLSearchParams;\n try {\n params = new URLSearchParams(search);\n } catch {\n return {};\n }\n const link: { spanSeconds?: number; paused?: boolean } = {};\n const spanSeconds = parseRangeParam(params.get('range'));\n if (spanSeconds !== undefined) link.spanSeconds = spanSeconds;\n if (parsePausedParam(params.get('paused'))) link.paused = true;\n return link;\n}\n\n// timeRangeLinkFromSearch reads every time-range parameter from a query string. Never throws. An\n// absolute window (`from`+`to`) wins over `range` and `paused`: it is the more specific ask.\nexport function timeRangeLinkFromSearch(search: string, now: number = Date.now()): TimeRangeLink {\n let params: URLSearchParams;\n try {\n params = new URLSearchParams(search);\n } catch {\n return {};\n }\n const abs = parseAbsoluteParams(params.get('from'), params.get('to'), now);\n if (abs.absolute) return { absolute: abs.absolute };\n const link: TimeRangeLink = timeRangeFromSearch(search);\n if (abs.rejected) link.rejected = abs.rejected;\n return link;\n}\n\n// The preset id for a span, or undefined when the span is none of them (a board default of\n// two hours, say): the control then shows the span's own label with no preset highlighted.\nexport function presetIdOf(spanSeconds: number): TimeRangePresetId | undefined {\n return TIME_RANGE_PRESETS.find((p) => p.seconds === spanSeconds)?.id;\n}\n\n// A compact label for any span: `15m`, `2h`, `3d`. A span that is not a whole number of the\n// largest unit that divides it falls to the next smaller one, so the label is always exact.\nexport function spanLabel(spanSeconds: number): string {\n if (spanSeconds % 86400 === 0) return `${spanSeconds / 86400}d`;\n if (spanSeconds % 3600 === 0) return `${spanSeconds / 3600}h`;\n if (spanSeconds % 60 === 0) return `${spanSeconds / 60}m`;\n return `${spanSeconds}s`;\n}\n\n// ---- Authoring ---------------------------------------------------------------\n\n// setBoardTimeRange writes the board's default span. The default (one hour) and an unusable value\n// are stored as NO range at all, so a board that never chose one stays byte-identical on save and\n// reads as it always did.\nexport function setBoardTimeRange(def: DashboardDefinition, defaultSeconds: number | undefined): DashboardDefinition {\n const { timeRange: _previous, ...rest } = def;\n if (!isValidSpanSeconds(defaultSeconds) || defaultSeconds === DEFAULT_TIME_RANGE_SECONDS) return rest;\n return { ...rest, timeRange: { defaultSeconds } };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Whether a stored widget can be rendered by an installed implementation.\n//\n// The compatibility key is (id, optionsVersion, sdkApiVersion). `version` is PROVENANCE: a\n// differing release still renders, and the editor shows a non-blocking \"authored with X,\n// rendering with Y\" notice (widgetVersionNotice). Only a settings-shape or SDK-generation\n// difference turns a widget into a placeholder, because only those change what the stored\n// options MEAN. \"Exact replay\" is claimed only when the versions are equal.\n//\n// React-free, and it reads a WidgetCatalog (descriptors), never an implementation, so the\n// parser and the headless validators can use it.\n\nimport type { WidgetCatalog } from './widget-catalog';\nimport type { WidgetDescriptor } from './widget-descriptor';\nimport type {\n DashboardDefinition,\n ResolvedWidgetInstance,\n UnresolvedWidgetReason,\n WidgetImplementationRef,\n WidgetInstance,\n} from './types';\nimport { UNKNOWN_WIDGET_TYPE, WIDGET_TYPES, type KnownWidgetInstance } from './types';\nimport { isRecord } from './is-record';\n\nconst BUILTIN_TYPES: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\n// isBuiltinWidget narrows a parsed widget to a built-in instance: not a placeholder and not\n// an extension. For code (the built-in editor panels) that is typed against the closed\n// built-in union and must not be handed an extension widget.\nexport function isBuiltinWidget(widget: WidgetInstance): widget is KnownWidgetInstance {\n return BUILTIN_TYPES.has(widget.type);\n}\n\n// isResolvedWidget narrows a parsed widget to one an installed implementation resolved (a\n// built-in or a registered extension): everything that is not a placeholder.\nexport function isResolvedWidget(widget: WidgetInstance): widget is ResolvedWidgetInstance {\n return widget.type !== UNKNOWN_WIDGET_TYPE;\n}\n\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\n\n// parseWidgetRef reads the stored `widget` metadata. `undefined` = absent (legacy / incomplete);\n// `null` = present but malformed, which must not be mistaken for absent.\nexport function parseWidgetRef(raw: unknown): WidgetImplementationRef | undefined | null {\n if (raw === undefined) return undefined;\n if (!isRecord(raw)) return null;\n const { version, optionsVersion, sdkApiVersion } = raw;\n if (typeof version !== 'string' || !SEMVER.test(version)) return null;\n if (typeof optionsVersion !== 'number' || !Number.isInteger(optionsVersion) || optionsVersion < 1) return null;\n const ref: WidgetImplementationRef = { version, optionsVersion };\n if (sdkApiVersion !== undefined) {\n if (typeof sdkApiVersion !== 'number' || !Number.isInteger(sdkApiVersion) || sdkApiVersion < 1) return null;\n ref.sdkApiVersion = sdkApiVersion;\n }\n return ref;\n}\n\n// checkWidgetCompatibility returns why the stored reference cannot be rendered by this\n// descriptor, or undefined when it can (a differing `version` alone is compatible).\nexport function checkWidgetCompatibility(\n id: string,\n ref: WidgetImplementationRef,\n descriptor: WidgetDescriptor,\n): UnresolvedWidgetReason | undefined {\n if (ref.optionsVersion !== descriptor.optionsVersion) {\n return {\n code: 'options-version',\n message: `\"${id}\" was saved with settings version ${ref.optionsVersion}; the installed widget uses settings version ${descriptor.optionsVersion}`,\n };\n }\n if (ref.sdkApiVersion !== undefined && ref.sdkApiVersion !== descriptor.sdkApiVersion) {\n return {\n code: 'sdk-incompatible',\n message: `\"${id}\" was authored for widget SDK API ${ref.sdkApiVersion}; the installed widget uses API ${descriptor.sdkApiVersion}`,\n };\n }\n return undefined;\n}\n\n// widgetVersionNotice is the non-blocking provenance message for a widget rendered by a\n// different release than the one it was authored with, or undefined when they match (or the\n// widget carries no version).\nexport function widgetVersionNotice(\n id: string,\n ref: WidgetImplementationRef | undefined,\n descriptor: WidgetDescriptor,\n): string | undefined {\n if (!ref || ref.version === descriptor.version) return undefined;\n return `\"${id}\" was authored with version ${ref.version}; rendering with version ${descriptor.version}`;\n}\n\n// classifyStoredWidget decides, for a stored widget object of a NON-reserved or versioned\n// type, whether the catalog resolves it. Returns the reason it does not, or undefined.\n// `builtin` selects the legacy rule: a built-in with no metadata is fine.\nexport function unresolvedReason(\n type: string,\n rawRef: unknown,\n catalog: WidgetCatalog | undefined,\n builtin: boolean,\n): UnresolvedWidgetReason | undefined {\n const ref = parseWidgetRef(rawRef);\n if (ref === null) {\n return { code: 'invalid-metadata', message: `\"${type}\" carries widget version metadata that is not valid` };\n }\n if (!catalog) {\n return builtin\n ? undefined\n : { code: 'unregistered', message: `\"${type}\" is not available: no widget catalog was supplied` };\n }\n const descriptor = catalog.get(type);\n if (!descriptor) {\n return {\n code: 'unregistered',\n message: builtin\n ? `\"${type}\" is not registered in this host`\n : `\"${type}\" is not installed in this host; add its package to the host's widget registry`,\n };\n }\n if (ref === undefined) {\n return builtin\n ? undefined\n : { code: 'unversioned', message: `\"${type}\" is stored without version metadata, so the installed widget cannot be matched to it` };\n }\n return checkWidgetCompatibility(type, ref, descriptor);\n}\n\nexport interface UnresolvedWidgetReport {\n widgetId: string;\n type: string;\n reason: UnresolvedWidgetReason;\n}\n\n// unresolvedWidgets lists every widget on the board that is a placeholder, with its reason.\n// A publish gate uses it: loading, layout edits and draft saves stay possible, but a new\n// version should not be published while a widget cannot be resolved. A placeholder built\n// by an older parser (no reason) is reported as unregistered.\nexport function unresolvedWidgets(definition: Pick<DashboardDefinition, 'widgets'>): UnresolvedWidgetReport[] {\n const out: UnresolvedWidgetReport[] = [];\n for (const w of definition.widgets as readonly WidgetInstance[]) {\n if (w.type !== UNKNOWN_WIDGET_TYPE) continue;\n const storedType = typeof w.raw.type === 'string' ? w.raw.type : '';\n out.push({\n widgetId: w.id,\n type: storedType,\n reason: w.reason ?? { code: 'unregistered', message: `\"${storedType}\" is not available in this host` },\n });\n }\n return out;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Load-time helpers for a stored dashboard definition.\n//\n// dashboard-management stores the definition as opaque JSON (well-formed object,\n// size-bounded \u2014 nothing more). This package owns the *shape*, so validation and\n// default-filling live here rather than on the server: parseDashboardDefinition\n// turns an untrusted parsed value into a DashboardDefinition or throws, so the\n// renderer never has to guess at a missing canvas/breakpoint/box. Kept permissive\n// where a sensible default exists (a bare `{ widgets: [] }` is valid) and strict\n// where a wrong value would silently mis-render (a widget with no type, no base box).\n//\n// A widget whose type this build does not know is NOT an error: it is a widget a newer\n// release added. It parses to a placeholder that keeps the stored object verbatim, and\n// serializeDefinition writes that object back (see UnknownWidgetInstance), so an older\n// host that loads and saves a newer board leaves the newer widget exactly as it was.\n\nimport {\n CURRENT_SCHEMA_VERSION,\n FRAME_BORDER_WIDTHS,\n FRAME_HEADERS,\n FRAME_RADII,\n FRAME_VARIANTS,\n PLACEMENT_ANCHORS,\n RESERVED_PLACEMENT_MODES,\n THEME_COLOR_TOKENS,\n WIDGET_TYPES,\n isCanvasDensity,\n type AnchorTarget,\n type BoardTimeRange,\n type Breakpoints,\n type Canvas,\n type CanvasGrid,\n type CanvasSizing,\n type DashboardDefinition,\n type ExtensionWidgetInstance,\n type ExtensionWidgetType,\n type FrameBorder,\n type GridLength,\n type KnownWidgetInstance,\n type LocationSelection,\n type Placement,\n type PlacementAnchor,\n type SlotBinding,\n type SlotDefinition,\n type SlotCandidates,\n type SlotScope,\n type WidgetFrameSpec,\n type WidgetImplementationRef,\n type WidgetInstance,\n type WidgetLayout,\n type UnknownWidgetInstance,\n type UnresolvedWidgetReason,\n type WidgetType,\n UNKNOWN_WIDGET_TYPE,\n} from './types';\nimport {\n BASE_BREAKPOINT,\n DEFAULT_GRID,\n gridFootprintsOverlap,\n isSpaceToken,\n layoutBreakpoints,\n resolvePlacement,\n} from './grid';\nimport { convertLegacyDefinition, GRID_SCHEMA_VERSION, storedSchemaVersion } from './legacy-grid';\nimport { convertSampleWindows } from './legacy-window';\nimport { isValidSpanSeconds } from './time-range';\nimport { parseWidgetRef, unresolvedReason } from './widget-compat';\nimport type { WidgetCatalog } from './widget-catalog';\nimport { isRecord } from './is-record';\n\n// The breakpoint every layout must define; a widget/viewport with no more specific placement\n// falls back to it.\nexport { BASE_BREAKPOINT };\n\nconst WIDGET_TYPE_SET: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\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\".\n//\n// `code` is set only when the document is one this build must not read at all:\n// 'schemaTooNew' means it was saved by a newer release (its schemaVersion, `found`, is above\n// CURRENT_SCHEMA_VERSION), and reading it as the current format would draw it wrong without\n// saying so (every widget collapsed to a single cell). It is the same class, so a caller that\n// knows nothing of the code still refuses the document; a caller that wants to say WHY tests\n// `code`, which -- unlike `instanceof` -- also holds across two bundled copies of this package.\nexport class DashboardDefinitionError extends Error {\n constructor(\n message: string,\n readonly code?: 'schemaTooNew',\n readonly found?: number,\n ) {\n super(`invalid dashboard definition: ${message}`);\n this.name = 'DashboardDefinitionError';\n }\n}\n\nfunction numberAt(rec: Record<string, unknown>, key: string, fallback: number): number {\n const v = rec[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\n// parseDashboardDefinition validates a parsed JSON value and returns a definition\n// with defaults filled, or throws DashboardDefinitionError. `raw` is the value\n// after JSON.parse (the caller owns the parse so a syntax error is theirs to\n// handle); an already-typed object round-trips unchanged, an unknown-type\n// placeholder included.\n//\n// `options.catalog` is the React-free view of the host's widget registry. With it, a\n// namespaced extension id the catalog holds parses to an ExtensionWidgetInstance, and a\n// stored `widget` version reference is checked against the installed descriptor; a widget\n// the catalog cannot resolve (absent, no/invalid metadata, settings or SDK generation\n// differing) becomes a placeholder carrying its reason and its stored JSON. Without a\n// catalog the parser knows the built-ins only (legacy behavior), and any other id is a\n// placeholder.\nexport interface ParseDefinitionOptions {\n catalog?: WidgetCatalog;\n}\n\nexport function parseDashboardDefinition(input: unknown, options: ParseDefinitionOptions = {}): DashboardDefinition {\n if (!isRecord(input)) throw new DashboardDefinitionError('not a JSON object');\n // Before anything reads the shape: a newer format is refused whole, not read as this one.\n const stored = storedSchemaVersion(input);\n if (stored > CURRENT_SCHEMA_VERSION) {\n throw new DashboardDefinitionError(`schemaVersion ${stored} is too new`, 'schemaTooNew', stored);\n }\n if (!Array.isArray(input.widgets)) throw new DashboardDefinitionError('widgets must be an array');\n\n // A document stored in an older format is converted on load, one step at a time: the square-cell\n // grid (approximately; see ./legacy-grid), then the time span (./legacy-window). The result\n // says so. Everything below reads the current format only.\n const legacy = stored < CURRENT_SCHEMA_VERSION;\n const gridConverted = stored < GRID_SCHEMA_VERSION;\n let raw: Record<string, unknown> = input;\n if (gridConverted) raw = convertLegacyDefinition(raw);\n let flaggedWindows: number[] = [];\n if (legacy) {\n const converted = convertSampleWindows(raw);\n raw = converted.doc;\n flaggedWindows = converted.flagged;\n }\n const widgetsRaw = raw.widgets as unknown[];\n\n const def: DashboardDefinition = {\n // The format this release writes (a newer one was refused above): a save writes that format.\n schemaVersion: CURRENT_SCHEMA_VERSION,\n title: typeof raw.title === 'string' ? raw.title : '',\n canvas: parseCanvas(raw.canvas),\n widgets: widgetsRaw.map((w, i) => parseWidget(w, i, options.catalog)),\n };\n // slots \u2014 the runtime-binding section (ADR-039). Normalized when present so a\n // stored definition round-trips, but omitted entirely otherwise so a slot-free\n // dashboard serializes unchanged (no spurious `\"slots\":{}` diff).\n const slots = parseSlots(raw.slots);\n if (slots) def.slots = slots;\n const timeRange = parseBoardTimeRange(raw.timeRange);\n if (timeRange) def.timeRange = timeRange;\n validatePlacements(def);\n // Only a conversion that changed something worth reviewing leaves a mark: a re-laid-out grid\n // (an empty board has no layout to review), or a chart that lost its sample-count window. A\n // v2 board with nothing to flag converts silently -- it lost nothing. A typed definition handed\n // back to the parser keeps the marks it already carries.\n const flaggedIds = flaggedWindows.map((i) => def.widgets[i]?.id).filter((id): id is string => id !== undefined);\n if ((gridConverted && def.widgets.length > 0) || flaggedIds.length > 0) def.convertedFrom = stored;\n else if (typeof raw.convertedFrom === 'number') def.convertedFrom = raw.convertedFrom;\n if (flaggedIds.length > 0) def.convertedWindows = flaggedIds;\n else if (Array.isArray(raw.convertedWindows) && raw.convertedWindows.every((id) => typeof id === 'string')) {\n def.convertedWindows = raw.convertedWindows as string[];\n }\n return def;\n}\n\n// validatePlacements refuses a layout that cannot be drawn as declared, at every breakpoint a\n// widget can be shown at (its own placement there, or its base one):\n// - two GRID footprints that overlap. Overlap is something a widget declares (the relative\n// mode), never something that happens to a grid widget;\n// - a relative widget whose host does not exist, is itself, or is a relative widget whose own\n// host is relative too (a chain deeper than one level).\n// It throws rather than degrading: a definition that overlaps would draw one widget over another\n// with nothing to say why.\nfunction validatePlacements(def: DashboardDefinition): void {\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets)) {\n const at = bp === BASE_BREAKPOINT ? '' : ` at breakpoint '${bp}'`;\n const placements = new Map<string, Placement>();\n for (const w of def.widgets) placements.set(w.id, resolvePlacement(w.layout, bp));\n const grid = [...placements].filter((e): e is [string, Extract<Placement, { mode: 'grid' }>] => e[1].mode === 'grid');\n for (let i = 0; i < grid.length; i++) {\n for (let j = i + 1; j < grid.length; j++) {\n if (gridFootprintsOverlap(grid[i][1], grid[j][1])) {\n throw new DashboardDefinitionError(\n `widgets '${grid[i][0]}' and '${grid[j][0]}' overlap on the grid${at}; ` +\n `a widget that is meant to sit over another is placed relative to it`,\n );\n }\n }\n }\n for (const [id, p] of placements) {\n if (p.mode !== 'relative') continue;\n if (p.host === id) throw new DashboardDefinitionError(`widget '${id}' is placed relative to itself${at}`);\n const host = placements.get(p.host);\n if (!host) {\n throw new DashboardDefinitionError(`widget '${id}' is placed relative to '${p.host}', which does not exist${at}`);\n }\n if (host.mode === 'relative' && placements.get(host.host)?.mode !== 'grid') {\n throw new DashboardDefinitionError(\n `widget '${id}' is placed relative to '${p.host}', which is relative to another relative widget${at}; ` +\n `a relative widget's host may be relative only when that host sits on a grid widget`,\n );\n }\n }\n }\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 const candidates = parseCandidates(spec.candidates);\n if (candidates) slot.candidates = candidates;\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// parseCandidates reads a root anchor slot's picker type filter: non-empty string tokens only,\n// anything else dropped, and nothing left means no filter.\nfunction parseCandidates(raw: unknown): SlotCandidates | undefined {\n if (!isRecord(raw)) return undefined;\n const token = (v: unknown) => (typeof v === 'string' && v ? v : undefined);\n const areaType = token(raw.areaType);\n const assetType = token(raw.assetType);\n return areaType || assetType ? { areaType, assetType } : undefined;\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// parseGridLength reads one length: a finite number (clamped to >= 0) or a spacing token. An\n// absent value takes the fallback; any other string or type is refused, since a misspelled token\n// would otherwise draw a different board from the one authored.\nfunction parseGridLength(raw: unknown, fallback: GridLength, where: string): GridLength {\n if (raw === undefined) return fallback;\n if (typeof raw === 'number' && Number.isFinite(raw)) return Math.max(0, raw);\n if (isSpaceToken(raw)) return raw;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; a length is a number of px or a space-0..space-4 token`);\n}\n\n// parseGrid coerces the canvas grid, filling defaults. `columns` is floored to an integer >= 1\n// and `minCellSize` to >= 1, so neither can yield an unusable grid. `gutter` is one length or an\n// {x,y} pair; `padding` one length or a {top,right,bottom,left} set; a side an object leaves\n// out takes 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 minCellSize = Math.max(1, numberAt(rec, 'minCellSize', DEFAULT_GRID.minCellSize));\n const dg = DEFAULT_GRID.gutter as GridLength;\n const gutter: CanvasGrid['gutter'] = isRecord(rec.gutter)\n ? {\n x: parseGridLength(rec.gutter.x, dg, 'canvas.grid.gutter.x'),\n y: parseGridLength(rec.gutter.y, dg, 'canvas.grid.gutter.y'),\n }\n : parseGridLength(rec.gutter, dg, 'canvas.grid.gutter');\n const dp = DEFAULT_GRID.padding as GridLength;\n const padding: CanvasGrid['padding'] = isRecord(rec.padding)\n ? {\n top: parseGridLength(rec.padding.top, dp, 'canvas.grid.padding.top'),\n right: parseGridLength(rec.padding.right, dp, 'canvas.grid.padding.right'),\n bottom: parseGridLength(rec.padding.bottom, dp, 'canvas.grid.padding.bottom'),\n left: parseGridLength(rec.padding.left, dp, 'canvas.grid.padding.left'),\n }\n : parseGridLength(rec.padding, dp, 'canvas.grid.padding');\n return { columns, minCellSize, gutter, padding };\n}\n\n// parseBreakpoints keeps every well-formed breakpoint (a min width, or {minWidth, columns}) and\n// drops a malformed one, as it always has. `base` is added at 0 when absent; it is always in the\n// grid's own column count, so a `base` that declares another count is refused.\nfunction parseBreakpoints(raw: unknown, grid: CanvasGrid): Breakpoints {\n const rec = isRecord(raw) ? raw : {};\n const breakpoints: Breakpoints = {};\n for (const [name, spec] of Object.entries(rec)) {\n if (name === '__proto__') continue;\n if (typeof spec === 'number' && Number.isFinite(spec)) {\n breakpoints[name] = spec;\n } else if (\n isRecord(spec) &&\n typeof spec.minWidth === 'number' &&\n Number.isFinite(spec.minWidth) &&\n typeof spec.columns === 'number' &&\n Number.isInteger(spec.columns) &&\n spec.columns >= 1\n ) {\n breakpoints[name] = { minWidth: spec.minWidth, columns: spec.columns };\n }\n }\n const base = breakpoints[BASE_BREAKPOINT];\n if (base === undefined) breakpoints[BASE_BREAKPOINT] = 0;\n else if (typeof base === 'object' && base.columns !== grid.columns) {\n throw new DashboardDefinitionError(\n `breakpoint '${BASE_BREAKPOINT}' declares ${base.columns} columns; the base layout is in canvas.grid.columns (${grid.columns})`,\n );\n }\n return breakpoints;\n}\n\nfunction oneOf<T extends string | number>(values: readonly T[], raw: unknown, where: string): T | undefined {\n if (raw === undefined) return undefined;\n if ((values as readonly unknown[]).includes(raw)) return raw as T;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; expected one of ${values.map((v) => JSON.stringify(v)).join(', ')}`);\n}\n\n// parseFrame reads a frame (canvas default or widget override). Every value is a token from a\n// closed list and anything else is REFUSED, a free colour above all: a frame that quietly dropped\n// `#f00` would render a different card from the one authored, and one that kept it would break\n// dark mode and the ISA-101 theme. Keys a frame does not have are ignored.\nfunction parseFrame(raw: unknown, where: string): WidgetFrameSpec | undefined {\n if (raw === undefined) return undefined;\n if (!isRecord(raw)) throw new DashboardDefinitionError(`${where} is not an object`);\n const frame: WidgetFrameSpec = {};\n const variant = oneOf(FRAME_VARIANTS, raw.variant, `${where}.variant`);\n if (variant) frame.variant = variant;\n if (raw.border !== undefined) {\n if (!isRecord(raw.border)) throw new DashboardDefinitionError(`${where}.border is not an object`);\n const border: FrameBorder = {};\n const width = oneOf(FRAME_BORDER_WIDTHS, raw.border.width, `${where}.border.width`);\n if (width !== undefined) border.width = width;\n const radius = oneOf(FRAME_RADII, raw.border.radius, `${where}.border.radius`);\n if (radius) border.radius = radius;\n const color = oneOf(THEME_COLOR_TOKENS, raw.border.color, `${where}.border.color`);\n if (color) border.color = color;\n frame.border = border;\n }\n if (raw.padding !== undefined) {\n if (!isSpaceToken(raw.padding)) {\n throw new DashboardDefinitionError(`${where}.padding is ${JSON.stringify(raw.padding)}; expected a space-0..space-4 token`);\n }\n frame.padding = raw.padding;\n }\n const background = oneOf([...THEME_COLOR_TOKENS, 'transparent' as const], raw.background, `${where}.background`);\n if (background) frame.background = background;\n const header = oneOf(FRAME_HEADERS, raw.header, `${where}.header`);\n if (header) frame.header = header;\n return frame;\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\n// parseBoardTimeRange reads the board's authored default span. A value that is not a usable span\n// (not an integer, under a minute, over a week) is IGNORED, so the board draws the default hour:\n// failing the whole board for a bad range would hide every widget on it, and clamping would turn\n// a typo into a window nobody wrote.\nfunction parseBoardTimeRange(raw: unknown): BoardTimeRange | undefined {\n if (!isRecord(raw) || !isValidSpanSeconds(raw.defaultSeconds)) return undefined;\n return { defaultSeconds: raw.defaultSeconds };\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 // Breakpoints must define 'base'; a definition that omits responsive layouts gets base:0.\n const breakpoints = parseBreakpoints(rec.breakpoints, grid);\n\n const canvas: Canvas = { grid, sizing, breakpoints };\n const frame = parseFrame(rec.frame, 'canvas.frame');\n if (frame) canvas.frame = frame;\n // An unknown value falls back to the default (absent) rather than failing the parse: a\n // definition written by a newer release still renders.\n if (isCanvasDensity(rec.density)) canvas.density = rec.density;\n if (isRecord(rec.background)) {\n const { color, imageUrl } = rec.background;\n canvas.background = {\n color: typeof color === 'string' ? color : null,\n imageUrl: typeof imageUrl === 'string' ? imageUrl : null,\n };\n }\n return canvas;\n}\n\nfunction parseWidget(raw: unknown, index: number, catalog: WidgetCatalog | undefined): WidgetInstance {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}] is not an object`);\n\n const type = raw.type;\n // A missing, empty or non-string type names nothing a newer viewer could render: the\n // document is broken, not newer.\n if (typeof type !== 'string' || type.length === 0) {\n throw new DashboardDefinitionError(`widgets[${index}] has unknown type ${JSON.stringify(type)}`);\n }\n if (type === UNKNOWN_WIDGET_TYPE && isRecord(raw.raw)) return reparseUnknownWidget(raw, index, catalog);\n\n const builtin = WIDGET_TYPE_SET.has(type);\n const reason = unresolvedReason(type, raw.widget, catalog, builtin) ?? (builtin ? undefined : reservedFieldReason(type, raw));\n if (reason) return parseUnknownWidget(raw, index, reason);\n\n const ref = parseWidgetRef(raw.widget) ?? undefined;\n const widget: KnownWidgetInstance | ExtensionWidgetInstance = builtin\n ? {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as WidgetType,\n ...(ref ? { widget: ref } : {}),\n layout: parseLayout(raw.layout, index),\n }\n : {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as ExtensionWidgetType,\n widget: ref as WidgetImplementationRef, // unresolvedReason guarantees a valid ref here\n layout: parseLayout(raw.layout, index),\n };\n // datasource is owned by the hub, but the two supported kinds are NORMALIZED here\n // so downstream (hub/widgets) never sees a partial shape (a device selector with\n // no measurements array, an anchor with a non-object target, \u2026). Reserved/other\n // kinds are carried through opaquely \u2014 the hub rejects them.\n const ds = parseDatasource(raw.datasource, index);\n if (ds) widget.datasource = ds;\n if (isRecord(raw.options)) widget.options = raw.options as Record<string, unknown>;\n const frame = parseFrame(raw.frame, `widgets[${index}].frame`);\n if (frame) widget.frame = frame;\n if (!builtin) return withStoredFields(raw, widget as ExtensionWidgetInstance);\n return widget;\n}\n\n// The envelope fields the parser owns on a resolved widget. Everything else a stored widget\n// carries is outside the owned schema.\nconst OWNED_WIDGET_KEYS: ReadonlySet<string> = new Set(['id', 'type', 'widget', 'layout', 'datasource', 'options', 'frame']);\n\n// withStoredFields keeps, on a resolved EXTENSION widget, every stored field the envelope does\n// not own (a package's own top-level additions, extra keys inside `widget`), in the stored key\n// order, so a load/save round trip writes them back byte-for-byte. Owned fields take their\n// parsed (normalized) values. Built-ins are deliberately left as they always were (only the\n// owned envelope survives), so an existing board's saved bytes do not change.\nfunction withStoredFields(raw: Record<string, unknown>, parsed: ExtensionWidgetInstance): ExtensionWidgetInstance {\n const owned = parsed as unknown as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n // defineProperty, not assignment: a JSON.parse'd `__proto__` key is an own field to keep,\n // and assigning it would set the prototype instead.\n const put = (key: string, value: unknown) =>\n Object.defineProperty(out, key, { value, enumerable: true, writable: true, configurable: true });\n for (const key of Object.keys(raw)) {\n if (!OWNED_WIDGET_KEYS.has(key)) put(key, JSON.parse(JSON.stringify(raw[key])) as unknown);\n else if (key === 'widget') put(key, { ...(raw.widget as Record<string, unknown>), ...parsed.widget });\n else if (owned[key] !== undefined) put(key, owned[key]);\n }\n for (const key of OWNED_WIDGET_KEYS) {\n if (!Object.prototype.hasOwnProperty.call(out, key) && owned[key] !== undefined) put(key, owned[key]);\n }\n return out as unknown as ExtensionWidgetInstance;\n}\n\n// A stored extension widget that uses a field name the parsed model reserves for placeholders\n// cannot be carried on a resolved instance without being mistaken for one, so it is kept\n// verbatim as a placeholder instead of having the field dropped.\nfunction reservedFieldReason(type: string, raw: Record<string, unknown>): UnresolvedWidgetReason | undefined {\n const clash = ['raw', 'reason'].find((k) => Object.prototype.hasOwnProperty.call(raw, k));\n return clash === undefined\n ? undefined\n : { code: 'invalid-metadata', message: `\"${type}\" carries a top-level \"${clash}\" field, which the dashboard format reserves` };\n}\n\n// The id a stored widget carries, or undefined when it has none worth keeping.\nfunction storedWidgetId(raw: Record<string, unknown>): string | undefined {\n return typeof raw.id === 'string' && raw.id.length > 0 ? raw.id : undefined;\n}\n\n// parseUnknownWidget builds the placeholder for a widget of a type this build does not\n// know. Only `id` and `layout` are read, through the same rules as every other widget,\n// so the canvas can place it; a layout with no base box still fails the parse, because\n// a widget that cannot be placed cannot be shown even as a placeholder.\n//\n// `raw` is a JSON copy of the stored object: detached from the caller's value (which a\n// host may go on to mutate), and exactly what JSON.stringify will write back.\nfunction parseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n reason: UnresolvedWidgetReason,\n): UnknownWidgetInstance {\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: JSON.parse(JSON.stringify(raw)) as Record<string, unknown>,\n reason,\n };\n}\n\n// reparseUnknownWidget takes back a placeholder this parser already built \u2014 a typed\n// definition handed to the parser again \u2014 as the same placeholder: its stored object is\n// kept as its raw, and its id and layout (which may carry an edit) come from the\n// placeholder. Wrapping it again would make the save write the wrapper and lose the\n// widget. The serializer never writes the placeholder type, so no stored document takes\n// this path.\nfunction reparseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n catalog: WidgetCatalog | undefined,\n): UnknownWidgetInstance {\n const stored = JSON.parse(JSON.stringify(raw.raw)) as Record<string, unknown>;\n const storedType = typeof stored.type === 'string' ? stored.type : '';\n // The reason is recomputed against THIS catalog (it is never stored); a stored object the\n // catalog now resolves stays a placeholder here, because only a parse of the stored\n // document \u2014 not a re-parse of a typed definition \u2014 promotes a widget.\n const reason = unresolvedReason(storedType, stored.widget, catalog, WIDGET_TYPE_SET.has(storedType)) ?? {\n code: 'unregistered' as const,\n message: `\"${storedType}\" is not available in this host`,\n };\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: stored,\n reason,\n };\n}\n\nfunction stringAt(rec: Record<string, unknown>, key: string): string {\n const v = rec[key];\n return typeof v === 'string' ? v : '';\n}\n\nfunction stringArrayAt(rec: Record<string, unknown>, key: string): string[] {\n const v = rec[key];\n return Array.isArray(v) ? v.filter((m): m is string => typeof m === 'string') : [];\n}\n\n// parseLocationSelection reads a selector's OPTIONAL location-series field.\n//\n// \uD83D\uDD34 Additive, and it has to stay that way: every definition stored before the location\n// channel existed carries no `location`, and undefined here is exactly right for all of\n// them \u2014 the selector names no location series, which is true. Nothing about the\n// measurement path is touched, so the regression risk is confined to what an author\n// deliberately adds.\n//\n// The vocabulary is closed, so an unrecognized series is DROPPED rather than carried\n// through: the hub resolves a location scope only for a selector naming a series it\n// understands, and carrying an unknown one would produce a widget that looks configured\n// and reads nothing. Dropping it makes the widget's empty state honest.\nfunction parseLocationSelection(raw: unknown): LocationSelection | undefined {\n if (!isRecord(raw)) return undefined;\n return raw.series === 'latest' ? { series: 'latest' } : undefined;\n}\n\n// withLocation attaches a parsed location selection to a selector, and OMITS the key\n// entirely when there is none \u2014 so a measurement-only definition serializes byte-for-\n// byte as it did before this field existed (no spurious `\"location\":undefined` in the\n// diff, and isDirty stays quiet on a load/save round trip).\nfunction withLocation<S extends object>(selector: S, raw: Record<string, unknown>): S {\n const location = parseLocationSelection(raw.location);\n return location ? { ...selector, location } : selector;\n}\n\n// parseDatasource coerces a raw datasource into a normalized selector, or drops it\n// (returns undefined) when it is absent or its `kind` is not a non-empty string.\nfunction parseDatasource(raw: unknown, index: number): WidgetInstance['datasource'] | undefined {\n if (!isRecord(raw)) return undefined;\n const kind = raw.kind;\n if (typeof kind !== 'string' || kind.length === 0) return undefined;\n\n // \uD83D\uDD34 `unbound` is what the RENDERER produces for a slot with no binding; it is never\n // stored. Carried through, a stored one would render as an empty pane on every channel\n // with nothing to say why \u2014 the shape of a correctly unbound slot, not of a broken\n // definition. A widget with no entity is stored as a `slot` with no binding.\n if (kind === 'unbound') {\n throw new DashboardDefinitionError(\n `widgets[${index}].datasource has kind 'unbound', which is never stored; bind the widget through a slot`,\n );\n }\n\n if (kind === 'device') {\n return withLocation(\n { kind: 'device' as const, deviceToken: stringAt(raw, 'deviceToken'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n if (kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const selector: WidgetInstance['datasource'] = withLocation(\n {\n kind: 'anchor' as const,\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n // targetType defaults to '' (the config panel constrains it to the union;\n // a hand-edited/empty value round-trips rather than being silently coerced).\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken: stringAt(anchorRec, 'targetToken'),\n },\n measurements: stringArrayAt(raw, 'measurements'),\n },\n raw,\n );\n if (isRecord(raw.aggregation)) {\n (selector as { aggregation?: unknown }).aggregation = raw.aggregation;\n }\n return selector;\n }\n\n if (kind === 'slot') {\n // Runtime-binding kind: the entity is resolved at mount via the Hub's binding\n // manifest; the widget carries only the slot name + the series it wants (the\n // measurement names, and/or the location series).\n return withLocation(\n { kind: 'slot' as const, slot: stringAt(raw, 'slot'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n\n // Reserved/other kinds: carry through opaquely (the hub rejects them).\n return raw as unknown as WidgetInstance['datasource'];\n}\n\nfunction parseLayout(raw: unknown, index: number): WidgetLayout {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}].layout is missing`);\n\n const layout: WidgetLayout = {};\n for (const [bp, placement] of Object.entries(raw)) {\n if (bp === '__proto__') continue;\n if (isRecord(placement)) layout[bp] = parsePlacement(placement, `widgets[${index}].layout.${bp}`);\n }\n if (!Object.prototype.hasOwnProperty.call(layout, BASE_BREAKPOINT)) {\n throw new DashboardDefinitionError(`widgets[${index}].layout has no '${BASE_BREAKPOINT}' placement`);\n }\n return layout;\n}\n\n// A whole number of cells >= min, or a refusal. Required: a span the author left out has no\n// value this parser could honestly pick.\nfunction cellCount(rec: Record<string, unknown>, key: string, min: number, where: string): number {\n const v = rec[key];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new DashboardDefinitionError(`${where}.${key} is ${JSON.stringify(v)}; expected a number of cells`);\n }\n return Math.max(min, Math.round(v));\n}\n\n// parsePlacement reads one breakpoint's placement. A missing `mode` is a grid placement. Every\n// value the mode needs is required and checked: a placement is where a widget is drawn, and a\n// guessed one draws it somewhere nobody chose.\nfunction parsePlacement(rec: Record<string, unknown>, where: string): Placement {\n const mode = rec.mode ?? 'grid';\n if (mode === 'grid') {\n return {\n mode: 'grid',\n col: cellCount(rec, 'col', 0, where),\n row: cellCount(rec, 'row', 0, where),\n cols: cellCount(rec, 'cols', 1, where),\n rows: cellCount(rec, 'rows', 1, where),\n };\n }\n if (mode === 'relative') return parseRelative(rec, where);\n if ((RESERVED_PLACEMENT_MODES as readonly unknown[]).includes(mode)) {\n throw new DashboardDefinitionError(`${where}.mode '${String(mode)}' is reserved and not supported by this release`);\n }\n throw new DashboardDefinitionError(`${where}.mode is ${JSON.stringify(mode)}; expected 'grid' or 'relative'`);\n}\n\nfunction parseAnchor(raw: unknown, where: string): PlacementAnchor {\n if ((PLACEMENT_ANCHORS as readonly unknown[]).includes(raw)) return raw as PlacementAnchor;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; expected one of ${PLACEMENT_ANCHORS.join(', ')}`);\n}\n\nfunction parseRelative(rec: Record<string, unknown>, where: string): Placement {\n if (typeof rec.host !== 'string' || rec.host.length === 0) {\n throw new DashboardDefinitionError(`${where}.host is ${JSON.stringify(rec.host)}; a relative widget names the widget it is placed against`);\n }\n const placement: Extract<Placement, { mode: 'relative' }> = {\n mode: 'relative',\n host: rec.host,\n anchor: parseAnchor(rec.anchor, `${where}.anchor`),\n size: { fill: 'host' },\n };\n if (rec.self !== undefined) placement.self = parseAnchor(rec.self, `${where}.self`);\n if (rec.offset !== undefined) {\n const o = rec.offset;\n if (!isRecord(o) || !isFiniteNumber(o.x) || !isFiniteNumber(o.y)) {\n throw new DashboardDefinitionError(`${where}.offset is ${JSON.stringify(o)}; expected {x, y} in cells`);\n }\n placement.offset = { x: o.x, y: o.y };\n }\n const size = rec.size;\n if (isRecord(size) && size.fill === 'host') {\n placement.size = { fill: 'host' };\n } else if (isRecord(size)) {\n placement.size = { cols: cellCount(size, 'cols', 1, `${where}.size`), rows: cellCount(size, 'rows', 1, `${where}.size`) };\n } else {\n throw new DashboardDefinitionError(`${where}.size is ${JSON.stringify(size)}; expected {cols, rows} or {fill: 'host'}`);\n }\n if (rec.layer !== undefined) {\n if (!isFiniteNumber(rec.layer) || !Number.isInteger(rec.layer)) {\n throw new DashboardDefinitionError(`${where}.layer is ${JSON.stringify(rec.layer)}; expected a whole number`);\n }\n placement.layer = rec.layer;\n }\n return placement;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\n// The stored shape of a definition: what serializeDefinition writes. A placeholder for\n// an unknown widget type is written as the stored object it was parsed from.\nexport type StoredDashboardDefinition = Omit<DashboardDefinition, 'widgets'> & {\n widgets: Array<KnownWidgetInstance | ExtensionWidgetInstance | Record<string, unknown>>;\n};\n\n// storedDefinition is the definition as it is stored: every unknown-type placeholder\n// replaced by its raw object (carrying any move or id change the editor made to it).\n// Anything that writes a definition out \u2014 a save, a copy-as-JSON export \u2014 goes through\n// this, because JSON.stringify of the parsed model would write the placeholder wrapper\n// and lose the widget. Returns the same reference when there is no placeholder.\n//\n// The parser's `convertedFrom` mark is never written: it describes how this copy was read, not\n// the board, and a save is exactly what retires it.\nexport function storedDefinition(def: DashboardDefinition): StoredDashboardDefinition {\n const unmarked = withoutConversionMark(def);\n if (!unmarked.widgets.some((w) => w.type === UNKNOWN_WIDGET_TYPE)) return unmarked as StoredDashboardDefinition;\n return {\n ...unmarked,\n widgets: unmarked.widgets.map((w) => (w.type === UNKNOWN_WIDGET_TYPE ? storedUnknownWidget(w) : w)),\n };\n}\n\n// storedUnknownWidget writes a placeholder back as its raw object. The raw object is\n// returned untouched unless the editor changed one of the two fields a placeholder\n// exposes: a changed id is written over the stored one, and a moved breakpoint box is\n// written over that breakpoint (every untouched breakpoint keeps its stored bytes). An\n// id the parser GENERATED for an id-less widget is not written, so the stored widget\n// stays id-less exactly as it was.\nfunction storedUnknownWidget(w: UnknownWidgetInstance): Record<string, unknown> {\n let out = w.raw;\n const rawId = storedWidgetId(w.raw);\n if (rawId !== undefined && rawId !== w.id) out = { ...out, id: w.id };\n const layout = storedUnknownLayout(w.layout, w.raw.layout);\n if (layout !== w.raw.layout) out = { ...out, layout };\n return out;\n}\n\n// storedUnknownLayout merges the placeholder's (parsed, possibly edited) layout into\n// the stored one, breakpoint by breakpoint. A stored box that parses to the same box is\n// kept as stored; a changed one is replaced; a breakpoint the stored layout did not\n// have is appended; a stored entry that is not a box at all (the parser skipped it) is\n// carried. Returns the stored reference when nothing changed.\nfunction storedUnknownLayout(layout: WidgetLayout, rawLayout: unknown): unknown {\n const stored = isRecord(rawLayout) ? rawLayout : {};\n const out: Record<string, unknown> = {};\n let changed = !isRecord(rawLayout);\n for (const [bp, box] of Object.entries(stored)) {\n if (!isRecord(box)) {\n out[bp] = box;\n continue;\n }\n const current = Object.prototype.hasOwnProperty.call(layout, bp) ? layout[bp] : undefined;\n if (!current) {\n changed = true; // the editor removed this breakpoint\n continue;\n }\n if (samePlacement(parsePlacement(box, bp), current)) {\n out[bp] = box;\n } else {\n out[bp] = movedPlacement(box, current);\n changed = true;\n }\n }\n for (const [bp, box] of Object.entries(layout)) {\n if (Object.prototype.hasOwnProperty.call(stored, bp) && isRecord(stored[bp])) continue;\n out[bp] = box;\n changed = true;\n }\n return changed ? out : rawLayout;\n}\n\n// Every field a placement has in this build, in either mode.\nconst PLACEMENT_KEYS: ReadonlySet<string> = new Set([\n 'mode', 'col', 'row', 'cols', 'rows', 'host', 'anchor', 'self', 'offset', 'size', 'layer',\n]);\n\n// movedPlacement writes an edited placement over a stored one. The edited placement replaces\n// every field this build knows (so a field the edit dropped, or one of the other mode, is gone);\n// any other field the stored entry carries (one a newer release added) is kept.\nfunction movedPlacement(stored: Record<string, unknown>, current: Placement): Record<string, unknown> {\n const out: Record<string, unknown> = { ...current };\n for (const [k, v] of Object.entries(stored)) if (!PLACEMENT_KEYS.has(k)) out[k] = v;\n return out;\n}\n\nfunction samePlacement(a: Placement, b: Placement): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n// serializeDefinition is the canonical on-the-wire JSON \u2014 the inverse of\n// parseDashboardDefinition \u2014 that dashboard-management stores. Named (not an inline\n// JSON.stringify) so every consumer that persists a definition shares one format.\nexport function serializeDefinition(def: DashboardDefinition): string {\n return JSON.stringify(storedDefinition(def));\n}\n\n// withoutConversionMark is the definition minus the parser's `convertedFrom` mark (the same\n// reference when it carries none): what an editor keeps once a save has written the current\n// format.\nexport function withoutConversionMark(def: DashboardDefinition): DashboardDefinition {\n if (def.convertedFrom === undefined && def.convertedWindows === undefined) return def;\n const { convertedFrom: _mark, convertedWindows: _windows, ...rest } = def;\n return rest;\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// generateWidgetId mints a unique widget-instance id. Uses crypto.randomUUID where\n// available (browsers, modern Node); the counter fallback keeps ids unique within a\n// session for the rare environment without it.\nlet idCounter = 0;\nexport function generateWidgetId(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return `w-${c.randomUUID()}`;\n idCounter += 1;\n return `w-${idCounter.toString(36)}`;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Runtime slot binding (ADR-039). A dashboard definition is a reusable TEMPLATE:\n// widgets reference named slots, and each slot declares a `defaultBinding` (the\n// authoring tenant's entity). At MOUNT the host may supply a manifest that overrides\n// any slot's binding \u2014 so one definition + two manifests renders as two live\n// dashboards on different entities. effectiveBindings computes the base manifest: the\n// slot defaults, overlaid by the host's overrides. The renderer resolves each widget's\n// slot through the settled manifest (resolveWidgetDatasource) before the hub sees it.\n\nimport { parseSlotBinding } from './definition';\nimport type { DashboardDefinition, DatasourceSelector, SlotBinding, SlotDefinition } from './types';\n\n// effectiveBindings merges a definition's slot default bindings with an optional\n// host manifest (manifest wins). Slots without a default and not in the manifest are\n// omitted \u2192 the renderer hands those widgets an `unbound` selector, an empty placeholder.\n//\n// This is the BASE layer (defaults + manifest), computed synchronously. For a dashboard\n// with SCOPED slots it is NOT the final manifest: a scoped slot's default here is a\n// fallback that resolveContextBindings (the cascade) supersedes with a value derived\n// from the parent. A host with scoped slots feeds this map as `base` into the cascade\n// and hands the renderer the cascade's output, not this map directly.\nexport function effectiveBindings(\n definition: DashboardDefinition,\n manifest?: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [name, slot] of Object.entries(definition.slots ?? {})) {\n if (slot.defaultBinding) out[name] = slot.defaultBinding;\n }\n if (manifest) {\n for (const [name, binding] of Object.entries(manifest)) out[name] = binding;\n }\n return out;\n}\n\n// What parseBindingManifest made of a host manifest: the bindings it accepted, and the\n// slot names it did NOT \u2014 in manifest order.\nexport interface ParsedBindingManifest {\n bindings: Record<string, SlotBinding>;\n dropped: string[];\n}\n\n// parseBindingManifest validates an untrusted host manifest (slot name \u2192 binding)\n// into a clean Record<slot, SlotBinding>, dropping malformed entries. The host of an\n// exported dashboard passes this to effectiveBindings to bind the definition's slots\n// to ITS entities: one definition + two manifests \u2192 two live dashboards.\n//\n// \uD83D\uDD34 IT REPORTS WHAT IT DROPPED, because a silent drop is this API's sharpest edge and\n// every host has to handle it. A typo'd binding does not fail \u2014 it vanishes, the slot\n// stays unbound, and the widgets on it render as empty frames with nothing anywhere\n// saying why. Returning the names makes that a thing a host can show a user. The\n// alternative every host would otherwise reach for is comparing key counts against its\n// own input, which infers the same fact less well: it cannot name the offender, and it\n// counts the `__proto__` refusal below as if it were malformed.\nexport function parseBindingManifest(raw: unknown): ParsedBindingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { bindings: {}, dropped: [] };\n }\n const bindings: Record<string, SlotBinding> = {};\n const dropped: string[] = [];\n for (const [slot, spec] of Object.entries(raw as Record<string, unknown>)) {\n // Skip a `__proto__` key: `out['__proto__'] = \u2026` would hit the prototype setter\n // (lose the binding + swap out's prototype) rather than set an own property.\n // Reported as dropped: it IS a binding the host asked for and did not get.\n if (slot === '__proto__') {\n dropped.push(slot);\n continue;\n }\n const binding = parseSlotBinding(spec);\n if (binding) bindings[slot] = binding;\n else dropped.push(slot);\n }\n return { bindings, dropped };\n}\n\n// stripDefaultBindings removes every slot's default binding, turning a concrete\n// dashboard into a TEMPLATE \u2014 the exported form a host must supply a manifest for\n// (each slot renders as a placeholder until bound). Slot names/types/labels are kept\n// so the importer knows what to bind. (Caveats: a slot's `label` is the author's\n// entity token \u2014 a naming hint, not stripped; and an anchor selector carrying a\n// Phase-2 `aggregation` stays concrete/un-slotted through migration, so it isn't\n// rebindable \u2014 neither is reachable through the console's authoring UI today.)\nexport function stripDefaultBindings(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) {\n const { defaultBinding: _drop, ...rest } = slot;\n slots[name] = rest;\n }\n return { ...def, slots };\n}\n\n// resolveWidgetDatasource turns a widget's datasource into the CONCRETE selector a data\n// source subscribes: a `slot` is looked up in the settled bindings and becomes the device\n// or anchor it is bound to, carrying the widget's own series (measurement names, and the\n// location series when it names one). Every other selector passes through unchanged.\n//\n// This is the renderer's job, not the hub's. The hub keeps no bindings, so a selection\n// that re-points one slot changes the concrete selector of exactly the widgets bound to\n// it \u2014 and only those re-subscribe, because every channel keys its subscription on the\n// selector's VALUE. A hub that resolved slots itself had to be rebuilt on every selection,\n// which tore down and re-fetched the whole board.\n//\n// \uD83D\uDD34 A slot with no binding becomes `{kind:'unbound'}`, NEVER `undefined`. An absent\n// datasource is tenant-wide on the alarm channel; an unbound slot is zero devices. The\n// two must not be confused, and this function is the one place they could be.\n//\n// The lookup is own-property only, so a slot named `constructor` or `__proto__` is\n// unbound rather than resolving to something on Object.prototype.\nexport function resolveWidgetDatasource(\n datasource: DatasourceSelector | undefined,\n bindings: Record<string, SlotBinding> | undefined,\n): DatasourceSelector | undefined {\n if (!datasource || datasource.kind !== 'slot') return datasource;\n const binding =\n bindings && Object.prototype.hasOwnProperty.call(bindings, datasource.slot)\n ? bindings[datasource.slot]\n : undefined;\n const series = datasource.location\n ? { measurements: datasource.measurements, location: datasource.location }\n : { measurements: datasource.measurements };\n if (binding?.kind === 'device') return { kind: 'device', deviceToken: binding.deviceToken, ...series };\n if (binding?.kind === 'anchor') return { kind: 'anchor', anchor: binding.anchor, ...series };\n return { kind: 'unbound', ...series };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// 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 entities of the slot's target type THE VIEWER MAY SEE (the lister\n// answers a customer user with the areas its devices are in), narrowed to\n// the slot's candidates.areaType/assetType when it declares one; 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 EntityListKind,\n SelectionCandidate,\n SlotBinding,\n SlotDefinition,\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\n// slotTypeFilter is the picker type filter a root anchor slot declares for its target kind: the\n// area type for an area slot, the asset type for an asset slot, none for anything else.\n// A lister MAY take the type token as a second argument (createEntityLister's does, and narrows the\n// areas or assets it reads); one that does not simply lists every entity of the kind. The public\n// EntityCandidateLister type stays one-argument, so a host's own lister keeps compiling.\nexport const listEntities = (\n lister: EntityCandidateLister,\n kind: EntityListKind,\n typeToken: string | undefined,\n): ReturnType<EntityCandidateLister> => (lister as (k: EntityListKind, t?: string) => ReturnType<EntityCandidateLister>)(kind, typeToken);\n\nexport function slotTypeFilter(def: SlotDefinition, targetType: string): string | undefined {\n return targetType === 'customer' ? undefined : def.candidates?.[targetType === 'area' ? 'areaType' : 'assetType'];\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 listEntities(lister, template.targetType, slotTypeFilter(def, 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// 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// The cascade as a VIEWER sees it. resolveContextBindings settles every slot from the board's own\n// defaults and the viewer's picks; this adds the one thing only the viewer's side can answer: is\n// the area or asset a root anchor slot is bound to one the viewer may pick? A board saved with\n// floor tp-tower-f4 and opened by a member who is not placed there would otherwise show an empty\n// floor under a name they cannot see. It falls back to the first entity the picker offers, for\n// every viewer (an operator's board pointing at a deleted area benefits too), by correcting the\n// root binding BEFORE the cascade derives the slots that follow it.\n//\n// It is a separate function, not a parameter of resolveContextBindings, so that function keeps the\n// signature published hosts call.\n\nimport { listEntities, slotTypeFilter } from './candidates';\nimport { resolveContextBindings, type MemberResolver } from './context';\nimport type { DashboardDefinition, EntityCandidateLister, SlotBinding } from './types';\n\nconst ownGet = <T>(map: Record<string, T>, key: string): T | undefined =>\n Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n\n// hasRootAnchorSlots reports whether any ROOT slot is an anchor slot: the slots whose binding the\n// viewer-side check applies to. A board without one has nothing to correct.\nexport function hasRootAnchorSlots(definition: DashboardDefinition): boolean {\n return Object.values(definition.slots ?? {}).some((s) => s.type === 'anchor' && !s.scope);\n}\n\n// resolveViewerBindings is resolveContextBindings plus the fallback. Fail-safe like the cascade: a\n// lister that is absent, fails or lists nothing keeps the binding, so an outage degrades to\n// today's behaviour and never to a blank board.\nexport async function resolveViewerBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n overlay: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister?: EntityCandidateLister,\n): Promise<Record<string, SlotBinding>> {\n const slots = definition.slots ?? {};\n let corrected: Record<string, SlotBinding> = { ...overlay };\n if (lister) {\n for (const name of Object.keys(slots)) {\n const slot = slots[name];\n if (slot.type !== 'anchor' || slot.scope) continue;\n // The binding the cascade would settle on: a type-compatible selection over the default.\n const picked = ownGet(overlay, name);\n const binding = picked?.kind === 'anchor' ? picked : ownGet(base, name);\n if (binding?.kind !== 'anchor') continue;\n const { targetType, targetToken } = binding.anchor;\n let rows: Array<{ token: string }>;\n try {\n rows = await listEntities(lister, targetType, slotTypeFilter(slot, targetType));\n } catch {\n continue;\n }\n if (rows.length > 0 && !rows.some((r) => r.token === targetToken)) {\n // A computed key in a literal makes an own property even for '__proto__'.\n corrected = { ...corrected, [name]: { kind: 'anchor', anchor: { ...binding.anchor, targetToken: rows[0].token } } };\n }\n }\n }\n return resolveContextBindings(definition, base, corrected, resolver);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Context deep links: a URL that opens a board with a device already bound.\n//\n// \u2026/dashboards/<board>?bind.<slot>=<deviceToken> (console)\n// /dash/?dashboard=<board>&bind.<slot>=<deviceToken> (the viewer app)\n//\n// One query parameter per slot, named for the slot, valued with the device token. The slot name\n// travels in the KEY (not in a `bind=slot:token` pair) so it needs no escaping rule of its own\n// and a second slot is simply a second parameter.\n//\n// This file is the pure half: reading the parameters against a definition, writing them back,\n// and asking the resolver whether the caller can see each device. The React half, which keeps\n// the address and the selection in step, is useBoundDevices in @devicechain/widgets.\n//\n// \uD83D\uDD34 A LINK IS A CLAIM, NOT A GRANT. A token in a URL is whatever its sender typed, so every\n// device a link names is checked with the CALLER'S OWN credentials before it is bound. The\n// server answers a device outside the caller's tenant or customer scope exactly as it answers a\n// device that does not exist (it must not confirm that a row the caller may not see exists), so\n// \"no such device\" and \"not yours\" are ONE outcome here, never two that could be told apart.\n\nimport { isValidToken } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport type { DashboardDefinition, SlotBinding } from './types';\n\n// The query-parameter prefix a bound slot travels under.\nexport const BIND_PARAM_PREFIX = 'bind.';\n\n// Why a parameter was set aside without being asked of the server.\nexport type BindParamIssue =\n // `bind.x` where the board has no slot `x` (a link for another board, or a renamed slot).\n | { kind: 'unknown-slot'; slot: string; token: string }\n // `bind.x` where slot `x` is an anchor slot: a link binds a single device.\n | { kind: 'not-a-device-slot'; slot: string; token: string }\n // The value is not a token at all.\n | { kind: 'invalid-token'; slot: string; token: string }\n // `bind.x` given more than once. Which one the link \"means\" depends on which parser reads it\n // (first wins in one, last in another), so the link is ambiguous and neither is asked of the\n // server. `token` is the first value.\n | { kind: 'duplicate-slot'; slot: string; token: string }\n // `bind.x` where slot `x` follows its parent (`scope.strategy: 'first'`): the board derives\n // that slot's device and ignores any pick, so honouring the link would mean showing a device\n // other than the one in the address.\n | { kind: 'derived-slot'; slot: string; token: string };\n\nexport interface ParsedBinds {\n // slot \u2192 device token, for the parameters that name a real device slot with a well-formed token.\n binds: Record<string, string>;\n // Everything else a `bind.` parameter could have been, so the viewer is told rather than\n // shown a board that silently ignored half the link.\n issues: BindParamIssue[];\n}\n\nfunction ownSlot(definition: DashboardDefinition, slot: string) {\n const slots = definition.slots;\n return slots && Object.prototype.hasOwnProperty.call(slots, slot) ? slots[slot] : undefined;\n}\n\n// parseBindParams reads the `bind.<slot>` parameters of a query string against a board. Pure and\n// synchronous: it checks SHAPE only. Whether the device exists, and is the caller's to see, is\n// validateBinds.\nexport function parseBindParams(search: string, definition: DashboardDefinition): ParsedBinds {\n const binds: Record<string, string> = {};\n const issues: BindParamIssue[] = [];\n const params = new URLSearchParams(search);\n const seen = new Set<string>();\n for (const [key, token] of params) {\n if (!key.startsWith(BIND_PARAM_PREFIX)) continue;\n const slot = key.slice(BIND_PARAM_PREFIX.length);\n // Every occurrence after the first is the same ambiguity, reported once.\n if (seen.has(slot)) continue;\n seen.add(slot);\n const def = ownSlot(definition, slot);\n if (params.getAll(key).length > 1) issues.push({ kind: 'duplicate-slot', slot, token });\n else if (!def) issues.push({ kind: 'unknown-slot', slot, token });\n else if (def.type !== 'device') issues.push({ kind: 'not-a-device-slot', slot, token });\n else if (def.scope?.strategy === 'first') issues.push({ kind: 'derived-slot', slot, token });\n else if (!isValidToken(token)) issues.push({ kind: 'invalid-token', slot, token });\n else Object.defineProperty(binds, slot, { value: token, enumerable: true, writable: true, configurable: true });\n }\n return { binds, issues };\n}\n\n// The outcome of asking the server about each named device.\nexport interface BindCheck {\n // Devices the caller can see: safe to bind.\n ok: Record<string, string>;\n // Absent for this caller: deleted, never existed, or outside their tenant or customer scope.\n // Deliberately one bucket.\n missing: Array<{ slot: string; token: string }>;\n // The check itself failed (network, server error). Inconclusive, NOT \"missing\": retryable.\n failed: Array<{ slot: string; token: string }>;\n}\n\n// validateBinds asks the resolver (device-management, under the caller's own token) whether\n// each named device is visible. It never throws: a failure is reported per device as `failed`.\nexport async function validateBinds(binds: Record<string, string>, resolver: Pick<DeviceResolver, 'deviceExists'>): Promise<BindCheck> {\n const out: BindCheck = { ok: {}, missing: [], failed: [] };\n await Promise.all(\n Object.entries(binds).map(async ([slot, token]) => {\n try {\n if (await resolver.deviceExists(token)) Object.defineProperty(out.ok, slot, { value: token, enumerable: true, writable: true, configurable: true });\n else out.missing.push({ slot, token });\n } catch {\n out.failed.push({ slot, token });\n }\n }),\n );\n return out;\n}\n\n// bindsToSelection turns validated binds into the selection overlay the hosts hold.\nexport function bindsToSelection(binds: Record<string, string>): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [slot, deviceToken] of Object.entries(binds)) {\n Object.defineProperty(out, slot, {\n value: { kind: 'device', deviceToken } satisfies SlotBinding,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n return out;\n}\n\n// selectionToBinds is the other direction: the device picks in a selection overlay, for slots\n// the board declares as device slots. An anchor pick is not carried (a link binds a device).\nexport function selectionToBinds(\n selection: Record<string, SlotBinding>,\n definition: DashboardDefinition,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const slot of Object.keys(selection)) {\n const b = selection[slot];\n if (b.kind !== 'device' || ownSlot(definition, slot)?.type !== 'device') continue;\n Object.defineProperty(out, slot, { value: b.deviceToken, enumerable: true, writable: true, configurable: true });\n }\n return out;\n}\n\n// withBindParams returns `search` with its `bind.*` parameters replaced by `binds`, every other\n// parameter (kiosk, theme, dashboard, \u2026) untouched and in place. Returns the query WITHOUT a\n// leading `?`, so a host puts it wherever its router wants it; empty when nothing is left.\nexport function withBindParams(search: string, binds: Record<string, string>): string {\n const next = new URLSearchParams();\n for (const [k, v] of new URLSearchParams(search)) if (!k.startsWith(BIND_PARAM_PREFIX)) next.append(k, v);\n for (const slot of Object.keys(binds).sort()) next.append(BIND_PARAM_PREFIX + slot, binds[slot]);\n return next.toString();\n}\n\n// bindParamsOf is the `bind.*` slice of a query string, normalised (sorted, no `?`), so two\n// addresses can be compared for \"same link\" regardless of parameter order.\nexport function bindParamsOf(search: string): string {\n const only = new URLSearchParams();\n for (const [k, v] of new URLSearchParams(search)) if (k.startsWith(BIND_PARAM_PREFIX)) only.append(k, v);\n only.sort();\n return only.toString();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Slot authoring transforms (ADR-039). A dashboard is a reusable template: every\n// widget references a named slot, and each slot carries a default entity binding.\n// These pure (React/DOM-free, unit-tested) helpers convert a dashboard's concrete\n// device/anchor selectors into that model and drive the editor's rebind/prune. The\n// authoring UX stays \"pick a device/anchor\"; the storage becomes slot-based, and\n// identical bindings collapse into ONE shared slot (dedup) \u2014 so a host manifest\n// binds each real entity once, and CompositeWidget-style sharing falls out for free.\n\nimport { canScopeSlot } from './definition';\nimport {\n UNKNOWN_WIDGET_TYPE,\n type AnchorTarget,\n type DashboardDefinition,\n type DatasourceSelector,\n type LocationSelection,\n type SlotBinding,\n type SlotDefinition,\n type SlotCandidates,\n type SlotScope,\n type WidgetInstance,\n} from './types';\n\n// slotSelector builds the slot selector these transforms write, carrying the SERIES the\n// widget reads (measurement names, and the location series when one is named) while the\n// ENTITY moves onto the slot.\n//\n// The location key is omitted when absent rather than written as undefined, so a\n// measurement-only widget serializes exactly as it did before the location channel\n// existed \u2014 a migration must not make every stored dashboard dirty.\nfunction slotSelector(\n slot: string,\n measurements: string[],\n location: LocationSelection | undefined,\n): DatasourceSelector {\n return location\n ? { kind: 'slot', slot, measurements, location }\n : { kind: 'slot', slot, measurements };\n}\n\n// sameBinding \u2014 value equality of two entity bindings (drives dedup).\nexport function sameBinding(a: SlotBinding | undefined, b: SlotBinding | undefined): boolean {\n if (!a || !b) return a === b;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') {\n return (\n a.anchor.relationship === b.anchor.relationship &&\n a.anchor.targetType === b.anchor.targetType &&\n a.anchor.targetToken === b.anchor.targetToken\n );\n }\n return false;\n}\n\n// bindingLabel \u2014 a readable slot label (the bound entity's token).\nfunction bindingLabel(b: SlotBinding): string {\n return b.kind === 'device' ? b.deviceToken : b.anchor.targetToken;\n}\n\n// nextSlotName \u2014 the lowest unused `slot-N` name in a slots map.\nexport function nextSlotName(slots: Record<string, SlotDefinition>): string {\n let n = 1;\n while (slots[`slot-${n}`]) n += 1;\n return `slot-${n}`;\n}\n\n// bindingOfSelector \u2014 the entity binding a concrete device/anchor selector names\n// (measurements are dropped \u2014 a binding is the entity only). undefined for a device\n// selector with no token, or a non-concrete selector.\nfunction bindingOfSelector(ds: DatasourceSelector | undefined): SlotBinding | undefined {\n if (ds?.kind === 'device') return ds.deviceToken ? { kind: 'device', deviceToken: ds.deviceToken } : undefined;\n // Guard an empty target token (symmetric with device): a token-less anchor names no\n // entity, so it can't be a binding \u2014 and parseSlotBinding would drop it, so slotting it\n // would silently lose the relationship/targetType on reload.\n if (ds?.kind === 'anchor') {\n return ds.anchor.targetToken ? { kind: 'anchor', anchor: ds.anchor } : undefined;\n }\n return undefined;\n}\n\n// findOrAddSlot returns the name of the slot bound to `binding`, creating one (in\n// the passed, freshly-copied map) when none exists. Dedup lives here. It only reuses a\n// PLAIN (unscoped) slot: a scoped slot follows a parent, so folding a plain widget onto\n// one that merely shares the same default binding would silently give the widget cascade\n// behavior it never asked for. The migration/authoring path only ever mints plain slots.\nfunction findOrAddSlot(slots: Record<string, SlotDefinition>, binding: SlotBinding): string {\n const existing = Object.keys(slots).find((k) => !slots[k].scope && sameBinding(slots[k].defaultBinding, binding));\n if (existing) return existing;\n const name = nextSlotName(slots);\n slots[name] = { type: binding.kind, label: bindingLabel(binding), defaultBinding: binding };\n return name;\n}\n\n// migrateToSlots rewrites every widget's concrete device/anchor selector into a slot\n// selector (default-bound to that entity), deduping identical bindings into one\n// shared slot. Idempotent: slot selectors (and slot-free widgets) pass through\n// untouched, so re-running is a no-op. This is the decisive pre-GA cutover applied\n// when the console loads a dashboard.\nexport function migrateToSlots(def: DashboardDefinition): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const ds = w.datasource;\n // Leave an anchor that carries a Phase-2 `aggregation` as a concrete selector: the\n // slot model has no aggregation field, so slotting it would drop that (reserved)\n // config. It coexists fine \u2014 the Hub resolves concrete anchors too. (Editing such a\n // widget in the panel still drops aggregation, but that's an explicit action.)\n if (ds?.kind === 'anchor' && ds.aggregation) return w;\n const binding = bindingOfSelector(ds);\n if (!binding || !ds) return w;\n changed = true;\n const slot = findOrAddSlot(slots, binding);\n // The location series moves across with the measurement names: it says WHICH SERIES\n // the widget reads, and slotting changes only WHICH ENTITY it reads them from.\n // Dropping it here would silently blank every map on the board at load time.\n return { ...w, datasource: slotSelector(slot, ds.measurements, ds.location) };\n });\n // Return the SAME reference (no spurious `slots:{}`) when nothing migrated, so the\n // early-out is a true no-op and the result round-trips byte-identically.\n if (!changed) return def;\n return { ...def, widgets, slots };\n}\n\n// bindWidgetSlot points a widget at the slot for `binding` (creating/reusing it,\n// deduped) with the given measurement names \u2014 the editor's rebind + measurements\n// operation. Picking a NEW entity forks a fresh slot, leaving other widgets' slots\n// alone. A measurements-only edit (binding unchanged) KEEPS the widget's own slot even\n// when another slot shares the binding \u2014 so a distinct slot the host may override\n// separately (I-3 templates) isn't silently collapsed away.\n// `location` names the location series the widget reads, when it reads one. It is a\n// parameter rather than something preserved from the widget's current selector for the\n// same reason `measurements` is: the caller (the config panel) is the one that knows\n// which series the widget wants, and a map widget's FIRST datasource has no previous\n// selector to preserve it from.\nexport function bindWidgetSlot(\n def: DashboardDefinition,\n widgetId: string,\n binding: SlotBinding,\n measurements: string[],\n location?: LocationSelection,\n): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n const current = def.widgets.find((w) => w.id === widgetId)?.datasource;\n const currentSlot = current?.kind === 'slot' ? current.slot : undefined;\n const currentDef = currentSlot ? slots[currentSlot] : undefined;\n // Keep the widget's own slot when it is SCOPED (a context slot the cascade drives \u2014\n // never silently rehome it onto a plain slot) or when the binding is unchanged;\n // otherwise fork/reuse a plain slot for the new entity.\n const slot =\n currentSlot && currentDef && (currentDef.scope || sameBinding(currentDef.defaultBinding, binding))\n ? currentSlot\n : findOrAddSlot(slots, binding);\n const widgets = def.widgets.map((w) =>\n // A placeholder for an unknown widget type binds nothing this build can see, so it is\n // never given a datasource here: its bindings live in its raw object.\n w.id === widgetId && w.type !== UNKNOWN_WIDGET_TYPE ? { ...w, datasource: slotSelector(slot, measurements, location) } : w,\n );\n return { ...def, widgets, slots };\n}\n\n// clearWidgetDatasource drops a widget's datasource (the \"None\" data source). Prune\n// afterwards to reclaim a now-orphaned slot.\nexport function clearWidgetDatasource(def: DashboardDefinition, widgetId: string): DashboardDefinition {\n const widgets = def.widgets.map((w) => {\n if (w.id !== widgetId) return w;\n const { datasource: _drop, ...rest } = w;\n return rest;\n });\n return { ...def, widgets };\n}\n\n// collectRawSlotReferences adds every slot name a raw (unparsed) widget object names,\n// in the two shapes this release knows a slot reference by \u2014 a slot selector\n// `{kind:'slot', slot}` and a `selectionTarget` \u2014 at any depth. Over-counting keeps an\n// unused slot, which is harmless; under-counting deletes a newer widget's binding. The\n// depth bound only guards against a hand-built cyclic object (parsed JSON has none).\nconst RAW_SLOT_SCAN_DEPTH = 32;\nfunction collectRawSlotReferences(value: unknown, used: Set<string>, depth: number): void {\n if (depth > RAW_SLOT_SCAN_DEPTH || typeof value !== 'object' || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) collectRawSlotReferences(item, used, depth + 1);\n return;\n }\n const rec = value as Record<string, unknown>;\n if (rec.kind === 'slot' && typeof rec.slot === 'string' && rec.slot.length > 0) used.add(rec.slot);\n if (typeof rec.selectionTarget === 'string' && rec.selectionTarget.length > 0) used.add(rec.selectionTarget);\n for (const child of Object.values(rec)) collectRawSlotReferences(child, used, depth + 1);\n}\n\n// pruneSlots removes slots no widget references, and omits the `slots` key entirely\n// when none remain (so a dashboard that loses its last slot serializes clean).\nexport function pruneSlots(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const defSlots = def.slots;\n const used = new Set<string>();\n for (const w of def.widgets) {\n if (w.datasource?.kind === 'slot') used.add(w.datasource.slot);\n // A widget can also REFERENCE a slot without binding it: a context/entity-selector's\n // (and an alarm-table drill's) `options.selectionTarget` names the slot it re-points.\n // Miss it and deleting the data widget that binds that slot would prune it out from\n // under the selector \u2014 a silent \"No options\". Count it as a use.\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target.length > 0) used.add(target);\n // A widget of a type this build does not know uses slots this build cannot see in\n // its parsed fields: they are inside its raw object, possibly nested (a container's\n // children). Pruning them would make the next save delete the newer widget's slots\n // out from under it, so every slot the raw object names counts as a use.\n if (w.raw) collectRawSlotReferences(w.raw, used, 0);\n }\n // Close `used` over scope.parent: a context-only parent slot (e.g. a root 'building'\n // that no widget binds directly but a scoped child depends on) must be kept, or the\n // child dangles and the cascade loses its top-level context. Walk each used slot's\n // ancestor chain (guarded against a hand-edited cycle) adding every parent.\n for (const start of [...used]) {\n let cur: string | undefined = start;\n const guard = new Set<string>();\n while (cur && Object.prototype.hasOwnProperty.call(defSlots, cur) && !guard.has(cur)) {\n guard.add(cur);\n const parent: string | undefined = defSlots[cur].scope?.parent;\n if (parent) used.add(parent);\n cur = parent;\n }\n }\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) if (used.has(name)) slots[name] = slot;\n if (Object.keys(slots).length === 0) {\n const { slots: _drop, ...rest } = def;\n return rest;\n }\n return { ...def, slots };\n}\n\n// anchorSlotNames lists the dashboard's anchor-typed slots \u2014 the candidate PARENTS a\n// scoped slot may attach to (an editor's parent picker), and the slots a root context-\n// selector can target. Empty when the dashboard has no slots.\nexport function anchorSlotNames(def: DashboardDefinition): string[] {\n const slots = def.slots ?? {};\n return Object.keys(slots).filter((name) => slots[name].type === 'anchor');\n}\n\n// setSlotScope makes `slotName` scoped to a parent (the context hierarchy) or, with\n// `scope` undefined, clears its scope back to a root context \u2014 the editor's scope-authoring\n// transform. It is a no-op when the slot doesn't exist, or when the proposed scope is\n// invalid (canScopeSlot: parent missing/non-anchor/self/cycle) \u2014 degrade, never write a\n// scope the loader would drop on reload. Prototype-free (operates on the parsed slots map).\nexport function setSlotScope(\n def: DashboardDefinition,\n slotName: string,\n scope: SlotScope | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const slots: Record<string, SlotDefinition> = { ...def.slots };\n const current = slots[slotName];\n if (!scope) {\n if (!current.scope) return def; // already root \u2014 no-op (keeps the reference stable)\n const { scope: _drop, ...rest } = current;\n slots[slotName] = rest;\n return { ...def, slots };\n }\n if (!canScopeSlot(slots, slotName, scope.parent)) return def;\n slots[slotName] = { ...current, scope: { parent: scope.parent, strategy: scope.strategy } };\n return { ...def, slots };\n}\n\n// setSlotCandidates sets (or, with undefined/empty, clears) the picker type filter of a ROOT anchor\n// slot - the area type or asset type its picker lists. A no-op when the slot does not exist, is not\n// an anchor slot, or is scoped (a scoped slot's options are its parent's members, which no type\n// filter applies to), so the editor can never write a field the loader would ignore.\nexport function setSlotCandidates(\n def: DashboardDefinition,\n slotName: string,\n candidates: SlotCandidates | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const current = def.slots[slotName];\n if (current.type !== 'anchor' || current.scope) return def;\n const next: SlotCandidates = {};\n if (candidates?.areaType) next.areaType = candidates.areaType;\n if (candidates?.assetType) next.assetType = candidates.assetType;\n const { candidates: _drop, ...rest } = current;\n const slot: SlotDefinition = next.areaType || next.assetType ? { ...rest, candidates: next } : rest;\n return { ...def, slots: { ...def.slots, [slotName]: slot } };\n}\n\n// widgetBinding resolves a widget's effective entity binding for the editor: a slot\n// widget's default binding, or a (pre-migration) concrete selector's entity.\nexport function widgetBinding(def: DashboardDefinition, widget: WidgetInstance): SlotBinding | undefined {\n const ds = widget.datasource;\n if (ds?.kind === 'slot') return def.slots?.[ds.slot]?.defaultBinding;\n return bindingOfSelector(ds);\n}\n\n// widgetSlotName is the slot a widget references, if any (for an editor hint).\nexport function widgetSlotName(widget: WidgetInstance): string | undefined {\n return widget.datasource?.kind === 'slot' ? widget.datasource.slot : undefined;\n}\n\n// A concrete device/anchor selector \u2014 what the config panel edits (slot-agnostic).\n// Rebuilt into slot storage by the workspace via bindWidgetSlot.\nexport type ConcreteSelector =\n | { kind: 'device'; deviceToken: string; measurements: string[]; location?: LocationSelection }\n | { kind: 'anchor'; anchor: AnchorTarget; measurements: string[]; location?: LocationSelection };\n\n// resolveConcrete gives the config panel a slot-free view of a widget's data source:\n// the bound entity + the series the widget reads (measurement names, and the location\n// series when it names one), or undefined when unbound.\n//\n// \uD83D\uDD34 A SELECTOR THE AUTHOR IS STILL FILLING IN IS RETURNED AS IT STANDS. Choosing the Device\n// kind stores `{kind:'device', deviceToken:''}` on the widget (the workspace keeps a partial\n// selector concrete rather than slotting it -- see applyDatasource). It names no entity, so\n// it has no binding; but the panel renders from THIS value, and answering undefined for it\n// snapped the Kind control straight back to \"None\" and never showed the device picker, so a\n// device could not be chosen at all.\nexport function resolveConcrete(\n def: DashboardDefinition,\n widget: WidgetInstance,\n): ConcreteSelector | undefined {\n const binding = widgetBinding(def, widget);\n const measurements = widget.datasource?.measurements ?? [];\n const location = widget.datasource?.location;\n const base = location ? { measurements, location } : { measurements };\n if (!binding) {\n const ds = widget.datasource;\n if (ds?.kind === 'device') return { kind: 'device', deviceToken: ds.deviceToken, ...base };\n if (ds?.kind === 'anchor') return { kind: 'anchor', anchor: ds.anchor, ...base };\n return undefined;\n }\n return binding.kind === 'device'\n ? { kind: 'device', deviceToken: binding.deviceToken, ...base }\n : { kind: 'anchor', anchor: binding.anchor, ...base };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// 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<string, Promise<Array<{ token: string; name: string | null }>>>();\n const criteria = { pageNumber: 1, pageSize: LIST_PAGE_SIZE };\n\n return (kind: EntityListKind, typeToken?: string) => {\n const key = `${kind}|${typeToken}`;\n let pending = cache.get(key);\n if (!pending) {\n pending = fetchKind(kind, criteria, typeToken).catch((err) => {\n cache.delete(key);\n throw err;\n });\n cache.set(key, pending);\n }\n return pending;\n };\n}\n\nfunction fetchKind(\n kind: EntityListKind,\n criteria: { pageNumber: number; pageSize: number },\n typeToken?: string,\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: { ...criteria, areaTypeToken: typeToken } }).then((r) => r.areas.results);\n case 'asset':\n return gql('device-management', LIST_ASSETS, { criteria: { ...criteria, assetTypeToken: typeToken } }).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 of a customer anchor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A customer anchor reads customerDevices rather than the relationship graph: the graph is\n// closed to a member limited to some customers (a customer user), and customerDevices\n// answers such a member about its own customers. For every other viewer it answers what\n// the relationship search did for (device -> customer, type), so ONE read serves\n// every viewer. It carries each device's name, so the roster needs no second read.\n\nexport interface CustomerDevicesResult {\n customerDevices: {\n results: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n pagination?: { totalRecords: number | null } | null;\n };\n}\nexport interface CustomerDevicesVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n customer: string;\n relationshipType: string;\n };\n}\n\nexport const CUSTOMER_DEVICES = `\n query DashboardCustomerDevices($criteria: CustomerDeviceSearchCriteria!) {\n customerDevices(criteria: $criteria) {\n results {\n token\n name\n deviceType {\n token\n }\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<CustomerDevicesResult, CustomerDevicesVariables>;\n\n// \u2500\u2500 device-management: the devices on an area or an asset anchor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// anchorDevices answers every viewer for an area or an asset: an unscoped caller gets the devices\n// with a live edge to the anchor, and a member limited to some customers gets the same list\n// narrowed to the devices it can see (the anchor itself needs no permission of its own, since an\n// anchor none of its devices is on answers an empty page). The relationship graph that read this\n// before is closed to such a member. relationshipType is optional on the wire and REFUSED for a\n// scoped caller (SCOPE_UNSUPPORTED): the resolver sends it first and retries without it.\n\nexport interface AnchorDevicesResult {\n anchorDevices: {\n results: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n pagination?: { totalRecords: number | null } | null;\n };\n}\nexport interface AnchorDevicesVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n targetType: string;\n target: string;\n relationshipType?: string | null;\n };\n}\n\nexport const ANCHOR_DEVICES =\n 'query DashboardAnchorDevices($criteria: AnchorDeviceSearchCriteria!){anchorDevices(criteria:$criteria){results{token name deviceType{token}}pagination{totalRecords}}}' as unknown as TypedDocument<AnchorDevicesResult, AnchorDevicesVariables>;\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// -- device-management: the names of a set of devices (one batched read) ------\n// A fleet widget labels its rows with device names, not tokens. devicesByToken takes up to 1000\n// tokens, and an anchor resolves to at most one page of 500, so the whole roster's names are ONE\n// round trip however large the fleet.\n\nexport interface DeviceNamesResult {\n devicesByToken: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n}\n\nexport const DEVICE_NAMES = `\n query DashboardDeviceNames($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n name\n deviceType {\n token\n }\n }\n }\n` as unknown as TypedDocument<DeviceNamesResult, DevicesByTokenVariables>;\n\n// -- device-management: what a device type's ACTIVE profile version declares ---\n// The names a fleet widget reads to tell \"does not report this\" (the device's profile never\n// declares the measurement) from \"not reporting\" (it declares it and the device is silent). A\n// catalogue read: a member limited to some customers may make it. Calculated metrics are part of\n// the same list. activeProfileVersion is null until the profile is first published, and profile is\n// null on a type with none: neither declares anything, so neither can say a measurement is absent.\n\nexport interface DeclaredMetricsResult {\n deviceTypesByToken: Array<{\n token: string;\n profile: { activeProfileVersion: { metrics: Array<{ metricKey: string }> } | null } | null;\n }>;\n}\nexport interface DeclaredMetricsVariables {\n tokens: string[];\n}\n\nexport const DECLARED_METRICS = `\n query DashboardDeclaredMetrics($tokens: [String!]!) {\n deviceTypesByToken(tokens: $tokens) {\n token\n profile {\n activeProfileVersion {\n metrics {\n metricKey\n }\n }\n }\n }\n }\n` as unknown as TypedDocument<DeclaredMetricsResult, DeclaredMetricsVariables>;\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; areaTypeToken?: string | null; assetTypeToken?: string | null };\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 // The bucket's other aggregates, exact over the readings it holds (null when it holds none).\n // Optional on the type because the chart seed reads only `avg`; the aggregate widgets read\n // these and refuse a bucket that has readings but lacks them.\n min?: number | null;\n max?: number | null;\n sum?: number | null;\n count?: number;\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 min\n max\n sum\n count\n }\n }\n` as unknown as TypedDocument<BucketedMeasurementsResult, BucketedMeasurementsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// sameLabel -- whether two display strings say the same thing: equal after trimming and\n// ignoring case. The ONE predicate behind \"this subtitle only repeats the title\": the widget\n// frame uses it to drop a duplicate subject line, and the layout lint uses it to flag a board\n// that would show one, so the two can never disagree about what counts as a repeat. Empty or\n// missing strings never match anything.\nexport function sameLabel(a: string | null | undefined, b: string | null | undefined): boolean {\n if (!a || !b) return false;\n const x = a.trim().toLowerCase();\n return x !== '' && x === b.trim().toLowerCase();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The hub's live-connection state, as a viewer should read it: is the data on this board\n// arriving (live), is the socket down and being retried (reconnecting), or has it been down\n// long enough, or the browser itself gone offline, that the board should be treated as frozen\n// (offline).\n//\n// \uD83D\uDD34 It is derived from the SOCKET events the client already surfaces (connected / closed),\n// per open subscription, not from the age of any sample. A quiet sensor on a healthy socket\n// is `live`; sample age is the freshness indicator's job, and the two answer different\n// questions.\n\nexport type HubConnectionState = 'live' | 'reconnecting' | 'offline';\n\n// How long a socket may stay down before \"reconnecting\" is no longer an honest word for it.\n// graphql-ws retries with a growing back-off, so a drop that outlasts this is not a blip.\nexport const OFFLINE_AFTER_MS = 20_000;\n\n// How long the sockets must stay up before a drop is forgotten. A server that accepts and then\n// drops the socket (a crash-looping pod, a proxy timing out the upgrade) reconnects every few\n// seconds; if each brief reconnect restarted the OFFLINE_AFTER_MS clock, that board would read\n// \"reconnecting\" forever and never \"offline\". Within this window a new drop resumes the\n// original clock. The badge still shows `live` the moment the socket is up, because it is.\nexport const STABLE_AFTER_MS = 10_000;\n\n// One open subscription's view of its socket. A subscription starts UP: the shared per-area\n// socket may already be connected, and the client then emits no `connected` for a late\n// joiner, so waiting for one would leave a healthy board stuck \"reconnecting\".\nexport interface ConnectionHandle {\n connected(): void;\n closed(): void;\n // The subscription is gone; its socket state no longer counts.\n release(): void;\n}\n\nexport class ConnectionTracker {\n private readonly down = new Set<ConnectionHandle>();\n private readonly listeners = new Set<() => void>();\n private escalated = false;\n private escalate: ReturnType<typeof setTimeout> | undefined;\n private recover: ReturnType<typeof setTimeout> | undefined;\n private browserOffline = false;\n private current: HubConnectionState = 'live';\n private detach: (() => void) | undefined;\n\n constructor(\n private readonly offlineAfterMs: number = OFFLINE_AFTER_MS,\n private readonly stableAfterMs: number = STABLE_AFTER_MS,\n ) {}\n\n get state(): HubConnectionState {\n return this.current;\n }\n\n register(): ConnectionHandle {\n const handle: ConnectionHandle = {\n connected: () => {\n this.down.delete(handle);\n this.settle();\n },\n closed: () => {\n this.down.add(handle);\n this.settle();\n },\n release: () => {\n this.down.delete(handle);\n this.settle();\n },\n };\n return handle;\n }\n\n // Listeners are what keep the browser online/offline hooks attached, so a hub nobody is\n // watching holds no window listeners. Returns the unsubscribe.\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n if (this.listeners.size === 1) this.attach();\n return () => {\n this.listeners.delete(listener);\n if (this.listeners.size === 0) this.release();\n };\n }\n\n private attach(): void {\n if (typeof window === 'undefined') return;\n const sync = (): void => {\n this.browserOffline = typeof navigator !== 'undefined' && navigator.onLine === false;\n this.settle();\n };\n window.addEventListener('offline', sync);\n window.addEventListener('online', sync);\n this.detach = () => {\n window.removeEventListener('offline', sync);\n window.removeEventListener('online', sync);\n };\n sync();\n }\n\n private release(): void {\n this.detach?.();\n this.detach = undefined;\n this.browserOffline = false;\n this.settle();\n }\n\n private settle(): void {\n if (this.down.size === 0) {\n // Up again: forget the drop only once the sockets have stayed up (STABLE_AFTER_MS).\n if ((this.escalate || this.escalated) && !this.recover) {\n this.recover = setTimeout(() => {\n this.recover = undefined;\n if (this.escalate) clearTimeout(this.escalate);\n this.escalate = undefined;\n this.escalated = false;\n }, this.stableAfterMs);\n }\n } else {\n // Down before the recovery window closed: keep the original clock running.\n if (this.recover) clearTimeout(this.recover);\n this.recover = undefined;\n }\n if (this.down.size > 0 && !this.escalate && !this.escalated) {\n this.escalate = setTimeout(() => {\n this.escalate = undefined;\n this.escalated = true;\n this.settle();\n }, this.offlineAfterMs);\n }\n const next: HubConnectionState = this.browserOffline\n ? 'offline'\n : this.down.size === 0\n ? 'live'\n : this.escalated\n ? 'offline'\n : 'reconnecting';\n if (next === this.current) return;\n this.current = next;\n for (const listener of [...this.listeners]) listener();\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AlarmRow, CommandRow } from './types';\n\n// The time range an event read covers, for a widget that draws events on a time axis: the\n// `spanSeconds` that end at `endAt` (epoch ms), or, without `endAt`, at the source's now() when\n// each read is made, so a live range keeps sliding. Every source applies it BEFORE paging, so a\n// snapshot's `total` counts the range and nothing outside it.\nexport interface EventWindow {\n spanSeconds: number;\n endAt?: number;\n}\n\n// The instants a window covers at `now`. `to` is undefined for a live window: the read is open\n// at the top, since nothing has happened after now.\nexport function eventWindowBounds(window: EventWindow, now: number): { from: number; to: number | undefined } {\n const end = window.endAt ?? now;\n return { from: end - window.spanSeconds * 1000, to: window.endAt };\n}\n\n// The range criteria of the alarms and commands queries (RFC 3339; null = unbounded), at `now`.\nexport function windowCriteria(window: EventWindow | undefined, now: number): { startTime: string | null; endTime: string | null } {\n if (!window) return { startTime: null, endTime: null };\n const { from, to } = eventWindowBounds(window, now);\n return { startTime: new Date(from).toISOString(), endTime: to === undefined ? null : new Date(to).toISOString() };\n}\n\nconst instant = (iso: string | null | undefined): number | null => {\n if (!iso) return null;\n const t = Date.parse(iso);\n return Number.isNaN(t) ? null : t;\n};\n\n// The platform's alarm range, for a source that answers in memory: the alarm's span (raise to\n// clear, or still active) overlaps the window. A row with no raise time cannot be placed.\nexport function alarmOverlaps(row: Pick<AlarmRow, 'raisedTime' | 'clearedTime'>, window: EventWindow, now: number): boolean {\n const { from, to } = eventWindowBounds(window, now);\n const raised = instant(row.raisedTime);\n if (raised === null || (to !== undefined && raised > to)) return false;\n const cleared = instant(row.clearedTime);\n return cleared === null || cleared >= from;\n}\n\n// The platform's command range, for a source that answers in memory: sent (or queued, when\n// never sent) inside the window.\nexport function commandInRange(row: Pick<CommandRow, 'sentTime' | 'queuedTime'>, window: EventWindow, now: number): boolean {\n const { from, to } = eventWindowBounds(window, now);\n const at = instant(row.sentTime) ?? instant(row.queuedTime);\n return at !== null && at >= from && (to === undefined || at <= to);\n}\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 { windowCriteria, type EventWindow } from './event-window';\nimport { gql, isForbiddenError, isScopeUnsupportedError, subscribe, type Area, type SubscriptionSink } from '@devicechain/client';\n\nimport { ConnectionTracker, type HubConnectionState } from './connection';\nimport type { HistoryWindow } from './history';\nimport {\n ACKNOWLEDGE_ALARM,\n ALARM_STREAM,\n ALARM_DEVICE_TYPES,\n ALARM_SEVERITY_COUNTS,\n ALARM_TYPE_COUNTS,\n ALARMS_QUERY,\n CLEAR_ALARM,\n type AlarmAnchorInput,\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 { DEVICE_PRESENCE_QUERY } from './internal/presence-doc';\nimport { createLimiter, MEMBER_SEED_CONCURRENCY, SEED_CANCELLED } from './members';\nimport type { MemberBucketCriteria, MemberRef, MemberRoster, MemberSeed, MemberSeedPlan, MemberSink, PresenceSink, SeedFailure } from './members';\nimport { BUCKETED_MEASUREMENTS, type MeasurementBucket } from './queries';\nimport {\n MEASUREMENT_STREAM,\n type MeasurementStreamResult,\n type MeasurementStreamVariables,\n} from './internal/measurement-doc';\nimport type {\n AlarmDeviceType,\n AlarmRow,\n AlarmSeverityCount,\n AlarmTypeCount,\n AnchorTarget,\n CommandRow,\n DatasourceSelector,\n DevicePresence,\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// devicesByToken takes at most 1000 tokens.\nconst ALARM_DEVICE_TYPES_CHUNK = 1000;\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// Presence channel cadence. device-state exposes NO presence subscription, so like the location\n// channel this is poll-only and the poll IS the channel; the same batch-per-tick reasoning applies\n// (one `deviceStatesByDeviceToken` round trip per tick covers every device on the widget). Presence\n// changes on the scale of an inactivity timeout (minutes), so it polls at the location cadence rather\n// than faster: a tighter poll would multiply a board's queries to learn nothing sooner.\nconst PRESENCE_POLL_MS = 15_000;\n// deviceStatesByDeviceToken refuses more than 1000 tokens with LIMIT_EXCEEDED.\nconst PRESENCE_CHUNK = 1000;\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 // The anchor's members with their names, and the anchor's true size (which can exceed the page\n // the resolver reads). Optional: a resolver without it yields tokens only, and a fleet widget then\n // labels rows by token and cannot report a truncated anchor.\n anchorMembers?(anchor: AnchorTarget): Promise<{ members: MemberRef[]; total: number }>;\n // The measurement names the ACTIVE profile version of a device type declares (calculated metrics\n // included), or null when it declares nothing to consult. Rejects when the read fails. Optional:\n // a resolver without it leaves every member's declaration unknown.\n declaredMetrics?(deviceType: string): Promise<string[] | null>;\n // The token of the device type a device belongs to, or null when it has none (or does not exist).\n // Rejects when the read fails. Optional: a resolver without it cannot say what a single device's\n // profile declares.\n deviceType?(deviceToken: string): Promise<string | null>;\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. reconnected() fires when one of its device streams resumes\n// after an interruption -- the socket dropped and came back, or the server ended the stream and it\n// was opened again. The samples sent meanwhile were never delivered and never will be: a widget\n// that draws history re-reads it rather than joining the readings either side of the outage.\n// Optional (a source that never drops a feed does not call it).\nexport interface WidgetStreamSink {\n next: (sample: MeasurementSample) => void;\n error?: (err: unknown) => void;\n reconnected?: () => 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// What an alarm subscription reads: tenant-wide, one anchored read, or one read per device token.\ninterface AlarmScope {\n tenantWide: boolean;\n tokens: string[];\n anchor?: AlarmAnchorInput;\n}\nconst STAMPED = new Set(['customer', 'area', 'asset']);\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 // Resolve each row's originating device type (AlarmRow.deviceType) with one batched read per\n // reconcile. Off by default: an alarm table that does not group by type pays nothing.\n enrichDeviceType?: boolean;\n // Keep only alarms whose originating device has this device-type TOKEN.\n // Filtered by device-management, so `total` is the exact match count.\n deviceType?: string;\n // Read the per-type counts of the matching alarms instead of the alarms: the snapshot then has no\n // rows, `typeCounts` is set, and `total` is their sum. A source that cannot count by type leaves\n // `typeCounts` unset (or fails the stream); it never answers with counts it did not make.\n countByDeviceType?: boolean;\n // Read the per-severity counts of the matching alarms instead of the alarms: the snapshot then has\n // no rows, `severityCounts` is set, and `total` is their sum. Tenant-wide, EVERY alarm the viewer\n // can see counts, whatever its originator (a device, an asset, an area, a customer, a group), so\n // the total is the alarm count's. With `deviceType`, only alarms on devices of that type count. A\n // source that cannot count by severity leaves `severityCounts` unset (or fails the stream).\n countBySeverity?: boolean;\n // Keep only alarms whose span (raise to clear, or still active) overlaps this range, filtered\n // by device-management, so `total` is the exact count in range. An alarm keeps only its latest\n // raise, so an earlier raise of the same alarm is in no range.\n window?: EventWindow;\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 // Only for a subscription with countByDeviceType: one entry per (device type, severity) present.\n typeCounts?: AlarmTypeCount[];\n // Only for a subscription with countBySeverity: one entry per severity present.\n severityCounts?: AlarmSeverityCount[];\n}\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 // Keep only commands sent inside this range (queued, for one never sent), filtered by\n // command-delivery, so `total` is the exact count in range.\n window?: EventWindow;\n}\n\n// A command-history snapshot: the resolved target device (null when unbound \u2014 the\n// widget then can't issue), the recent commands (newest first, capped to pageSize) and\n// the total matching count. One replaces the last (poll-then-emit).\nexport interface CommandSnapshot {\n deviceToken: string | null;\n commands: CommandRow[];\n total: number;\n}\n\nexport interface CommandStreamSink {\n next: (snapshot: CommandSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// The result of issuing a command. It is a DISCRIMINATED UNION rather than a token,\n// because the enqueue has two possible answers and a widget must not show them the same\n// way:\n//\n// - 'sent' \u2014 the command was created; `token` is its freshly-minted dispatch\n// token, so the widget can highlight the command it just issued as it\n// moves through the lifecycle.\n// - 'rejected' \u2014 the server REFUSED the request and said why: `code` is the stable\n// classification to branch on (DEVICE_NOT_FOUND,\n// COMMAND_NOT_IN_VOCABULARY, PAYLOAD_SCHEMA_VIOLATION,\n// PAYLOAD_NOT_JSON, METADATA_NOT_JSON, EXPIRES_AT_INVALID,\n// HELD_CEILING_EXCEEDED, \u2026 \u2014 an OPEN set), `reason` is client-safe\n// prose to show a person.\n//\n// A THROWN error is neither: it means the platform could not decide the enqueue at all\n// (the service unreachable, a database error), which says nothing about the command and\n// so deserves a generic failure message rather than a reason.\n//\n// The union is deliberately not \"token, plus an optional rejection\". A caller reading\n// `.token` off a refusal would compile, highlight nothing, and report no failure \u2014 the\n// send would look successful. The discriminant makes the refusal impossible to ignore\n// at the type level.\nexport type CommandDispatch =\n | { status: 'sent'; token: string }\n | { status: 'rejected'; code: string; reason: string };\n\n// \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The map widget consumes a fifth surface: where the bound devices ARE. Like the\n// control channel there is no live subscription to ride (device-state exposes none for\n// position), so it is query-then-poll \u2014 the alarm and command channels are the\n// precedent here, not the measurement channel.\n//\n// What is different from all three is the REFUSAL. Position is gated on its own\n// `location:read` authority, which is deliberately absent from the read-only viewer\n// baseline, so an ordinary member with full telemetry access is routinely refused. The\n// channel therefore turns a refusal into a VALUE rather than an error: it is not a\n// fault, and it is not emptiness either.\n\n// LocationSubscription is one map widget's interest: the selector naming both the\n// devices and the location series to read. There is no page size \u2014 a map shows every\n// device it is bound to, and the bound set is the selector's own resolution.\nexport interface LocationSubscription {\n datasource?: DatasourceSelector;\n}\n\n// A location snapshot, discriminated so the refusal cannot be mistaken for emptiness.\n//\n// \uD83D\uDD34 `positions` with an empty `locations` and `forbidden` are OPPOSITE facts, and only\n// one of them is actionable. \"No device here has ever reported a position\" is a claim\n// about the DEVICES; \"you may not view location\" is a claim about the CALLER. Folding\n// them together (a bare `locations: []`) would tell an operator their fleet is\n// unlocated when the truth is that they need a role \u2014 which is the exact mistake the\n// device-detail position panel was built to avoid, held here in the type.\n//\n// `deviceTokens` is the set the selector resolved to, carried alongside the positions\n// because a never-located device is ABSENT from the query result: without it, \"bound to\n// nothing\" and \"bound to devices that have never moved\" are indistinguishable.\nexport type LocationSnapshot =\n | { kind: 'positions'; deviceTokens: string[]; locations: LocationSample[] }\n | { kind: 'forbidden' };\n\nexport interface LocationStreamSink {\n next: (snapshot: LocationSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Action seam (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Read widgets are pure `(widget, data)`; a widget that ACTS (acknowledge/clear an\n// alarm, send a command) never touches the SDK either \u2014 it calls this seam, which the\n// renderer threads in from the runtime. So preview stays offline (SyntheticDataSource\n// implements a no-op/echo WidgetActions) and the \"widget never reaches the backend\"\n// invariant holds. `can` gates the UI: a widget hides an action the viewer isn't\n// authorized for (the server enforces it regardless).\n//\n// Growing this interface is a breaking change for any external host implementing it.\n// The alarm actions (acknowledgeAlarm/clearAlarm) are the required baseline; capabilities\n// added later \u2014 like sendCommand below \u2014 are declared OPTIONAL so a host predating them\n// still satisfies the type, and consumers feature-detect (typeof actions?.sendCommand).\nexport interface WidgetActions {\n // Acknowledge / clear a raised alarm by token (requires alarm:write). Resolves when\n // the mutation succeeds; the runtime reconciles the affected alarm widgets so the\n // change shows immediately rather than waiting for the next poll.\n acknowledgeAlarm(alarmToken: string): Promise<void>;\n clearAlarm(alarmToken: string): Promise<void>;\n // Issue a command to a device (requires command:write). The runtime mints the dispatch\n // token and returns it; it also reconciles the command widgets so the new command\n // appears at once. It RESOLVES on a refusal too \u2014 with a 'rejected' dispatch carrying\n // the server's code + reason (see CommandDispatch) \u2014 and rejects only when the enqueue\n // could not be decided at all. OPTIONAL: added after this interface shipped, so a host\n // predating it (or a strictly read-only one) may omit it \u2014 a command widget then\n // renders its Send control disabled. `payload` is the request body sent to the device\n // verbatim (the widget serializes its typed parameter form to JSON).\n sendCommand?(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch>;\n // Whether the current viewer holds an authority (e.g. 'alarm:write'). Drives whether\n // an action control renders at all.\n can(authority: string): boolean;\n}\n\n// The hub holds NO slot bindings. A widget's `slot` selector is resolved by the renderer\n// (resolveWidgetDatasource) through the settled bindings before it reaches the hub, so one\n// hub serves a board for its whole life: a selection that re-points a slot changes the\n// concrete selector of just the widgets bound to it, and only those re-subscribe. A host\n// builds one hub per (resolver, authorities).\nexport interface DashboardHubConfig {\n resolver: DeviceResolver;\n // The current viewer's authorities (access-token claims). Drives `can()` \u2014 which\n // gates whether a widget's action controls render. Omitted/empty = no write actions\n // (the read-only default); '*' grants all. The server enforces authority regardless.\n authorities?: string[];\n}\n\n// WidgetDataSource is the minimal contract a widget renderer needs from a data\n// source: bind a datasource selector to a sink, get a disposer back. DashboardHub\n// is the live implementation (multiplexed backend telemetry); SyntheticDataSource\n// is the offline preview implementation. The widget layer depends on THIS interface,\n// not the concrete class, so a host can feed either without widgets knowing which.\nexport interface WidgetDataSource {\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void;\n // Bind an alarm widget's scope+filters to a sink; returns a disposer. Delivers\n // whole snapshots (query-then-reconcile), not incremental events. Implemented by\n // both the live hub and the synthetic preview source so alarm widgets render\n // identically from either.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void;\n // Bind a command widget's scope to a sink; returns a disposer. Delivers whole\n // command-history snapshots on a poll (command-delivery has no subscription).\n // Implemented by both the live hub and the synthetic preview source.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;\n // Bind a map widget's selector to a sink; returns a disposer. Delivers whole\n // position snapshots on a poll (device-state has no location subscription), and\n // reports a `location:read` refusal as a snapshot state rather than an error.\n // Implemented by both the live hub and the synthetic preview source.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;\n // Whether a widget's bound entity still exists. Optimistic + async: a widget renders\n // from its stream immediately, and this resolves separately \u2014 only a device selector\n // (or a slot bound to a device) whose token no longer resolves reports false, so the\n // widget shows \"unavailable\" instead of a blank pane. Anchor / unbound-slot / no-\n // datasource report true (their empty state is legitimate, not \"unavailable\"). Fails\n // OPEN (true) on an inconclusive check, so a device-management blip never falsely marks\n // a live device unavailable.\n isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;\n // Whether the platform offers this binding to the viewer at all. False only when the server\n // refuses it as not available to a member limited to part of the tenant (a customer user):\n // an area or asset anchor, whose members are found through the relationship graph such a\n // member cannot read. The widget then says so rather than rendering empty or \"no access\",\n // since nothing that member could be granted would change it. Optional: a source without it\n // (the synthetic preview, a recorded run) supports every binding. Fails open (true) on any\n // other failure, which the widget's own channel reports.\n isDatasourceSupported?(datasource: DatasourceSelector | undefined): Promise<boolean>;\n // The source's idea of \"now\" (epoch ms), for measuring a time span against its samples. Omitted\n // = the wall clock, which is right for every live source. A source that plays back a recorded\n // run implements it, because the run's samples are dated by the run.\n now?(): number;\n // The time buckets of a selector's measurements over a window, each carrying the exact count,\n // minimum, maximum and sum of its readings: what a stat widget folds into the span's aggregates.\n // Optional: a source that cannot produce them omits it, and the widget then says so instead of\n // showing a number. Rejects when the read fails.\n bucketMeasurements?(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]>;\n // A fleet widget's roster and live feed: the members the selector resolves to (delivered once),\n // then every member's samples, each tagged with its device. ONE subscription per widget, however\n // many members; the hub shares each member's upstream stream with every other widget on it.\n // Optional: a source without it leaves a fleet widget with an explained \"not supported\" state.\n subscribeMembers?(datasource: DatasourceSelector, sink: MemberSink): () => void;\n // Each device's seed, made by the widget's plan from the reads it asks for, delivered per device as\n // it settles (`null` when that device's plan failed; `failure` says why). Every read is bounded\n // hub-wide (MEMBER_SEED_CONCURRENCY) and shared with any identical one in flight. `cancelled` is\n // checked before each read starts. Optional like the above.\n readMemberSeeds?(\n deviceTokens: readonly string[],\n plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void,\n cancelled?: () => boolean,\n ): Promise<void>;\n // The measurement names a device TYPE's active profile version declares: what separates \"does not\n // report this\" (the profile never declares it) from \"not reporting\" (it does, and the device is\n // silent). `null` when it cannot say -- no published profile, no way to look, or the lookup\n // failed -- and an unknown declaration is read as declared, never as absent. Optional like the above.\n declaredMetrics?(deviceType: string): Promise<readonly string[] | null>;\n // The same, for ONE device: what its device type's active profile declares. It backs the explained\n // empty state of a single-device widget (\"this device does not report that\"). `null` when it cannot\n // say, with the same rule: unknown is read as declared. Optional like the above.\n declaredMetricsForDevice?(deviceToken: string): Promise<readonly string[] | null>;\n // A presence widget's roster and connectivity: the members the selector resolves to (delivered\n // once), then a whole snapshot of their device-state on every poll (online or offline, last active).\n // Optional: a source with no device-state to read (a recorded run) omits it, and the widget then\n // says so rather than draw a status it cannot know.\n subscribePresence?(datasource: DatasourceSelector, sink: PresenceSink): () => void;\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 // Live presence-subscription disposers (same rationale: a poll per subscription).\n private readonly presenceDisposers = new Set<() => void>();\n\n // Which of this hub's sockets are down, folded into one state a header can show.\n private readonly connection = new ConnectionTracker();\n // Streams evicted by an upstream error that still have subscribers: out of `streams`, but\n // still holding their connection handle (see ensureStream), so disposeAll() can reach them.\n private readonly deadStreams = new Set<DeviceStream>();\n\n constructor(config: DashboardHubConfig) {\n this.resolver = config.resolver;\n this.authorities = new Set(config.authorities ?? []);\n }\n\n // The hub's live-connection state: `live`, `reconnecting` (a socket dropped and is being\n // retried) or `offline` (down past the point a retry is a blip, or the browser is offline).\n // A hub with nothing subscribed has nothing to lose and reads `live`. Pair with\n // onConnectionState (a subscribe/getSnapshot pair, so it drops into useSyncExternalStore).\n get connectionState(): HubConnectionState {\n return this.connection.state;\n }\n\n onConnectionState(listener: () => void): () => void {\n return this.connection.subscribe(listener);\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 // subscribeMembers: the roster, then the live samples of every member. Resolution is async, so\n // the disposer is returned at once and cancels a still-pending resolution (a widget torn down\n // before its roster arrives never attaches a leaked subscriber). Each member rides the same\n // ref-counted device stream every other widget on it uses.\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n let disposed = false;\n const detachers: Array<() => void> = [];\n this.resolveRoster(datasource)\n .then(({ roster, names }) => {\n if (disposed) return;\n sink.roster(roster);\n for (const member of roster.members) {\n detachers.push(\n this.attach(member.token, names, {\n next: (sample) => sink.next(sample),\n error: (err) => sink.error?.(err),\n reconnected: () => sink.reconnected?.(),\n }),\n );\n }\n })\n .catch((err) => {\n if (disposed) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n });\n return () => {\n disposed = true;\n for (const detach of detachers.splice(0)) detach();\n };\n }\n\n // One resolution of a selector into a roster. An anchor goes through the resolver's member read\n // when it has one (names and the true size); everything else is the tokens resolveDevices yields.\n private async resolveRoster(datasource: DatasourceSelector): Promise<{ roster: MemberRoster; names: Set<string> }> {\n const names = new Set(datasource.measurements);\n if (datasource.kind === 'anchor' && this.resolver.anchorMembers) {\n const { members, total } = await this.resolver.anchorMembers(datasource.anchor);\n return { roster: { members, total, unbound: false }, names };\n }\n const groups = await this.resolveDevices(datasource);\n const members = groups.map((g) => ({ token: g.deviceToken, name: null }));\n return { roster: { members, total: members.length, unbound: datasource.kind === 'unbound' }, names };\n }\n\n // One read per distinct device type, whoever asks and however many devices have it. A settled\n // answer (names, or null for \"no published profile\") is kept for the session; a failed read is\n // not, so a later ask tries again, and that ask is answered null meanwhile.\n private readonly declared = new Map<string, Promise<readonly string[] | null>>();\n\n declaredMetrics(deviceType: string): Promise<readonly string[] | null> {\n const read = this.resolver.declaredMetrics;\n if (!read) return Promise.resolve(null);\n let pending = this.declared.get(deviceType);\n if (!pending) {\n pending = read.call(this.resolver, deviceType).catch(() => {\n this.declared.delete(deviceType);\n return null;\n });\n this.declared.set(deviceType, pending);\n }\n return pending;\n }\n\n async declaredMetricsForDevice(deviceToken: string): Promise<readonly string[] | null> {\n const typeOf = this.resolver.deviceType;\n if (!typeOf) return null;\n try {\n const type = await typeOf.call(this.resolver, deviceToken);\n return type ? await this.declaredMetrics(type) : null;\n } catch {\n return null;\n }\n }\n\n // The hub-wide cap on in-flight member seed reads, and the reads currently in flight (so a second\n // widget asking the same question of the same device shares one request).\n private readonly seedLimiter = createLimiter(MEMBER_SEED_CONCURRENCY);\n private readonly seedReads = new Map<string, Promise<MeasurementBucket[]>>();\n\n async readMemberSeeds(\n deviceTokens: readonly string[],\n plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void,\n cancelled: () => boolean = () => false,\n ): Promise<void> {\n await Promise.all(\n deviceTokens.map(async (token) => {\n let seed: MemberSeed;\n try {\n seed = await plan.seed(token, (criteria) => this.memberBuckets(token, criteria, cancelled));\n } catch (err) {\n if (cancelled() || err === SEED_CANCELLED) return;\n onSeed(token, null, isForbiddenError(err) ? 'forbidden' : 'failed');\n return;\n }\n if (!cancelled()) onSeed(token, seed);\n }),\n );\n }\n\n private async memberBuckets(token: string, criteria: MemberBucketCriteria, cancelled: () => boolean): Promise<MeasurementBucket[]> {\n const key = JSON.stringify([token, criteria]);\n for (;;) {\n let read = this.seedReads.get(key);\n if (!read) {\n // The first asker's `cancelled` decides whether the read starts.\n read = this.seedLimiter(\n () => gql(EVENT_AREA, BUCKETED_MEASUREMENTS, { criteria: { deviceToken: token, ...criteria } }).then((r) => r.bucketedMeasurements),\n cancelled,\n );\n this.seedReads.set(key, read);\n const forget = () => this.seedReads.delete(key);\n read.then(forget, forget);\n }\n try {\n return await read;\n } catch (err) {\n // A read this asker shared with one that has since gone away was dropped before it started;\n // this asker is still here, so it asks again (now as the first asker).\n if (err === SEED_CANCELLED && !cancelled()) continue;\n throw err;\n }\n }\n }\n\n // bucketMeasurements reads the buckets of every (device, measurement) pair over a window. Widgets\n // that ask the same question while a read is in flight share it: stat cards on one measurement\n // and span read on the same minute boundaries, so a board of them costs one read per question.\n private readonly bucketReads = new Map<string, Promise<MeasurementBucket[]>>();\n\n bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const key = JSON.stringify([datasource, window]);\n let read = this.bucketReads.get(key);\n if (!read) {\n read = this.readBuckets(datasource, window);\n this.bucketReads.set(key, read);\n const forget = () => this.bucketReads.delete(key);\n read.then(forget, forget);\n }\n return read;\n }\n\n private async readBuckets(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const groups = await this.resolveDevices(datasource);\n const reads = groups.flatMap((g) =>\n (g.names.size ? [...g.names] : [undefined]).map((name) =>\n gql(EVENT_AREA, BUCKETED_MEASUREMENTS, { criteria: { deviceToken: g.deviceToken, name, ...window } }),\n ),\n );\n return (await Promise.all(reads)).flatMap((r) => r.bucketedMeasurements);\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 = (scope: AlarmScope): void => {\n const gen = ++generation;\n this.queryAlarms(subscription, scope)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveAlarmScope(subscription.datasource)\n .then((scope) => {\n if (disposed) return;\n\n // A scoped widget that resolves to no device (an unbound slot, an empty anchor)\n // shows an empty state \u2014 NOT tenant-wide. Only a widget with no datasource at\n // all is tenant-wide. Nothing to stream/poll here. Scope is resolved once (like\n // the measurement channel): a slot rebind arrives as a new concrete selector, which\n // re-subscribes this widget, but organic anchor-membership change isn't picked up\n // until the widget re-subscribes \u2014 a deferred enhancement shared with the\n // measurement channel.\n if (!scope.tenantWide && !scope.anchor && 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), 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 handle = this.connection.register();\n const adapter: SubscriptionSink<AlarmStreamResult> = {\n next: () => trigger(),\n connected: (wasRetry) => {\n handle.connected();\n if (wasRetry) reconcile(scope);\n },\n closed: () => handle.closed(),\n };\n const closeSocket = subscribe(DEVICE_AREA, ALARM_STREAM, { anchor: scope.anchor }, adapter);\n unsubscribe = () => {\n handle.release();\n closeSocket();\n };\n poll = setInterval(() => reconcile(scope), 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);\n this.alarmReconcilers.add(reconciler);\n reconcile(scope); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveAlarmScope turns an alarm widget's scope selector into the originator device\n // tokens to filter on, or tenant-wide when it carries no datasource. Reuses the same\n // device/anchor/unbound resolution the measurement channel does.\n //\n // \uD83D\uDD34 Only an ABSENT datasource is tenant-wide. `{kind:'unbound'}` is a datasource, and\n // it resolves to zero devices like any other empty scope.\n private async resolveAlarmScope(datasource: DatasourceSelector | undefined): Promise<AlarmScope> {\n if (!datasource) return { tenantWide: true, tokens: [] };\n // A customer, area or asset the alarms are stamped with is ONE anchored read, not a read per\n // member device. Any other anchor (a group) still expands to its devices.\n if (datasource.kind === 'anchor' && datasource.anchor.relationship === 'assigned' && STAMPED.has(datasource.anchor.targetType)) {\n return { tenantWide: false, tokens: [], anchor: { type: datasource.anchor.targetType, token: datasource.anchor.targetToken } };\n }\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 { tenantWide, tokens, anchor }: AlarmScope,\n ): Promise<AlarmSnapshot> {\n const filters = {\n state: sub.state ?? null,\n severity: sub.severity ?? null,\n acknowledged: sub.acknowledged ?? null,\n } satisfies Partial<AlarmSearchCriteriaInput>;\n const originators: Array<{ originatorType: string | null; originator: string | null; anchor?: AlarmAnchorInput }> =\n tenantWide || anchor\n ? [{ originatorType: null, originator: null, anchor }]\n : tokens.map((token) => ({ originatorType: 'device', originator: token }));\n\n if (sub.countBySeverity) {\n // One grouped read per originator query (one tenant-wide), summed by severity. Per-device\n // reads cannot double count: an alarm has one originator.\n const pages = await Promise.all(\n originators.map((o) =>\n gql(DEVICE_AREA, ALARM_SEVERITY_COUNTS, { criteria: { ...filters, ...o }, deviceType: sub.deviceType ?? null }),\n ),\n );\n const merged = new Map<string, number>();\n for (const page of pages) {\n for (const c of page.alarmCountsBySeverity) merged.set(c.severity, (merged.get(c.severity) ?? 0) + c.count);\n }\n const severityCounts = [...merged].map(([severity, count]) => ({ severity, count }));\n return { alarms: [], total: severityCounts.reduce((n, c) => n + c.count, 0), severityCounts };\n }\n\n if (sub.countByDeviceType) {\n // One grouped read per originator query (one tenant-wide), summed by (type, severity).\n const pages = await Promise.all(\n originators.map((o) => gql(DEVICE_AREA, ALARM_TYPE_COUNTS, { criteria: { ...filters, ...o } })),\n );\n const merged = new Map<string, AlarmTypeCount>();\n for (const page of pages) {\n for (const c of page.alarmCountsByDeviceType) {\n const key = `${c.deviceType.token}\\u0000${c.severity}`;\n const prior = merged.get(key);\n merged.set(key, prior ? { ...prior, count: prior.count + c.count } : { ...c, deviceType: { ...c.deviceType } });\n }\n }\n const typeCounts = [...merged.values()];\n return { alarms: [], total: typeCounts.reduce((n, c) => n + c.count, 0), typeCounts };\n }\n\n // The type filter is device-management's, so a page is the subscription's either way.\n const base = {\n pageNumber: 1, // the alarms query paginates 1-based\n pageSize: sub.pageSize,\n ...filters,\n deviceType: sub.deviceType ?? null,\n ...windowCriteria(sub.window, Date.now()),\n } satisfies Partial<AlarmSearchCriteriaInput>;\n const pages = await Promise.all(\n originators.map((o) => gql(DEVICE_AREA, ALARMS_QUERY, { criteria: { ...base, ...o } })),\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 let alarms = [...byToken.values()]\n .sort((a, b) => (b.raisedTime ?? '').localeCompare(a.raisedTime ?? ''))\n .slice(0, sub.pageSize);\n if (sub.enrichDeviceType === true) alarms = await this.withDeviceTypes(alarms);\n return { alarms, total };\n }\n\n // Each device's type, for the lifetime of the hub: a device's type is fixed for a viewing session,\n // and the same originators come back every poll. ONE devicesByToken read per chunk of uncached\n // tokens (the server takes 1000), never one per alarm. A failed read leaves those rows with no\n // `deviceType` (unknown, so a grouping widget lists them under All only) instead of failing the\n // whole alarm table, and is retried on the next reconcile because a failure is not cached.\n private readonly alarmDeviceTypes = new Map<string, AlarmDeviceType>();\n\n private async withDeviceTypes(rows: AlarmRow[]): Promise<AlarmRow[]> {\n const missing = [\n ...new Set(\n rows\n .filter((r) => r.originatorType === 'device' && r.originatorToken && !this.alarmDeviceTypes.has(r.originatorToken))\n .map((r) => r.originatorToken as string),\n ),\n ];\n for (let i = 0; i < missing.length; i += ALARM_DEVICE_TYPES_CHUNK) {\n try {\n const r = await gql(DEVICE_AREA, ALARM_DEVICE_TYPES, { tokens: missing.slice(i, i + ALARM_DEVICE_TYPES_CHUNK) });\n for (const d of r.devicesByToken) this.alarmDeviceTypes.set(d.token, d.deviceType);\n } catch {\n // rows stay unresolved; see above\n }\n }\n return rows.map((r) => {\n if (r.originatorType !== 'device' || !r.originatorToken) return { ...r, deviceType: null };\n const deviceType = this.alarmDeviceTypes.get(r.originatorToken);\n return deviceType ? { ...r, deviceType } : r;\n });\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, ...windowCriteria(sub.window, Date.now()) },\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 // subscribePresence: the roster, then the roster's device-state on an interval. Poll-only (see\n // PRESENCE_POLL_MS). A refusal (`state:read` is outside some members' grants) becomes `forbidden`,\n // and every other failure stays an error, as for the location channel. A slow read that lands after\n // a newer one is dropped, so an old answer never overwrites a fresher one.\n subscribePresence(datasource: DatasourceSelector, sink: PresenceSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let shown: (() => void) | undefined;\n let generation = 0;\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n if (shown) document.removeEventListener('visibilitychange', shown);\n this.presenceDisposers.delete(dispose);\n };\n this.presenceDisposers.add(dispose);\n\n const reconcile = (tokens: string[]): void => {\n const gen = ++generation;\n this.queryPresence(tokens).then(\n (states) => {\n if (!disposed && gen === generation) sink.next(states);\n },\n (err) => {\n if (disposed || gen !== generation) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n },\n );\n };\n\n this.resolveRoster(datasource).then(\n ({ roster }) => {\n if (disposed) return;\n sink.roster(roster);\n const tokens = roster.members.map((m) => m.token);\n if (tokens.length === 0) {\n sink.next([]);\n return;\n }\n // A hidden tab reads nothing: nobody is looking, and a board left open in a background tab\n // would otherwise poll device-state for as long as the browser lives. Shown again, it reads at\n // once rather than drawing the snapshot it last had until the next tick.\n const hidden = (): boolean => typeof document !== 'undefined' && document.hidden;\n poll = setInterval(() => {\n if (!hidden()) reconcile(tokens);\n }, PRESENCE_POLL_MS);\n if (typeof document !== 'undefined') {\n shown = () => {\n if (!hidden()) reconcile(tokens);\n };\n document.addEventListener('visibilitychange', shown);\n }\n reconcile(tokens);\n },\n (err) => {\n if (disposed) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n },\n );\n return dispose;\n }\n\n private async queryPresence(tokens: string[]): Promise<DevicePresence[]> {\n const chunks: string[][] = [];\n for (let i = 0; i < tokens.length; i += PRESENCE_CHUNK) chunks.push(tokens.slice(i, i + PRESENCE_CHUNK));\n const reads = await Promise.all(chunks.map((deviceTokens) => gql(STATE_AREA, DEVICE_PRESENCE_QUERY, { deviceTokens })));\n return reads.flatMap((r) => r.deviceStatesByDeviceToken);\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 stream of this.deadStreams) stream.unsubscribe();\n this.deadStreams.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 for (const dispose of [...this.presenceDisposers]) dispose();\n this.presenceDisposers.clear();\n }\n\n // The number of distinct upstream device streams currently open (observability\n // + test hook \u2014 proves multiplexing collapses shared devices to one stream).\n get openStreamCount(): number {\n return this.streams.size;\n }\n\n // isDatasourceAvailable reports whether a widget's bound device still exists. Only a\n // device selector is validated (a slot bound to a device arrives as one) \u2014 an anchor, an\n // unbound slot, or no datasource has a legitimate empty state and is always \"available\".\n // Fails open: an existence-check outage returns true (never falsely mark a live device\n // gone).\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n const deviceToken = this.availabilityToken(datasource);\n if (deviceToken === undefined) return true;\n try {\n return await this.resolver.deviceExists(deviceToken);\n } catch {\n return true;\n }\n }\n\n // isDatasourceSupported reports whether the viewer is offered the binding at all: false only\n // when resolving an anchor is refused as SCOPE_UNSUPPORTED (an anchor the platform still\n // declines for a customer user; areas and assets are answered through anchorDevices). The resolution is the same\n // memoized one the widget's channel makes, so a supported anchor costs no second read. Every other answer, including a failure, is true.\n async isDatasourceSupported(datasource: DatasourceSelector | undefined): Promise<boolean> {\n if (datasource?.kind !== 'anchor') return true;\n try {\n await this.resolver.devicesForAnchor(datasource.anchor);\n return true;\n } catch (err) {\n return !isScopeUnsupportedError(err);\n }\n }\n\n // availabilityToken returns the single device token whose existence gates a widget's\n // availability, or undefined when there is nothing device-specific to validate (an\n // anchor's membership is self-validating; an unbound slot is a placeholder; a reserved\n // kind isn't resolved yet; an unresolved slot fails on its data channel instead).\n private availabilityToken(datasource: DatasourceSelector | undefined): string | undefined {\n if (!datasource) return undefined;\n // An empty token (a half-authored or hand-edited definition) has nothing to\n // validate \u2014 treat it like an unbound slot (available/empty), not a device that\n // \"no longer exists\", and skip the guaranteed-empty query.\n if (datasource.kind === 'device') return datasource.deviceToken || undefined;\n return undefined;\n }\n\n // resolveDevices turns a selector into the devices to stream, each with the\n // measurement names the widget wants (empty = all). Reserved selector kinds are\n // rejected here, mirroring the backend (Phase 1 ships device + anchor).\n private async resolveDevices(\n datasource: DatasourceSelector,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n switch (datasource.kind) {\n case 'device':\n return this.resolveBinding(\n { kind: 'device', deviceToken: datasource.deviceToken },\n new Set(datasource.measurements),\n );\n case 'anchor':\n return this.resolveBinding(\n { kind: 'anchor', anchor: datasource.anchor },\n new Set(datasource.measurements),\n );\n case 'unbound':\n // A slot with no binding, as the renderer resolved it: a valid placeholder (a\n // template the host hasn't bound, a selection not yet made), not an error.\n // Zero devices \u2014 a silent empty state, like an anchor with no members.\n return [];\n case 'slot':\n // \uD83D\uDD34 FAIL LOUDLY. The hub keeps no bindings, so a slot here was never resolved:\n // the host handed a raw definition widget to the data source instead of going\n // through the renderer (or resolveWidgetDatasource). Answering \"zero devices\"\n // would make that bug look exactly like a slot that is legitimately unbound.\n throw new Error(\n `dashboard slot '${datasource.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(\n `dashboard selector kind '${datasource.kind}' is not supported yet`,\n );\n }\n }\n\n // resolveBinding turns a concrete entity binding (device or anchor) into the\n // device streams to open, each carrying the given measurement names. Shared by the\n // device/anchor selectors and by slot resolution (whose binding is either kind).\n // A device binding streams its token directly (measurementStream is keyed by token,\n // per ADR-044); an anchor expands to its member device tokens.\n private async resolveBinding(\n binding: SlotBinding,\n names: Set<string>,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n if (binding.kind === 'device') {\n return [{ deviceToken: binding.deviceToken, names }];\n }\n const tokens = await this.resolver.devicesForAnchor(binding.anchor);\n return tokens.map((deviceToken) => ({ deviceToken, names }));\n }\n\n // attach registers a subscriber on a device's stream (opening the upstream on\n // the first subscriber) and returns a detacher that drops it and closes the\n // upstream once the last subscriber leaves.\n private attach(deviceToken: string, names: Set<string>, sink: WidgetStreamSink): () => void {\n const stream = this.ensureStream(deviceToken);\n const subscriber: Subscriber = { names, sink };\n stream.subscribers.add(subscriber);\n\n return () => {\n if (!stream.subscribers.delete(subscriber)) return;\n // Only tear down and forget the stream if it is STILL the registered stream\n // for this device \u2014 an upstream error may have evicted and replaced it, and a\n // lingering old subscriber's detach must not delete the replacement.\n if (stream.subscribers.size === 0) {\n // An evicted stream's operation is already closed; this releases its connection\n // handle, which it kept so a dead socket reads as down while anything still shows it.\n stream.unsubscribe();\n this.deadStreams.delete(stream);\n if (this.streams.get(deviceToken) === stream) 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 handle = this.connection.register();\n let closeSocket: (() => void) | undefined;\n let operationOpen = true;\n // Consecutive server-side ends with no reading in between; spaces the re-opens below.\n let ended = 0;\n // This operation's socket is up, as far as its last connected/closed said.\n let up = true;\n let reopen: ReturnType<typeof setTimeout> | undefined;\n const closeOperation = (): void => {\n if (!operationOpen) return;\n operationOpen = false;\n clearTimeout(reopen);\n closeSocket?.();\n };\n const open = (): void => {\n closeSocket = subscribe(EVENT_AREA, MEASUREMENT_STREAM, variables, adapter);\n };\n // The stream resumed after an interruption: whatever was sent meanwhile was never delivered,\n // and each subscriber is told so it can read the missed history back. Sent to the stream's own\n // subscribers. Never called once the operation is closed: closing it drops its connection\n // listener and clears a pending re-open.\n const resumed = (): void => {\n stream.subscribers.forEach((s) => s.sink.reconnected?.());\n };\n const adapter: SubscriptionSink<MeasurementStreamResult> = {\n next: (data) => {\n ended = 0;\n this.fanout(deviceToken, data.measurementStream);\n },\n connected: (wasRetry) => {\n up = true;\n handle.connected();\n // graphql-ws re-sends this operation on the new socket; a first connect is no gap.\n if (wasRetry) resumed();\n },\n closed: () => {\n up = false;\n handle.closed();\n },\n // \uD83D\uDD34 The server ended this operation on a socket that is still up: its live source\n // closed, or could not be opened (a resolver error reaches the client as an error-only\n // `next`, which carries no data, and then this). Left alone, the stream would sit here\n // receiving nothing while the board read `live` -- a widget frozen until someone reloads,\n // one stream at a time. Open it again, after a doubling wait capped at 30 s, for as long\n // as anything shows it. Two other completes are not the server's and are never re-opened:\n // a widget's own unsubscribe (closeOperation has marked it), and the client being disposed\n // on sign-out, which closes the socket first (re-opening would bring back the socket the\n // sign-out exists to drop).\n complete: () => {\n if (!operationOpen || !up) return;\n closeSocket?.();\n reopen = setTimeout(() => {\n open();\n // Opened on a socket that is already up, so no connection_ack follows to say so.\n resumed();\n }, Math.min(1000 * 2 ** ended++, 30_000));\n },\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 // \uD83D\uDD34 Close the operation but KEEP the connection handle until the last subscriber\n // detaches. A dropped socket is retried for as long as the stream is wanted (the\n // client never gives up on one), so what lands here is final: a close that says\n // the request is wrong (a refused token among them) or the server ending this\n // operation. A fatal close errors every operation at once, so releasing here would\n // empty the tracker exactly when the socket is down for good, and a board whose\n // every stream had died would read `live`. A handle\n // whose socket was up when the operation failed (a server-side error) counts as up.\n closeOperation();\n if (stream.subscribers.size > 0) this.deadStreams.add(stream);\n else handle.release();\n for (const subscriber of stream.subscribers) subscriber.sink.error?.(err);\n },\n };\n const variables: MeasurementStreamVariables = { deviceToken, name: null };\n open();\n if (!operationOpen) closeSocket?.(); // it failed while subscribing\n stream.unsubscribe = () => {\n handle.release();\n closeOperation();\n };\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 // Only alarms raised on a device of this type (its token).\n deviceType?: string | null;\n // Only alarms whose span overlaps this range (RFC 3339, each optional).\n startTime?: string | null;\n endTime?: string | null;\n // Only alarms stamped with this customer, area or asset.\n anchor?: AlarmAnchorInput | null;\n}\n\nexport interface AlarmAnchorInput {\n type: string;\n token: string;\n}\n\nexport interface AlarmsQueryResult {\n alarms: {\n results: AlarmRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface AlarmsQueryVariables {\n criteria: AlarmSearchCriteriaInput;\n}\n\nexport const ALARMS_QUERY = `\n query DashboardAlarms($criteria: AlarmSearchCriteria!) {\n alarms(criteria: $criteria) {\n results {\n token\n originatorType\n originatorToken\n alarmKey\n metricKey\n state\n acknowledged\n severity\n raisedTime\n clearedTime\n acknowledgedTime\n acknowledgedBy\n lastValue\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<AlarmsQueryResult, AlarmsQueryVariables>;\n\n// \u2500\u2500 Device types of a set of originators (one batched read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The alarm row carries only its originator's token. A widget that groups alarms by device type\n// resolves them with ONE devicesByToken read (up to 1000 tokens), never one query per alarm.\n\nexport interface AlarmDeviceTypesResult {\n devicesByToken: Array<{ token: string; deviceType: { token: string; name: string | null } }>;\n}\n\nexport interface AlarmDeviceTypesVariables {\n tokens: string[];\n}\n\nexport const ALARM_DEVICE_TYPES = `\n query DashboardAlarmDeviceTypes($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n deviceType {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<AlarmDeviceTypesResult, AlarmDeviceTypesVariables>;\n\n// \u2500\u2500 Alarm counts per device type (one grouped read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The tab strip of an alarm table grouped by type: how many alarms match, per type and severity,\n// counted by device-management, so the badges are exact however many alarms there are.\n\nexport type AlarmCountCriteriaInput = Omit<AlarmSearchCriteriaInput, 'pageNumber' | 'pageSize' | 'deviceType' | 'startTime' | 'endTime'>;\n\nexport interface AlarmTypeCountsResult {\n alarmCountsByDeviceType: Array<{ deviceType: { token: string; name: string | null }; severity: string; count: number }>;\n}\n\nexport interface AlarmTypeCountsVariables {\n criteria: AlarmCountCriteriaInput;\n}\n\nexport const ALARM_TYPE_COUNTS = `\n query DashboardAlarmTypeCounts($criteria: AlarmCountCriteria!) {\n alarmCountsByDeviceType(criteria: $criteria) {\n deviceType {\n token\n name\n }\n severity\n count\n }\n }\n` as unknown as TypedDocument<AlarmTypeCountsResult, AlarmTypeCountsVariables>;\n\n// \u2500\u2500 Alarm counts per severity (one grouped read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A severity breakdown. With no deviceType it counts every alarm the search would return, whatever\n// its originator, so the counts sum to the alarm search's total; with one, only that type's devices.\n\nexport interface AlarmSeverityCountsResult {\n alarmCountsBySeverity: Array<{ severity: string; count: number }>;\n}\n\nexport interface AlarmSeverityCountsVariables {\n criteria: AlarmCountCriteriaInput;\n deviceType: string | null;\n}\n\nexport const ALARM_SEVERITY_COUNTS = `\n query DashboardAlarmSeverityCounts($criteria: AlarmCountCriteria!, $deviceType: String) {\n alarmCountsBySeverity(criteria: $criteria, deviceType: $deviceType) {\n severity\n count\n }\n }\n` as unknown as TypedDocument<AlarmSeverityCountsResult, AlarmSeverityCountsVariables>;\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 anchor?: AlarmAnchorInput | 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 $anchor: AlarmAnchor\n ) {\n alarmStream(\n originatorType: $originatorType\n originator: $originator\n state: $state\n severity: $severity\n alarmKey: $alarmKey\n anchor: $anchor\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 // Only commands sent (queued, when never sent) inside this range (RFC 3339, each optional).\n startTime?: string | null;\n endTime?: 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 device-state document the hub's presence read drives, hand-authored (packages carry no\n// graphql-codegen; see measurement-doc).\n//\n// `deviceStatesByDeviceToken` is the BATCH read (up to 1000 tokens): one round trip covers a whole\n// board's worth of devices. A device with no state yet is ABSENT from the result, not present with\n// nulls -- the service's contract, which the channel keeps. Requires state:read.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { DevicePresence } from '../types';\n\nexport interface DeviceStatesResult {\n deviceStatesByDeviceToken: DevicePresence[];\n}\n\nexport interface DeviceStatesVariables {\n deviceTokens: string[];\n}\n\nexport const DEVICE_PRESENCE_QUERY = `\n query DashboardDevicePresence($deviceTokens: [String!]!) {\n deviceStatesByDeviceToken(deviceTokens: $deviceTokens) {\n deviceToken\n active\n lastActivityTime\n inactivityTimeout\n }\n }\n` as unknown as TypedDocument<DeviceStatesResult, DeviceStatesVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What a fleet widget (one row or one bar per member of an anchor) reads from a data source.\n//\n// The measurement channel keys its latest values by measurement NAME alone, so an anchor of ten\n// thermostats collapses into one \"temperature\". A fleet widget needs the device kept: a roster of\n// the anchor's members, each member's own live samples (every sample already carries its device\n// token), and one seed read per member for what it last reported before the board was opened.\n//\n// There is no batched \"latest value of these devices\" read in the platform: the event store\n// aggregates one device at a time (bucketedMeasurements), and device-state holds presence and\n// position but no measurement values. The seed is therefore read per member, and what keeps that\n// from being a query storm is that (a) it is the only per-member read -- the roster is one\n// relationship page plus one batched name lookup, whatever the size of the fleet -- (b) a hub-wide\n// limiter bounds how many are in flight, (c) identical reads share one request, and (d) the widget's\n// plan asks for as little as answers it (the fleet widgets read the last few minutes first; see\n// fleet-seed.ts in the widgets package).\n\nimport type { MeasurementBucket } from './queries';\nimport type { DevicePresence, MeasurementSample } from './types';\n\n// One device of a roster. `name` is null when the source does not know it. `deviceType` is the\n// token of its device type, when the source knows it: it is what the device's declared metrics are\n// looked up by, once per type.\nexport interface MemberRef {\n token: string;\n name: string | null;\n deviceType?: string | null;\n}\n\n// The members a selector resolves to. `total` is the size of the anchor, which can exceed\n// `members.length`: the resolver reads one bounded page, and a fleet beyond it must be reported as\n// truncated rather than shown as if it were whole. `unbound` marks a slot with no binding (zero\n// members, but a placeholder, not an empty fleet).\nexport interface MemberRoster {\n members: MemberRef[];\n total: number;\n unbound: boolean;\n}\n\n// Where a fleet widget's data lands. `roster` arrives once, before any `next`.\nexport interface MemberSink {\n roster(roster: MemberRoster): void;\n next(sample: MeasurementSample): void;\n // The viewer may not read this data (a refusal, not a failure): the widget says so in words\n // rather than showing the generic error pane every ordinary member would see for a working platform.\n forbidden?(): void;\n error?(err: unknown): void;\n // A member's live feed resumed after an interruption (a dropped socket, or the server ending\n // the stream): readings sent meanwhile were never delivered, so a widget that seeds from history\n // reads it again. Called once per member stream that resumed. Optional on both sides: a source\n // that never drops a feed does not call it.\n reconnected?(): void;\n}\n\n// Where a presence widget's data lands: the roster once, then a whole snapshot of the roster's\n// connectivity on every poll. A token the snapshot does not carry has no device-state row (the\n// service omits those), which is a settled answer -- never connected -- not a failure.\nexport interface PresenceSink {\n roster(roster: MemberRoster): void;\n next(states: readonly DevicePresence[]): void;\n // The viewer may not read device state (a refusal, not a failure).\n forbidden?(): void;\n error?(err: unknown): void;\n}\n\n// Why one member's seed read did not come back.\nexport type SeedFailure = 'forbidden' | 'failed';\n\n// One bucketed read of ONE member, as a seed plan asks for it: the hub fills in the device. `name`\n// narrows the read to one measurement (the store takes one name or none).\nexport interface MemberBucketCriteria {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n name?: string;\n}\n\nexport type MemberBucketRead = (criteria: MemberBucketCriteria) => Promise<MeasurementBucket[]>;\n\n// What to read for one member and how to make its seed of the answer. The WIDGET decides that (which\n// span, which measurements, read in what order); the hub supplies `read` -- bounded hub-wide and\n// shared, so two widgets asking one device the same question cost one request -- and settles the\n// result. A plan that rejects settles that member as failed (or forbidden).\nexport interface MemberSeedPlan {\n seed(deviceToken: string, read: MemberBucketRead): Promise<MemberSeed>;\n}\n\n// What one member last reported inside a window: its newest reading per measurement name, and every\n// name it reported at all (which is how \"does not report this\" is told from \"sent nothing\").\nexport interface MemberSeed {\n latest: Record<string, MeasurementSample>;\n observed: string[];\n // Names it reported in the window whose newest reading the plan chose not to read (a bound on its\n // reads): the device HAS data for them, so they must never read as \"no data\".\n unread?: string[];\n}\n\n// How many seed reads may be in flight at once across the whole hub. Six is what a browser opens\n// to one origin over HTTP/1.1, so a larger number only queues in the browser; a fleet of 500 then\n// takes ~84 rounds rather than 500 simultaneous requests starving every other widget on the board.\nexport const MEMBER_SEED_CONCURRENCY = 6;\n\n// The reason a queued read gave up because its widget went away before it started.\nexport const SEED_CANCELLED = Symbol('member seed cancelled');\n\n// A first-in-first-out limiter: at most `max` tasks run at once, the rest wait their turn. A task\n// whose `cancelled()` is true when its turn comes is dropped without running -- a widget that\n// changed its span or unmounted must not leave hundreds of reads queued behind it.\nexport function createLimiter(max: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = (): void => {\n while (active < max && queue.length > 0) queue.shift()!();\n };\n return function run<T>(task: () => Promise<T>, cancelled: () => boolean = () => false): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const start = (): void => {\n if (cancelled()) {\n reject(SEED_CANCELLED);\n return;\n }\n active += 1;\n task().then(resolve, reject).finally(() => {\n active -= 1;\n next();\n });\n };\n queue.push(start);\n next();\n });\n };\n}\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 { alarmOverlaps, commandInRange } from './event-window';\nimport type { HistoryWindow } from './history';\nimport type { MemberRef, MemberSeed, MemberSeedPlan, MemberSink, PresenceSink } from './members';\nimport type { MeasurementBucket } from './queries';\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 AlarmDeviceType,\n AlarmRow,\n AlarmTypeCount,\n CommandRow,\n DatasourceSelector,\n DevicePresence,\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// Every generator id, for a picker. Ids only: the display name is the host's to localize,\n// and the id is what a definition stores, so it never changes with the language.\nexport const SYNTHETIC_GENERATORS: ReadonlyArray<SyntheticGenerator> = ['sine', 'ramp', 'random-walk'];\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// The most buckets one preview read returns per measurement (the store's own ceiling is far higher).\nconst MAX_PREVIEW_BUCKETS = 500;\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.\n// The device types of the originators above, for a table that groups by type. device-99 has none\n// (unresolved), which is the row a grouping widget keeps under All only.\nconst SYNTHETIC_DEVICE_TYPES: Readonly<Record<string, AlarmDeviceType>> = {\n 'thermostat-01': { token: 'thermostat', name: 'Thermostat' },\n 'sensor-14': { token: 'sensor', name: 'Sensor' },\n 'sensor-03': { token: 'sensor', name: 'Sensor' },\n 'gateway-02': { token: 'gateway', name: 'Gateway' },\n};\n\nfunction withType(token: string | null): Pick<AlarmRow, 'deviceType'> {\n const deviceType = token ? SYNTHETIC_DEVICE_TYPES[token] : undefined;\n return deviceType ? { deviceType } : {};\n}\n\nconst SYNTHETIC_ALARMS: ReadonlyArray<\n Pick<\n AlarmRow,\n 'severity' | 'state' | 'acknowledged' | 'alarmKey' | 'metricKey' | 'lastValue' | 'originatorToken'\n >\n> = [\n { severity: 'CRITICAL', state: 'ACTIVE', acknowledged: false, alarmKey: 'over-temperature', metricKey: 'temperature', lastValue: 87.4, originatorToken: 'thermostat-01' },\n { severity: 'MAJOR', state: 'ACTIVE', acknowledged: true, alarmKey: 'low-battery', metricKey: 'battery', lastValue: 12, originatorToken: 'sensor-14' },\n { severity: 'MINOR', state: 'ACTIVE', acknowledged: false, alarmKey: 'humidity-high', metricKey: 'humidity', lastValue: 78, originatorToken: 'sensor-03' },\n { severity: 'WARNING', state: 'CLEARED', acknowledged: true, alarmKey: 'signal-weak', metricKey: 'rssi', lastValue: -89, originatorToken: 'gateway-02' },\n { severity: 'INDETERMINATE', state: 'ACTIVE', acknowledged: false, alarmKey: 'self-test', metricKey: 'status', lastValue: null, originatorToken: 'device-99' },\n];\n\n// A canonical spread of synthetic commands (one per lifecycle stage) so an author\n// previewing a command-button sees a populated, representative history \u2014 an in-flight\n// command, one withheld for an absent device, one parked after finding nobody home, a\n// completed one, a failure, a cancellation \u2014 before any real command has been issued.\n//\n// `dispatched` says whether this command was ever actually put on the wire, which is\n// what decides sentTime below. It is a per-fixture fact, not something derivable from\n// the status: a CANCELLED command may have been called off while still held (never\n// sent) or after dispatch, and the preview picks the former.\nconst SYNTHETIC_COMMANDS: ReadonlyArray<\n Pick<CommandRow, 'name' | 'status' | 'payload' | 'responsePayload' | 'error'> & { dispatched: boolean }\n> = [\n { name: 'reboot', status: 'SENT', payload: '{\"delaySeconds\":5}', responsePayload: null, error: null, dispatched: true },\n // Early in the list on purpose: an author can cap the widget's row count, and a\n // withheld command is the state a preview most needs to show.\n { name: 'self-test', status: 'HELD', payload: null, responsePayload: null, error: null, dispatched: false },\n // The other way a command ends up waiting on an absent device: this one was published\n // and found nobody there, so it is parked until the device wakes. Included next to HELD\n // because the two look alike in a list and an author laying out a command-button needs\n // to see that they are distinguishable. `dispatched` is false, matching the service: it\n // CLEARS sent_time when it parks a command, because a dispatch that reached nobody sent\n // nothing. That is also why a parked command is still cancellable \u2014 nothing has taken\n // delivery of it.\n { name: 'sync-clock', status: 'PARKED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'set-interval', status: 'SUCCESSFUL', payload: '{\"seconds\":30}', responsePayload: '{\"ok\":true}', error: null, dispatched: true },\n { name: 'calibrate', status: 'QUEUED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'firmware-update', status: 'FAILED', payload: '{\"version\":\"2.1.0\"}', responsePayload: null, error: 'device offline', dispatched: true },\n { name: 'open-valve', status: 'CANCELLED', payload: '{\"percent\":100}', responsePayload: null, error: null, dispatched: false },\n];\n\n// The device a synthetic command-button reports as its target, so the Send control\n// renders (a real widget needs a bound device to issue against).\nconst SYNTHETIC_COMMAND_DEVICE = 'synthetic-device';\n\n// A canonical spread of synthetic positions so an author previewing a map sees markers\n// laid out the way a real fleet would be \u2014 several devices a short distance apart, at a\n// zoom a map actually renders at, rather than one dot in the middle.\n//\n// \uD83D\uDD34 THE OPTIONALS ARE DELIBERATELY MIXED, INCLUDING NULLS AND A GENUINE ZERO. Preview\n// is where an author decides what a marker looks like, so it must show the real range:\n// `sp-loader-03` reports no heading and no speed at all (a receiver that does not supply\n// them), while `sp-dozer-01` reports speed 0 \u2014 parked, which is a reading, not an\n// absence. A preview that filled every optional in would teach an author that a marker\n// always has a heading, and the first real fleet would prove otherwise.\nconst SYNTHETIC_LOCATIONS: ReadonlyArray<\n Pick<LocationSample, 'deviceToken' | 'latitude' | 'longitude' | 'elevation' | 'accuracy' | 'speed' | 'heading'>\n> = [\n { deviceToken: 'sp-dozer-01', latitude: 33.749, longitude: -84.388, elevation: 320.5, accuracy: 4.2, speed: 0, heading: 271.5 },\n { deviceToken: 'sp-excavator-02', latitude: 33.7512, longitude: -84.3858, elevation: 318.1, accuracy: 3.1, speed: 1.4, heading: 88 },\n { deviceToken: 'sp-loader-03', latitude: 33.7468, longitude: -84.3903, elevation: null, accuracy: 9.8, speed: null, heading: null },\n { deviceToken: 'sp-truck-04', latitude: 33.7481, longitude: -84.3841, elevation: 315.9, accuracy: 2.5, speed: 8.3, heading: 12.25 },\n];\n\n// Deterministic small hash of a name \u2192 a stable phase offset, so multiple series on\n// one dashboard are visibly out of phase rather than overlapping identically.\nfunction hashName(name: string): number {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (Math.imul(h, 31) + name.charCodeAt(i)) | 0;\n return h >>> 0;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nexport class SyntheticDataSource implements WidgetDataSource, WidgetActions {\n private readonly generator: SyntheticGenerator;\n private readonly intervalMs: number;\n private readonly backfill: number;\n private readonly min: number;\n private readonly max: number;\n private readonly periodMs: number;\n // Live timers, tracked so disposeAll() can stop every widget's stream at once.\n private readonly timers = new Set<ReturnType<typeof setInterval>>();\n\n constructor(config: SyntheticDataSourceConfig = {}) {\n this.generator = config.generator ?? 'sine';\n // Guard the divisors/counts so a misconfigured host can't produce NaN values\n // (periodMs:0 \u2192 tMs/0) or a zero-delay flood: intervalMs/periodMs floor at 1ms,\n // backfill can't go negative.\n this.intervalMs = Math.max(1, config.intervalMs ?? 1000);\n this.backfill = Math.max(0, config.backfill ?? 60);\n this.min = config.min ?? 0;\n this.max = config.max ?? 100;\n this.periodMs = Math.max(1, config.periodMs ?? 60_000);\n }\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n // Per-name random-walk state, private to this subscription so two widgets don't\n // share (and corrupt) each other's walk.\n const walk = new Map<string, number>();\n let seq = 0;\n\n const emit = (name: string, tMs: number): void => {\n const value = this.valueFor(name, tMs, walk);\n const s: MeasurementSample = {\n id: `syn-${seq++}`,\n deviceToken: 'synthetic',\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value,\n classifier: null,\n };\n sink.next(s);\n };\n\n // Backfill oldest \u2192 newest so the widget window is chronological (and the\n // random-walk advances forward through the backfilled points).\n const now = Date.now();\n for (let i = this.backfill - 1; i >= 0; i--) {\n const tMs = now - i * this.intervalMs;\n for (const name of names) emit(name, tMs);\n }\n\n const timer = setInterval(() => {\n const tMs = Date.now();\n for (const name of names) emit(name, tMs);\n }, this.intervalMs);\n this.timers.add(timer);\n\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeAlarms emits a synthetic alarm snapshot for preview. The canonical set is\n // filtered by the subscription (state/severity/ack) so the preview reflects what the\n // widget is configured to show; scope (datasource) is ignored \u2014 preview never resolves\n // a device. Re-emits on the same cadence with advancing raised times so the table\n // looks live. Returns whole snapshots, matching the live hub's contract.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n const typed = subscription.enrichDeviceType === true;\n const matches = SYNTHETIC_ALARMS.filter(\n (a) =>\n (subscription.deviceType === undefined ||\n SYNTHETIC_DEVICE_TYPES[a.originatorToken ?? '']?.token === subscription.deviceType) &&\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.severity === subscription.severity) &&\n (subscription.acknowledged == null || a.acknowledged === subscription.acknowledged),\n );\n\n const emit = (): void => {\n const now = Date.now();\n const rows: AlarmRow[] = matches.map((a, i) => {\n const raised = new Date(now - i * 45_000).toISOString();\n return {\n token: `syn-alarm-${i}`,\n originatorType: 'device',\n alarmKey: a.alarmKey,\n metricKey: a.metricKey,\n state: a.state,\n acknowledged: a.acknowledged,\n severity: a.severity,\n originatorToken: a.originatorToken,\n lastValue: a.lastValue,\n raisedTime: raised,\n clearedTime: a.state === 'CLEARED' ? raised : null,\n acknowledgedTime: a.acknowledged ? raised : null,\n acknowledgedBy: a.acknowledged ? 'preview@devicechain' : null,\n ...(typed ? withType(a.originatorToken) : {}),\n };\n });\n if (subscription.countBySeverity) {\n // The live hub's severity count, over the same filtered set (type facet included).\n const bySeverity = new Map<string, number>();\n for (const r of rows) bySeverity.set(r.severity, (bySeverity.get(r.severity) ?? 0) + 1);\n const severityCounts = [...bySeverity].map(([severity, count]) => ({ severity, count }));\n sink.next({ alarms: [], total: rows.length, severityCounts });\n return;\n }\n if (subscription.countByDeviceType) {\n // The live hub's count read, over the same filtered set: per (type, severity).\n const counts = new Map<string, AlarmTypeCount>();\n for (const r of rows) {\n const deviceType = SYNTHETIC_DEVICE_TYPES[r.originatorToken ?? ''];\n if (!deviceType) continue;\n const key = `${deviceType.token}/${r.severity}`;\n const prior = counts.get(key);\n counts.set(key, { deviceType, severity: r.severity, count: (prior?.count ?? 0) + 1 });\n }\n const typeCounts = [...counts.values()];\n sink.next({ alarms: [], total: typeCounts.reduce((n, c) => n + c.count, 0), typeCounts });\n return;\n }\n const inRange = subscription.window ? rows.filter((r) => alarmOverlaps(r, subscription.window!, now)) : rows;\n sink.next({ alarms: inRange.slice(0, subscription.pageSize), total: inRange.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 const inRange = subscription.window ? commands.filter((c) => commandInRange(c, subscription.window!, now)) : commands;\n sink.next({\n deviceToken: SYNTHETIC_COMMAND_DEVICE,\n commands: inRange.slice(0, subscription.pageSize),\n total: inRange.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 // bucketMeasurements answers with ONE bucket per measurement spanning the window, folded from a\n // synthetic reading per interval, so a preview stat reads something the waveform actually did\n // (a minimum and maximum inside [min, max]).\n async bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n const from = Date.parse(window.startTime);\n const to = Date.parse(window.endTime);\n // A window asked in buckets narrower than itself (a history table, a trend) gets one bucket per\n // interval, aligned on the epoch as the store's are; one as wide as the window gets the single\n // bucket below.\n const width = window.intervalSeconds * 1000;\n if (width > 0 && width < to - from) {\n // Past the cap the OLDEST buckets go: a table lists newest first and a trend ends at now, so the\n // preview keeps the end of the window, as the screen does.\n const aligned = Math.floor(from / width) * width;\n const total = Math.ceil((to - aligned) / width);\n const count = Math.min(total, MAX_PREVIEW_BUCKETS);\n const first = aligned + (total - count) * width;\n return names.flatMap((name) => {\n const walk = new Map<string, number>();\n return Array.from({ length: count }, (_, i) => {\n const start = first + i * width;\n // Six instants inside the bucket, never the next bucket's start.\n const values = Array.from({ length: 6 }, (_v, j) => this.valueFor(name, start + (width * j) / 6, walk));\n const sum = values.reduce((a, v) => a + v, 0);\n return { bucketStart: new Date(start).toISOString(), name, avg: sum / values.length, min: Math.min(...values), max: Math.max(...values), sum, count: values.length };\n });\n });\n }\n return names.map((name) => {\n const walk = new Map<string, number>();\n const values = Array.from({ length: 120 }, (_, i) => this.valueFor(name, from + ((to - from) * i) / 119, walk));\n const sum = values.reduce((a, v) => a + v, 0);\n return {\n bucketStart: window.startTime,\n name,\n avg: sum / values.length,\n min: Math.min(...values),\n max: Math.max(...values),\n sum,\n count: values.length,\n };\n });\n }\n\n // A preview fleet: a handful of fabricated devices, each out of phase with the others (the\n // series is keyed by device and name together), and one that never reports its second\n // measurement, so a table previews its \"does not report this\" cell. An unbound slot has no\n // members, as in the live hub.\n private previewMembers(datasource: DatasourceSelector): MemberRef[] {\n if (datasource.kind === 'unbound') return [];\n return Array.from({ length: 5 }, (_, i) => ({ token: `synthetic-${i + 1}`, name: `Unit ${i + 1}` }));\n }\n\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n const members = this.previewMembers(datasource);\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n const walk = new Map<string, number>();\n let seq = 0;\n const emit = (token: string, tMs: number): void => {\n // One member never reports past its first measurement.\n for (const name of token === 'synthetic-4' ? names.slice(0, 1) : names) {\n sink.next({\n id: `syn-m-${seq++}`,\n deviceToken: token,\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value: this.valueFor(`${token}/${name}`, tMs, walk),\n classifier: null,\n });\n }\n };\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n for (const m of members) emit(m.token, Date.now());\n const timer = setInterval(() => {\n for (const m of members) emit(m.token, Date.now());\n }, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // A preview fleet's connectivity: three online, one offline (silent for hours) and one that never\n // reported, with no state row, so every state a presence widget draws is previewed. Last-active\n // times are offsets from now, so the preview always reads as just-seen for the online ones.\n subscribePresence(datasource: DatasourceSelector, sink: PresenceSink): () => void {\n // A device is its own roster of one; a group is the preview fleet.\n const members = datasource.kind === 'device' ? [{ token: datasource.deviceToken, name: null }] : this.previewMembers(datasource);\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n const emit = (): void => {\n const now = Date.now();\n const states: DevicePresence[] = [];\n members.forEach((m, i) => {\n if (m.token === 'synthetic-5') return; // never reported: no state row\n const offline = m.token === 'synthetic-4';\n states.push({\n deviceToken: m.token,\n active: !offline,\n lastActivityTime: new Date(now - (offline ? 2 * 3600_000 : (i + 1) * 20_000)).toISOString(),\n inactivityTimeout: 600,\n });\n });\n sink.next(states);\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 // A preview has no history to read: its members report the moment they are subscribed, so the\n // seed settles empty and the live feed alone fills the cells.\n async readMemberSeeds(\n deviceTokens: readonly string[],\n _plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null) => void,\n ): Promise<void> {\n for (const token of deviceTokens) onSeed(token, { latest: {}, observed: [] });\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\nimport type { AnchorTarget } from './types';\n\n// The board-recording format: one captured run of a board, as a plain JSON document a\n// RecordedDataSource can play back. This file owns the SHAPE and the parser; playback is in\n// recorded.ts. The document is produced by the Sitepulse recording converter\n// (demos/sitepulse-unity/tools/recording_to_board.py, version 1, with a demo's profile; Towerpulse's is demos/towerpulse-unity/tools/board-replay.profile.json) and by dc-simulator's\n// sitepulse-record (backend/sims/dc-simulator/recording, version 2); each writer's own\n// documentation is its side of this contract, and they must change together.\n//\n// \uD83D\uDD34 THE PARSER IS FAIL-CLOSED, AND THAT IS ITS WHOLE JOB. A recording is data produced by\n// another tool and published as a static file, so the reader is the last place a bad one\n// can be stopped before it is shown to a person as \"what the platform reported\". An\n// unknown key, a wrong kind or version, a ragged column, an unsorted time axis, a device or\n// metric index out of range, or a channel claim the document cannot honour is a thrown\n// RecordingFormatError naming the path, never a best-effort read. In particular an unknown\n// key is refused rather than ignored: an ignored key is a field somebody believed was being\n// honoured.\n//\n// Layout (formatVersion 1). Every time is `t`/`tMs`, in milliseconds from the START of the\n// run, as the recording viewer APPLIED the update (the first moment a viewer could have\n// seen it). Measurements are COLUMNS (one entry per row, parallel arrays): `d` indexes\n// `devices`, `n` indexes `channels.measurements`, `t` is the time, `v` the value and `s` is\n// 1 for a row the platform itself seeded at the start of the run. Alarms are a fold over\n// history: `snapshots` (the whole alarm set at a moment) plus `events` (one alarm's new\n// state), with ids re-minted as alarm-N. An excerpt carries `excerpt{fromMs,toMs}`: it\n// holds only the measurement rows of that window, but ALL alarm history up to its end.\n//\n// What a recording does NOT hold is declared, not implied: `channels` says which channels\n// were recorded at all. A player answers a channel that was not recorded with \"not in this\n// recording\" rather than with empty data, which would read as a fact about the machines.\n// No version records command data (`channels.commands` is always false).\n//\n// formatVersion 2 adds POSITIONS. `channels.locations` may be true, and `locations` is then\n// columns like the measurements': `d` (device index), `t` (ms from the start), `lat` and\n// `lon` (WGS84 degrees), `hdg` (degrees clockwise from north) and `spd` (m/s), the last two\n// null where the fix did not carry them. With `channels.locations` false the columns are\n// empty. Version 1 (no positions, `locations: []`) is still read, and parses to the same\n// shape with empty columns. Version 2 also lets `converter` carry the producing tool's\n// `name`: a recording read back from the platform's own history (dc-simulator's\n// sitepulse-record) times every row by the device's own stamp, where the Unity converter\n// times it by when the recording viewer applied it. And in version 2 an alarm may carry `val`:\n// the reading that RAISED it, which the platform shows as the alarm's value. sitepulse-record\n// writes it only from the device's own publish log (the value it sent for the alarm's metric\n// at the raise instant), so an alarm raised before the run carries none, and plays with no\n// value rather than a guessed one.\n//\n// Version 2 may also carry `declaredMetrics`: for each device `kind`, the measurement names that\n// kind's device-type profile DECLARES (what the live board reads from the active profile\n// version). It is what lets a replayed fleet tell \"does not report this\" (the profile never\n// declares it: the crusher has no fuel) from \"no data in range\" (declared, and silent). A kind\n// with no entry is unknown, and a player counts its devices as declaring everything, which is the\n// live board's rule for a profile it cannot read. Every key is a kind some device has, and every\n// list names at least one measurement, each once.\n\n// Version 2 may also carry `anchors`: the groupings (an area, a floor, a customer) the run's devices belonged to, each with the\n// device tokens that were members. A board bound to an area or a floor cannot be replayed without them: a recording that carried\n// only devices could not say which of them are on floor 4, and a player that guessed would show one building's numbers as another's.\n// A recording without `anchors` keeps the older rule (the board's one customer anchor holds every device). Each anchor is named once,\n// has at least one member, and every member is a device of the run.\n\n// The newest version this reader understands; it reads every version in\n// RECORDING_FORMAT_VERSIONS.\nexport const RECORDING_FORMAT_VERSION = 2;\nexport const RECORDING_FORMAT_VERSIONS = [1, 2] as const;\nexport const RECORDING_KIND = 'sitepulse-board-recording';\n\nconst ALARM_STATES = ['ACTIVE', 'CLEARED'] as const;\nconst ALARM_SEVERITIES = ['CRITICAL', 'MAJOR', 'MINOR', 'WARNING'] as const;\n\nexport class RecordingFormatError extends Error {\n constructor(\n public readonly path: string,\n reason: string,\n ) {\n super(`invalid recording at ${path}: ${reason}`);\n this.name = 'RecordingFormatError';\n }\n}\n\n// One segment of the platform's simulation clock: from `fromMs` on, it ran in `mode` at\n// `scale` times real time (1 = real time). Disclosed by the host; playback does not use it.\nexport interface RecordedClockSegment {\n fromMs: number;\n mode: string;\n scale: number;\n}\n\nexport interface RecordedDevice {\n id: string;\n token: string;\n kind: string;\n}\n\nexport interface RecordedChannels {\n measurements: string[];\n alarms: boolean;\n // Whether positions were recorded (only ever true from formatVersion 2).\n locations: boolean;\n // Always false: no version carries command data.\n commands: false;\n}\n\nexport interface RecordedChapter {\n tMs: number;\n name: string;\n note: string;\n}\n\n// The measurement rows, as parallel columns sorted by `t`.\nexport interface RecordedMeasurementColumns {\n d: number[];\n n: number[];\n t: number[];\n v: number[];\n s: Array<0 | 1>;\n}\n\n// One alarm as the recording saw it. `occ` is the platform's own time for THIS state: when\n// the alarm was raised for an ACTIVE row, when it cleared for a CLEARED row. `ack` is\n// present only when the alarm was acknowledged. `dev` is a device token.\nexport interface RecordedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: (typeof ALARM_STATES)[number];\n sev: (typeof ALARM_SEVERITIES)[number];\n occ: string;\n ack?: true;\n // The reading that raised the alarm (version 2; absent when the recording does not know it).\n val?: number;\n}\n\n// A snapshot is the whole alarm set as the recorder's query returned it. `requestedAtMs` is\n// when that query was SENT (it answers at `tMs`); an event applied after it is newer than\n// anything the answer can say about that alarm. `total` always equals the alarms listed: a\n// truncated answer cannot be played as if it were complete.\nexport interface RecordedAlarmSnapshot {\n tMs: number;\n requestedAtMs: number;\n total: number;\n alarms: RecordedAlarm[];\n}\n\nexport interface RecordedAlarmEvent extends RecordedAlarm {\n tMs: number;\n}\n\n// The position rows, as parallel columns sorted by `t`. Empty unless channels.locations.\nexport interface RecordedLocationColumns {\n d: number[];\n t: number[];\n lat: number[];\n lon: number[];\n hdg: Array<number | null>;\n spd: Array<number | null>;\n}\n\n// What each device kind's profile declares (formatVersion 2, optional): kind -> measurement names.\nexport type RecordedDeclarations = Record<string, string[]>;\n\n// A grouping the run's devices belonged to (formatVersion 2, optional): which devices were its members, by token.\nexport interface RecordedAnchor extends AnchorTarget {\n devices: string[];\n}\n\nexport interface BoardRecording {\n kind: typeof RECORDING_KIND;\n formatVersion: (typeof RECORDING_FORMAT_VERSIONS)[number];\n runId: string;\n startedAtUtc: string;\n durationMs: number;\n platformVersion: string;\n instance: string;\n tenant: string;\n build: { gitSha: string; sdkCommit: string };\n clock: RecordedClockSegment[];\n devices: RecordedDevice[];\n // Absent when the recording does not say (every version 1 recording, and older version 2 ones).\n declaredMetrics?: RecordedDeclarations;\n // Absent when the recording does not say (every version 1 recording, and older version 2 ones).\n anchors?: RecordedAnchor[];\n channels: RecordedChannels;\n board: { path: string; sourceCommit: string; sha256: string };\n sourceHashes: Record<string, string>;\n converter: { version: number; name?: string };\n chapters: RecordedChapter[];\n excerpt?: { fromMs: number; toMs: number };\n measurements: RecordedMeasurementColumns;\n alarms: { snapshots: RecordedAlarmSnapshot[]; events: RecordedAlarmEvent[] };\n locations: RecordedLocationColumns;\n}\n\n// ---- helpers that read a parsed recording ---------------------------------\n\n// The span a player may cover: the whole run, or the excerpt's window.\nexport function recordingBounds(rec: BoardRecording): { fromMs: number; toMs: number } {\n return rec.excerpt ? { ...rec.excerpt } : { fromMs: 0, toMs: rec.durationMs };\n}\n\n// The wall-clock instant (epoch ms) a recording offset corresponds to. Offsets are\n// measured in real elapsed time from the run's start, so this is the start plus the offset;\n// an accelerated SIMULATION clock changes what the platform's own timestamps say, not how\n// long the recording took (see simulationScaleAt for disclosing that).\nexport function recordedWallTimeMs(rec: BoardRecording, tMs: number): number {\n return Date.parse(rec.startedAtUtc) + tMs;\n}\n\n// The simulation clock's scale at a recording offset, or null before the first segment\n// begins (the recorder had not yet reported its clock).\nexport function simulationScaleAt(rec: BoardRecording, tMs: number): { mode: string; scale: number } | null {\n let hit: RecordedClockSegment | null = null;\n for (const seg of rec.clock) {\n if (seg.fromMs <= tMs) hit = seg;\n }\n return hit ? { mode: hit.mode, scale: hit.scale } : null;\n}\n\n// ---- parser ---------------------------------------------------------------\n\ntype Obj = Record<string, unknown>;\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// strict returns `v` as an object after checking it carries every required key and no key\n// outside required + optional.\nfunction strict(v: unknown, path: string, required: readonly string[], optional: readonly string[] = []): Obj {\n if (!isObj(v)) throw new RecordingFormatError(path, 'expected an object');\n const allowed = new Set([...required, ...optional]);\n for (const key of Object.keys(v)) {\n if (!allowed.has(key)) throw new RecordingFormatError(`${path}.${key}`, 'unknown key');\n }\n for (const key of required) {\n if (!Object.prototype.hasOwnProperty.call(v, key)) {\n throw new RecordingFormatError(`${path}.${key}`, 'missing required key');\n }\n }\n return v;\n}\n\nfunction str(v: unknown, path: string): string {\n if (typeof v !== 'string' || v === '') throw new RecordingFormatError(path, 'expected a non-empty string');\n return v;\n}\n\nfunction bool(v: unknown, path: string): boolean {\n if (typeof v !== 'boolean') throw new RecordingFormatError(path, 'expected a boolean');\n return v;\n}\n\nfunction num(v: unknown, path: string): number {\n if (typeof v !== 'number' || !Number.isFinite(v)) throw new RecordingFormatError(path, 'expected a finite number');\n return v;\n}\n\nfunction arr(v: unknown, path: string): unknown[] {\n if (!Array.isArray(v)) throw new RecordingFormatError(path, 'expected an array');\n return v;\n}\n\nfunction oneOf<T extends string>(v: unknown, path: string, allowed: readonly T[]): T {\n if (typeof v !== 'string' || !(allowed as readonly string[]).includes(v)) {\n throw new RecordingFormatError(path, `expected one of ${allowed.join(', ')}`);\n }\n return v as T;\n}\n\nfunction matching(v: unknown, path: string, re: RegExp, what: string): string {\n if (typeof v !== 'string' || !re.test(v)) throw new RecordingFormatError(path, `expected ${what}`);\n return v;\n}\n\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst HEX40 = /^[0-9a-f]{40}$/;\nconst HEX_SHORT = /^[0-9a-f]{7,40}$/;\n\nconst INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?Z$/;\n\n// Strict: a date that does not exist (2026-02-30) is refused, not rolled over to March.\nfunction instant(v: unknown, path: string): string {\n const s = str(v, path);\n const m = INSTANT.exec(s);\n if (m) {\n const [y, mo, d, h, mi, sec] = m.slice(1, 7).map(Number);\n const t = new Date(Date.UTC(y, mo - 1, d, h, mi, sec));\n if (\n t.getUTCFullYear() === y && t.getUTCMonth() === mo - 1 && t.getUTCDate() === d &&\n t.getUTCHours() === h && t.getUTCMinutes() === mi && t.getUTCSeconds() === sec\n ) {\n return s;\n }\n }\n throw new RecordingFormatError(path, 'expected an RFC 3339 UTC instant ending in Z that exists on the calendar');\n}\n\n// Orders two instants as parsed times, not as strings: \"...:10Z\" is BEFORE \"...:10.5Z\"\n// although it sorts after it as text. Fractions compare to the full digits given (the\n// platform writes up to 7), not just to the millisecond Date can hold.\nexport function compareInstants(a: string, b: string): number {\n const pa = INSTANT.exec(a);\n const pb = INSTANT.exec(b);\n if (!pa || !pb) throw new RangeError(`compareInstants: not an instant: ${pa ? b : a}`);\n const whole = (m: RegExpExecArray): number => Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`);\n const wa = whole(pa);\n const wb = whole(pb);\n if (wa !== wb) return wa < wb ? -1 : 1;\n const fa = (pa[7] ?? '').padEnd(12, '0');\n const fb = (pb[7] ?? '').padEnd(12, '0');\n return fa < fb ? -1 : fa > fb ? 1 : 0;\n}\n\nfunction column<T>(v: unknown, path: string, item: (x: unknown, p: string) => T, length?: number): T[] {\n const a = arr(v, path);\n if (length !== undefined && a.length !== length) {\n throw new RecordingFormatError(path, `length ${a.length} does not match the time column (${length})`);\n }\n return a.map((x, i) => item(x, `${path}[${i}]`));\n}\n\nfunction nonNegative(v: unknown, path: string): number {\n const n = num(v, path);\n if (n < 0) throw new RecordingFormatError(path, 'must not be negative');\n return n;\n}\n\nexport function parseBoardRecording(input: unknown): BoardRecording {\n const root = strict(\n input,\n '$',\n [\n 'kind', 'formatVersion', 'runId', 'startedAtUtc', 'durationMs', 'platformVersion', 'instance', 'tenant',\n 'build', 'clock', 'devices', 'channels', 'board', 'sourceHashes', 'converter', 'chapters',\n 'measurements', 'alarms', 'locations',\n ],\n ['excerpt', 'declaredMetrics', 'anchors'],\n );\n\n if (root.kind !== RECORDING_KIND) {\n throw new RecordingFormatError('$.kind', `expected ${JSON.stringify(RECORDING_KIND)}`);\n }\n const version = root.formatVersion;\n if (version !== 1 && version !== 2) {\n throw new RecordingFormatError(\n '$.formatVersion',\n `unsupported version ${JSON.stringify(root.formatVersion)}; this reader understands ${RECORDING_FORMAT_VERSIONS.join(', ')}`,\n );\n }\n const durationMs = num(root.durationMs, '$.durationMs');\n if (durationMs <= 0) throw new RecordingFormatError('$.durationMs', 'must be positive');\n\n const b = strict(root.build, '$.build', ['gitSha', 'sdkCommit']);\n const build = {\n gitSha: matching(b.gitSha, '$.build.gitSha', HEX_SHORT, 'a lowercase hex commit id'),\n sdkCommit: matching(b.sdkCommit, '$.build.sdkCommit', HEX_SHORT, 'a lowercase hex commit id'),\n };\n\n const clock = column(root.clock, '$.clock', (x, p): RecordedClockSegment => {\n const o = strict(x, p, ['fromMs', 'mode', 'scale']);\n const scale = num(o.scale, `${p}.scale`);\n if (scale <= 0) throw new RecordingFormatError(`${p}.scale`, 'must be positive');\n return { fromMs: nonNegative(o.fromMs, `${p}.fromMs`), mode: str(o.mode, `${p}.mode`), scale };\n });\n clock.forEach((seg, i) => {\n if (i > 0 && seg.fromMs <= clock[i - 1].fromMs) throw new RecordingFormatError(`$.clock[${i}].fromMs`, 'must increase');\n });\n\n const devices = column(root.devices, '$.devices', (x, p): RecordedDevice => {\n const o = strict(x, p, ['id', 'token', 'kind']);\n return { id: str(o.id, `${p}.id`), token: str(o.token, `${p}.token`), kind: str(o.kind, `${p}.kind`) };\n });\n const tokens = new Set(devices.map((d) => d.token));\n if (tokens.size !== devices.length || new Set(devices.map((d) => d.id)).size !== devices.length) {\n throw new RecordingFormatError('$.devices', 'duplicate device id or token');\n }\n\n let declaredMetrics: RecordedDeclarations | undefined;\n if (root.declaredMetrics !== undefined) {\n if (version === 1) throw new RecordingFormatError('$.declaredMetrics', 'version 1 carries no declarations');\n const dm = root.declaredMetrics;\n if (!isObj(dm)) throw new RecordingFormatError('$.declaredMetrics', 'expected an object');\n const kinds = new Set(devices.map((d) => d.kind));\n declaredMetrics = {};\n for (const kind of Object.keys(dm)) {\n const p = `$.declaredMetrics.${kind}`;\n if (!kinds.has(kind)) throw new RecordingFormatError(p, `no device in $.devices is of kind '${kind}'`);\n const names = column(dm[kind], p, str);\n if (names.length === 0) throw new RecordingFormatError(p, 'declares nothing; leave the kind out instead');\n if (new Set(names).size !== names.length) throw new RecordingFormatError(p, 'duplicate measurement name');\n declaredMetrics[kind] = names;\n }\n }\n\n let anchors: RecordedAnchor[] | undefined;\n if (root.anchors !== undefined) {\n if (version === 1) throw new RecordingFormatError('$.anchors', 'version 1 carries no anchors');\n const listed = column(root.anchors, '$.anchors', (x, p): RecordedAnchor => {\n const o = strict(x, p, ['relationship', 'targetType', 'targetToken', 'devices']);\n const members = column(o.devices, `${p}.devices`, str);\n if (members.length === 0) throw new RecordingFormatError(`${p}.devices`, 'has no member; leave the anchor out instead');\n if (new Set(members).size !== members.length) throw new RecordingFormatError(`${p}.devices`, 'duplicate member');\n for (const m of members) {\n if (!tokens.has(m)) throw new RecordingFormatError(`${p}.devices`, `'${m}' is not a device of the run`);\n }\n return {\n relationship: str(o.relationship, `${p}.relationship`),\n targetType: oneOf(o.targetType, `${p}.targetType`, ['customer', 'area', 'asset'] as const),\n targetToken: str(o.targetToken, `${p}.targetToken`),\n devices: members,\n };\n });\n if (listed.length === 0) throw new RecordingFormatError('$.anchors', 'declares nothing; leave it out instead');\n const seen = new Set<string>();\n listed.forEach((a, i) => {\n const key = `${a.relationship}\\u0000${a.targetType}\\u0000${a.targetToken}`;\n if (seen.has(key)) throw new RecordingFormatError(`$.anchors[${i}]`, 'the same anchor is declared twice');\n seen.add(key);\n });\n anchors = listed;\n }\n\n const ch = strict(root.channels, '$.channels', ['measurements', 'alarms', 'locations', 'commands']);\n if (version === 1 && ch.locations !== false) {\n throw new RecordingFormatError('$.channels.locations', 'version 1 records no positions; must be false');\n }\n if (ch.commands !== false) {\n throw new RecordingFormatError('$.channels.commands', 'this version records no command data; must be false');\n }\n const channels: RecordedChannels = {\n measurements: column(ch.measurements, '$.channels.measurements', str),\n alarms: bool(ch.alarms, '$.channels.alarms'),\n locations: bool(ch.locations, '$.channels.locations'),\n commands: false,\n };\n if (new Set(channels.measurements).size !== channels.measurements.length) {\n throw new RecordingFormatError('$.channels.measurements', 'duplicate measurement name');\n }\n\n const bd = strict(root.board, '$.board', ['path', 'sourceCommit', 'sha256']);\n const board = {\n path: str(bd.path, '$.board.path'),\n sourceCommit: matching(bd.sourceCommit, '$.board.sourceCommit', HEX40, 'a full lowercase hex commit id'),\n sha256: matching(bd.sha256, '$.board.sha256', HEX64, 'a lowercase hex SHA-256'),\n };\n\n const sh = root.sourceHashes;\n if (!isObj(sh)) throw new RecordingFormatError('$.sourceHashes', 'expected an object');\n const sourceHashes: Record<string, string> = {};\n for (const key of Object.keys(sh)) {\n // Assigning '__proto__' on a plain object swaps its prototype instead of adding a key.\n if (key === '__proto__') throw new RecordingFormatError(`$.sourceHashes.${key}`, 'reserved key');\n sourceHashes[key] = matching(sh[key], `$.sourceHashes.${key}`, HEX64, 'a lowercase hex SHA-256');\n }\n\n const cv = strict(root.converter, '$.converter', ['version'], version >= 2 ? ['name'] : []);\n const converter: BoardRecording['converter'] = { version: num(cv.version, '$.converter.version') };\n if (cv.name !== undefined) converter.name = str(cv.name, '$.converter.name');\n\n let excerpt: { fromMs: number; toMs: number } | undefined;\n if (root.excerpt !== undefined) {\n const e = strict(root.excerpt, '$.excerpt', ['fromMs', 'toMs']);\n excerpt = { fromMs: nonNegative(e.fromMs, '$.excerpt.fromMs'), toMs: nonNegative(e.toMs, '$.excerpt.toMs') };\n if (excerpt.fromMs >= excerpt.toMs || excerpt.toMs > durationMs) {\n throw new RecordingFormatError('$.excerpt', 'must be a non-empty window inside the run');\n }\n }\n const lo = excerpt ? excerpt.fromMs : 0;\n const hi = excerpt ? excerpt.toMs : durationMs;\n\n const chapters = column(root.chapters, '$.chapters', (x, p): RecordedChapter => {\n const o = strict(x, p, ['tMs', 'name', 'note']);\n const tMs = num(o.tMs, `${p}.tMs`);\n if (tMs < 0 || tMs > durationMs) throw new RecordingFormatError(`${p}.tMs`, 'is outside the run');\n if (excerpt && (tMs < lo || tMs > hi)) throw new RecordingFormatError(`${p}.tMs`, 'is outside the excerpt window');\n return { tMs, name: str(o.name, `${p}.name`), note: str(o.note, `${p}.note`) };\n });\n chapters.forEach((c, i) => {\n if (i > 0 && c.tMs < chapters[i - 1].tMs) throw new RecordingFormatError(`$.chapters[${i}].tMs`, 'chapters must not decrease in time');\n });\n\n const m = strict(root.measurements, '$.measurements', ['d', 'n', 't', 'v', 's']);\n // A time column (measurements and positions alike): offsets inside [lo, hi], never decreasing.\n const times = (v: unknown, path: string): number[] => {\n const col = column(v, path, num);\n let prev = lo;\n col.forEach((x, i) => {\n if (x < lo || x > hi) throw new RecordingFormatError(`${path}[${i}]`, `${x} is outside [${lo}, ${hi}]`);\n if (x < prev) throw new RecordingFormatError(`${path}[${i}]`, 'time column must not decrease');\n prev = x;\n });\n return col;\n };\n const t = times(m.t, '$.measurements.t');\n const index = (name: string, limit: number) => (x: unknown, p: string): number => {\n const n = num(x, p);\n if (!Number.isInteger(n) || n < 0 || n >= limit) throw new RecordingFormatError(p, `${name} index out of range`);\n return n;\n };\n const measurements: RecordedMeasurementColumns = {\n d: column(m.d, '$.measurements.d', index('device', devices.length), t.length),\n n: column(m.n, '$.measurements.n', index('measurement', channels.measurements.length), t.length),\n t,\n v: column(m.v, '$.measurements.v', num, t.length),\n s: column(\n m.s,\n '$.measurements.s',\n (x, p): 0 | 1 => {\n if (x !== 0 && x !== 1) throw new RecordingFormatError(p, 'expected 0 or 1');\n return x;\n },\n t.length,\n ),\n };\n\n const al = strict(root.alarms, '$.alarms', ['snapshots', 'events']);\n const alarm = (o: Obj, p: string): RecordedAlarm => {\n const id = matching(o.id, `${p}.id`, /^alarm-\\d+$/, 'a re-minted id (alarm-N)');\n const dev = str(o.dev, `${p}.dev`);\n if (!tokens.has(dev)) throw new RecordingFormatError(`${p}.dev`, `device '${dev}' is not in $.devices`);\n if (o.ack !== undefined && o.ack !== true) throw new RecordingFormatError(`${p}.ack`, 'present only when true');\n if (o.val !== undefined && (typeof o.val !== 'number' || !Number.isFinite(o.val))) {\n throw new RecordingFormatError(`${p}.val`, 'expected a finite number');\n }\n const out: RecordedAlarm = {\n id,\n dev,\n key: str(o.key, `${p}.key`),\n metric: str(o.metric, `${p}.metric`),\n state: oneOf(o.state, `${p}.state`, ALARM_STATES),\n sev: oneOf(o.sev, `${p}.sev`, ALARM_SEVERITIES),\n occ: instant(o.occ, `${p}.occ`),\n };\n if (o.ack === true) out.ack = true;\n if (typeof o.val === 'number') out.val = o.val;\n return out;\n };\n const ALARM_KEYS = ['id', 'dev', 'key', 'metric', 'state', 'sev', 'occ'];\n // `val` is a version 2 field: a version 1 document that carries it is refused, not read.\n const ALARM_OPTIONAL = version >= 2 ? ['ack', 'val'] : ['ack'];\n const checkTime = (tMs: number, p: string): number => {\n if (tMs < 0 || tMs > hi) throw new RecordingFormatError(p, `${tMs} is outside [0, ${hi}]`);\n return tMs;\n };\n const snapshots = column(al.snapshots, '$.alarms.snapshots', (x, p): RecordedAlarmSnapshot => {\n const o = strict(x, p, ['tMs', 'requestedAtMs', 'total', 'alarms']);\n const alarms = column(o.alarms, `${p}.alarms`, (a, ap) => alarm(strict(a, ap, ALARM_KEYS, ALARM_OPTIONAL), ap));\n const ids = new Set<string>();\n alarms.forEach((a, i) => {\n if (ids.has(a.id)) throw new RecordingFormatError(`${p}.alarms[${i}].id`, `duplicate alarm ${a.id} in one snapshot`);\n ids.add(a.id);\n });\n // A snapshot that lists fewer alarms than its total is a truncated answer. Folding it\n // as the whole set would erase every alarm it left out.\n const total = nonNegative(o.total, `${p}.total`);\n if (total !== alarms.length) {\n throw new RecordingFormatError(`${p}.total`, `is ${total} but ${alarms.length} alarms are listed (truncated snapshot)`);\n }\n const tMs = checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`);\n const requestedAtMs = nonNegative(o.requestedAtMs, `${p}.requestedAtMs`);\n if (!Number.isInteger(requestedAtMs)) throw new RecordingFormatError(`${p}.requestedAtMs`, 'expected an integer');\n if (requestedAtMs > tMs) throw new RecordingFormatError(`${p}.requestedAtMs`, 'a query cannot be sent after its answer');\n return { tMs, requestedAtMs, total, alarms };\n });\n const events = column(al.events, '$.alarms.events', (x, p): RecordedAlarmEvent => {\n const o = strict(x, p, [...ALARM_KEYS, 'tMs'], ALARM_OPTIONAL);\n return { ...alarm(o, p), tMs: checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`) };\n });\n for (const [name, list] of [['snapshots', snapshots], ['events', events]] as const) {\n list.forEach((e, i) => {\n if (i > 0 && e.tMs < list[i - 1].tMs) throw new RecordingFormatError(`$.alarms.${name}[${i}].tMs`, 'must not decrease');\n });\n }\n // An alarm id names one alarm on one device for the whole recording.\n const deviceOf = new Map<string, string>();\n const everyAlarm: Array<[string, RecordedAlarm]> = [\n ...snapshots.flatMap((s, i) => s.alarms.map((a, j): [string, RecordedAlarm] => [`$.alarms.snapshots[${i}].alarms[${j}]`, a])),\n ...events.map((e, i): [string, RecordedAlarm] => [`$.alarms.events[${i}]`, e]),\n ];\n for (const [path, a] of everyAlarm) {\n const seen = deviceOf.get(a.id);\n if (seen !== undefined && seen !== a.dev) {\n throw new RecordingFormatError(`${path}.dev`, `alarm ${a.id} was on '${seen}' and cannot move to '${a.dev}'`);\n }\n deviceOf.set(a.id, a.dev);\n }\n if (!channels.alarms && (snapshots.length > 0 || events.length > 0)) {\n throw new RecordingFormatError('$.alarms', 'history present but $.channels.alarms is false');\n }\n\n let locations: RecordedLocationColumns;\n if (version === 1) {\n if (!Array.isArray(root.locations) || root.locations.length !== 0) {\n throw new RecordingFormatError('$.locations', 'version 1 carries no positions; must be an empty array');\n }\n locations = { d: [], t: [], lat: [], lon: [], hdg: [], spd: [] };\n } else {\n const lc = strict(root.locations, '$.locations', ['d', 't', 'lat', 'lon', 'hdg', 'spd']);\n const lt = times(lc.t, '$.locations.t');\n const within = (min: number, max: number, what: string) => (x: unknown, p: string): number => {\n const n = num(x, p);\n if (n < min || n > max) throw new RecordingFormatError(p, `${what} ${n} is outside [${min}, ${max}]`);\n return n;\n };\n const optional = (min: number, max: number, what: string) => (x: unknown, p: string): number | null =>\n x === null ? null : within(min, max, what)(x, p);\n locations = {\n d: column(lc.d, '$.locations.d', index('device', devices.length), lt.length),\n t: lt,\n lat: column(lc.lat, '$.locations.lat', within(-90, 90, 'latitude'), lt.length),\n lon: column(lc.lon, '$.locations.lon', within(-180, 180, 'longitude'), lt.length),\n hdg: column(lc.hdg, '$.locations.hdg', optional(0, 360, 'heading'), lt.length),\n spd: column(lc.spd, '$.locations.spd', optional(0, Number.MAX_VALUE, 'speed'), lt.length),\n };\n if (!channels.locations && lt.length > 0) {\n throw new RecordingFormatError('$.locations', 'positions present but $.channels.locations is false');\n }\n }\n\n const rec: BoardRecording = {\n kind: RECORDING_KIND,\n formatVersion: version,\n runId: str(root.runId, '$.runId'),\n startedAtUtc: instant(root.startedAtUtc, '$.startedAtUtc'),\n durationMs,\n platformVersion: str(root.platformVersion, '$.platformVersion'),\n instance: str(root.instance, '$.instance'),\n tenant: str(root.tenant, '$.tenant'),\n build,\n clock,\n devices,\n channels,\n board,\n sourceHashes,\n converter,\n chapters,\n measurements,\n alarms: { snapshots, events },\n locations,\n };\n if (excerpt) rec.excerpt = excerpt;\n if (declaredMetrics) rec.declaredMetrics = declaredMetrics;\n if (anchors) rec.anchors = anchors;\n return rec;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// RecordedDataSource \u2014 plays one recorded run (recording.ts) through the SAME\n// WidgetDataSource contract the live DashboardHub and the SyntheticDataSource implement,\n// so the dashboard renderer and every widget consume it unchanged.\n//\n// It is deliberately NOT a mode of SyntheticDataSource. Synthetic data is canned and\n// labelled synthetic; this is observed data, and folding the two together would blur the\n// one boundary a viewer relies on. It also implements WidgetDataSource ONLY, not\n// WidgetActions: a recording cannot acknowledge, clear or send anything, so no write\n// control may render (the alarm table shows its action buttons only when an actions seam\n// exists).\n//\n// \u2500\u2500 Time \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A RecordedClock owns the cursor (play / pause / seek / rate). A source VIEW follows the\n// clock forward: as the cursor passes a row's `t` the row is delivered to the widgets\n// that asked for it.\n//\n// \uD83D\uDD34 A SEEK ENDS A VIEW; IT DOES NOT REWIND ONE. Widget streams only append (a chart\n// window, a \"latest\" card keep what they were given), so a backwards jump cannot be\n// retracted through a sink. The host therefore builds a NEW RecordedDataSource over the\n// same parsed recording after every seek: the renderer sees a new data source, every\n// widget re-subscribes and resets its buffer, and the new view back-fills the preceding\n// window at the new cursor. The old view goes quiet on the seek event (it never emits\n// another row, in either direction) and `isStale` reports it, so a host that forgets to\n// swap it freezes visibly in a test instead of replaying the wrong moment.\n//\n// \u2500\u2500 Re-timing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A source may be built with a TIME SHIFT (RecordedSourceOptions.timeShiftMs, usually\n// retimeShiftMs: \"the cursor is now\"). Every instant the source hands out is then moved by\n// that one amount: sample and position times, alarm raise and clear times, now(), and the\n// windows a stat asks buckets for are moved back by it before the rows are read. One\n// shift for everything, so ages, alarm times, aggregates and positions agree with each\n// other exactly as they did in the run; only the date they claim is different. A shift is\n// a presentation choice of the HOST (the showcase embed), which must say on screen that it\n// is a replay.\n//\n// \u2500\u2500 What the recording does not hold \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A channel the recording did not capture raises NotInRecordingError through the sink's\n// error path. It NEVER answers with an empty snapshot: an empty map would read as \"no\n// machine has a position\" when the truth is \"this recording has no positions\". The widget\n// frame already renders a sink error as \"Data unavailable\" plus the error text, so the\n// message below is what a viewer reads.\n//\n// No network: this module touches no fetch, socket or timer of its own beyond the clock's\n// optional frame ticker, and the tests trap any attempt.\n\nimport type {\n AlarmSnapshot,\n AlarmStreamSink,\n AlarmSubscription,\n CommandStreamSink,\n CommandSubscription,\n DeviceResolver,\n LocationStreamSink,\n LocationSubscription,\n WidgetDataSource,\n WidgetStreamSink,\n} from './hub';\nimport type {\n AlarmRow,\n AnchorTarget,\n DatasourceSelector,\n EntityCandidateLister,\n EntityListKind,\n MeasurementSample,\n} from './types';\nimport { alarmOverlaps } from './event-window';\nimport type { HistoryWindow } from './history';\nimport type { MeasurementBucket } from './queries';\nimport {\n compareInstants,\n recordedWallTimeMs,\n recordingBounds,\n type BoardRecording,\n type RecordedAlarm,\n type RecordedAlarmEvent,\n type RecordedAlarmSnapshot,\n} from './recording';\nimport type { MemberSeed, MemberSeedPlan, MemberSink } from './members';\n\n// How far back from the cursor a fresh subscription back-fills measurement rows. Raw rows,\n// not buckets: a recording has no aggregate to show honestly.\nexport const RECORDED_HISTORY_WINDOW_MS = 60 * 60 * 1000;\n\n// ---- the typed \"not in this recording\" error ------------------------------\n\nexport type RecordedChannel = 'measurements' | 'alarms' | 'locations' | 'commands';\n\nexport class NotInRecordingError extends Error {\n readonly channel: RecordedChannel;\n readonly detail: string;\n\n constructor(channel: RecordedChannel, detail = '') {\n super(`Not in this recording${detail ? `: ${detail}` : ''}`);\n this.name = 'NotInRecordingError';\n this.channel = channel;\n this.detail = detail;\n }\n\n // The widget error frame prints String(error). The default would read\n // \"NotInRecordingError: Not in this recording\", which says the name twice; the message\n // alone is what a viewer should see.\n toString(): string {\n return this.message;\n }\n}\n\n// ---- the replay clock -----------------------------------------------------\n\nexport const PLAYBACK_RATES = [1, 4, 16] as const;\nexport type PlaybackRate = (typeof PLAYBACK_RATES)[number];\n\n// 'tick' = the cursor advanced during playback; 'seek' = the cursor was moved (any\n// direction); 'state' = play / pause / rate changed with the cursor where it was.\nexport interface ClockEvent {\n timeMs: number;\n reason: 'tick' | 'seek' | 'state';\n}\n\n// A frame source for the clock. The default drives requestAnimationFrame (or a timer where\n// there is none); a test injects its own, or drives the clock by hand with advance().\nexport interface ClockTicker {\n // Start calling onFrame(wallDeltaMs) until the returned stopper is called.\n start(onFrame: (wallDeltaMs: number) => void): () => void;\n}\n\nfunction defaultTicker(): ClockTicker {\n return {\n start(onFrame) {\n const now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n let last = now();\n const step = (): void => {\n const t = now();\n const dt = t - last;\n last = t;\n onFrame(dt);\n };\n if (typeof requestAnimationFrame === 'function') {\n let handle = 0;\n let stopped = false;\n const loop = (): void => {\n if (stopped) return;\n step();\n handle = requestAnimationFrame(loop);\n };\n handle = requestAnimationFrame(loop);\n return () => {\n stopped = true;\n if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);\n };\n }\n const timer = setInterval(step, 100);\n return () => clearInterval(timer);\n },\n };\n}\n\nexport class RecordedClock {\n // The end of the playable span, and its beginning (0 for a whole run; an excerpt starts\n // at its window).\n readonly durationMs: number;\n readonly minMs: number;\n private time: number;\n private isPlaying = false;\n private playbackRate: PlaybackRate = 1;\n private stopTicker: (() => void) | null = null;\n private readonly ticker: ClockTicker;\n private readonly listeners = new Set<(ev: ClockEvent) => void>();\n\n constructor(durationMs: number, options: { ticker?: ClockTicker; startMs?: number; minMs?: number } = {}) {\n if (!Number.isFinite(durationMs) || durationMs <= 0) {\n throw new RangeError(`RecordedClock: durationMs must be positive and finite, got ${durationMs}`);\n }\n this.durationMs = durationMs;\n this.minMs = options.minMs ?? 0;\n if (!Number.isFinite(this.minMs) || this.minMs < 0 || this.minMs >= durationMs) {\n throw new RangeError(`RecordedClock: minMs must be in [0, ${durationMs}), got ${this.minMs}`);\n }\n this.ticker = options.ticker ?? defaultTicker();\n this.time = this.clampTime(options.startMs ?? this.minMs);\n }\n\n get timeMs(): number {\n return this.time;\n }\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get rate(): PlaybackRate {\n return this.playbackRate;\n }\n\n get ended(): boolean {\n return this.time >= this.durationMs;\n }\n\n // play starts the cursor moving. At the end of the run it does nothing: playback stops\n // there (no loop seam) and a host that wants to start over seeks to 0 first.\n play(): void {\n if (this.isPlaying || this.ended) return;\n this.isPlaying = true;\n this.stopTicker = this.ticker.start((dt) => this.advance(dt));\n this.emit('state');\n }\n\n pause(): void {\n if (!this.isPlaying) return;\n this.halt();\n this.emit('state');\n }\n\n // seek moves the cursor, clamped to [minMs, durationMs]. Playback state is unchanged, except\n // that seeking to the very end stops it (there is nothing left to play).\n seek(tMs: number): void {\n if (!Number.isFinite(tMs)) throw new RangeError(`RecordedClock.seek: not a finite time: ${tMs}`);\n this.time = this.clampTime(tMs);\n if (this.isPlaying && this.ended) this.halt();\n this.emit('seek');\n }\n\n setRate(rate: PlaybackRate): void {\n if (!(PLAYBACK_RATES as readonly number[]).includes(rate)) {\n throw new RangeError(`RecordedClock.setRate: ${rate} is not one of ${PLAYBACK_RATES.join(', ')}`);\n }\n this.playbackRate = rate;\n this.emit('state');\n }\n\n // advance moves the cursor by wallDeltaMs of real time (scaled by the playback rate).\n // The ticker calls this each frame; a test calls it directly. It does nothing while\n // paused, and stops at the end of the run.\n advance(wallDeltaMs: number): void {\n if (!this.isPlaying) return;\n if (!Number.isFinite(wallDeltaMs) || wallDeltaMs < 0) {\n throw new RangeError(`RecordedClock.advance: expected a non-negative finite delta, got ${wallDeltaMs}`);\n }\n this.time = this.clampTime(this.time + wallDeltaMs * this.playbackRate);\n if (this.ended) this.halt();\n this.emit('tick');\n }\n\n subscribe(listener: (ev: ClockEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private halt(): void {\n this.isPlaying = false;\n this.stopTicker?.();\n this.stopTicker = null;\n }\n\n private clampTime(t: number): number {\n return t < this.minMs ? this.minMs : t > this.durationMs ? this.durationMs : t;\n }\n\n private emit(reason: ClockEvent['reason']): void {\n for (const listener of [...this.listeners]) listener({ timeMs: this.time, reason });\n }\n}\n\n// ---- helpers --------------------------------------------------------------\n\n// First index whose value is >= x (the array is non-decreasing).\nfunction lowerBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] < x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// First index whose value is > x.\nfunction upperBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] <= x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// createRecordedClock builds a clock covering exactly what the recording can play: the\n// whole run, or an excerpt's window (starting at its beginning).\nexport function createRecordedClock(rec: BoardRecording, options: { ticker?: ClockTicker } = {}): RecordedClock {\n const { fromMs, toMs } = recordingBounds(rec);\n return new RecordedClock(toMs, { minMs: fromMs, ticker: options.ticker });\n}\n\n// ---- anchors, resolver and lister -----------------------------------------\n\n// The recording does not carry the anchor a board is bound to; it carries the devices. The\n// host names the anchors (taken from the board definition it plays) whose members are the\n// run's devices. Every device of a Sitepulse run is a member of the one site CUSTOMER, so\n// that anchor expands to ALL of the run's devices. Exactly one anchor may be declared, and\n// it must be a customer: an area or asset anchor would claim every device is in it, which\n// the recording cannot know. An anchor that was not declared is unknown and is refused,\n// never answered with an empty set. Declaring nothing declares no anchor at all.\nexport interface RecordedSiteOptions {\n siteAnchors?: readonly AnchorTarget[];\n}\n\n// A source's options: the site, and an optional time shift (see \"Re-timing\" above).\nexport interface RecordedSourceOptions extends RecordedSiteOptions {\n // Milliseconds added to every instant the source hands out. 0 (the default) shows the\n // run's own times.\n timeShiftMs?: number;\n}\n\n// retimeShiftMs is the shift that makes recording offset `cursorMs` read as `nowMs` (the\n// wall clock by default): the source built with it says the cursor is now.\nexport function retimeShiftMs(rec: BoardRecording, cursorMs: number, nowMs: number = Date.now()): number {\n return Math.round(nowMs - recordedWallTimeMs(rec, cursorMs));\n}\n\nfunction sameAnchor(a: AnchorTarget, b: AnchorTarget): boolean {\n return a.relationship === b.relationship && a.targetType === b.targetType && a.targetToken === b.targetToken;\n}\n\n// The one declared site anchor, or null when none was declared. Anything else is refused\n// when the source, resolver or lister is built, not at some later lookup.\nfunction declaredSite(options: RecordedSiteOptions, rec?: BoardRecording): AnchorTarget | null {\n // The recording names its own anchors, so the host's declaration is not what decides membership.\n if (rec?.anchors) return null;\n const anchors = options.siteAnchors;\n if (anchors === undefined) return null;\n if (anchors.length !== 1) {\n throw new Error(`RecordedSiteOptions.siteAnchors must declare exactly one anchor, got ${anchors.length}`);\n }\n if (anchors[0].targetType !== 'customer') {\n throw new Error(\n `RecordedSiteOptions.siteAnchors must be a customer anchor, got '${anchors[0].targetType}': ` +\n 'a recording cannot say that every device belongs to an area or an asset',\n );\n }\n return anchors[0];\n}\n\n// A recording that carries its own anchors answers from them: an anchor it does not list is unknown, never an empty set. One that does\n// not falls back to the single declared customer anchor, which holds every device.\nfunction recordedMembers(rec: BoardRecording, anchor: AnchorTarget): string[] | null {\n if (!rec.anchors) return null;\n const hit = rec.anchors.find((a) => sameAnchor(a, anchor));\n if (!hit) {\n throw new Error(`anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`);\n }\n return hit.devices;\n}\n\nfunction anchorMembers(rec: BoardRecording, site: AnchorTarget | null, anchor: AnchorTarget): string[] {\n const own = recordedMembers(rec, anchor);\n if (own) return own;\n if (!site || !sameAnchor(site, anchor)) {\n throw new Error(\n `anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`,\n );\n }\n return rec.devices.map((d) => d.token);\n}\n\n// createRecordedResolver answers the hub's DeviceResolver questions from the recording: a\n// site anchor expands to the run's devices, and a device exists if the run has it. An\n// anchor the host did not declare THROWS (an empty answer would read as \"the site had no\n// machines\").\nexport function createRecordedResolver(rec: BoardRecording, options: RecordedSiteOptions = {}): DeviceResolver {\n const devices = new Set(rec.devices.map((d) => d.token));\n const site = declaredSite(options, rec);\n return {\n devicesForAnchor: async (anchor) => anchorMembers(rec, site, anchor),\n deviceExists: async (token) => devices.has(token),\n };\n}\n\n// createRecordedLister backs a root entity-selector's candidate list from the recording.\n//\n// \uD83D\uDD34 WITHOUT A LISTER THE ENTITY SELECTOR SAYS \"Selection is available on the live\n// dashboard.\" A replay must never say that. The lister is what turns the widget's\n// selection on, so a host that mounts a recording passes this one.\n//\n// Devices are listed by token (the recording carries no display names). The other kinds\n// list the declared site anchors of that type; a kind with none yields an empty list,\n// which is true of the recording.\nexport function createRecordedLister(rec: BoardRecording, options: RecordedSiteOptions = {}): EntityCandidateLister {\n const site = declaredSite(options, rec);\n return async (kind: EntityListKind) => {\n if (kind === 'device') return rec.devices.map((d) => ({ token: d.token, name: null }));\n if (rec.anchors) return rec.anchors.filter((a) => a.targetType === kind).map((a) => ({ token: a.targetToken, name: null }));\n return site && site.targetType === kind ? [{ token: site.targetToken, name: null }] : [];\n };\n}\n\n// ---- the recording, indexed -----------------------------------------------\n\ninterface Series {\n device: string;\n name: string;\n // Row indices into the measurement columns, ascending in time, and the same rows' times.\n rows: number[];\n times: number[];\n}\n\n// One alarm record on the merged history timeline. A snapshot REPLACES the whole alarm set;\n// an event sets one alarm. At equal times a snapshot comes first.\ntype TimelineEntry =\n | { tMs: number; snapshot: RecordedAlarmSnapshot }\n | { tMs: number; event: RecordedAlarmEvent };\n\ninterface Indexed {\n byDevice: Map<string, Series[]>;\n timeline: TimelineEntry[];\n timelineTimes: number[];\n}\n\n// The parts only some boards use (positions, aggregates), fetched on first use: one import\n// site, so the bundle carries one loader for them.\ntype RecordedExtra = typeof import('./recorded-extra');\nconst loadExtra = (): Promise<RecordedExtra> => import('./recorded-extra');\n\n// A view is rebuilt after every seek, so the index is built once per parsed recording.\nconst indexCache = new WeakMap<BoardRecording, Indexed>();\n\nfunction indexOf(rec: BoardRecording): Indexed {\n const cached = indexCache.get(rec);\n if (cached) return cached;\n\n const bySeries = new Map<string, Series>();\n const byDevice = new Map<string, Series[]>();\n const { d, n, t } = rec.measurements;\n for (let i = 0; i < t.length; i++) {\n const key = d[i] * rec.channels.measurements.length + n[i];\n let s = bySeries.get(String(key));\n if (!s) {\n s = { device: rec.devices[d[i]].token, name: rec.channels.measurements[n[i]], rows: [], times: [] };\n bySeries.set(String(key), s);\n const list = byDevice.get(s.device);\n if (list) list.push(s);\n else byDevice.set(s.device, [s]);\n }\n s.rows.push(i);\n s.times.push(t[i]);\n }\n\n const timeline: TimelineEntry[] = [\n ...rec.alarms.snapshots.map((snapshot): TimelineEntry => ({ tMs: snapshot.tMs, snapshot })),\n ...rec.alarms.events.map((event): TimelineEntry => ({ tMs: event.tMs, event })),\n ];\n // Array.prototype.sort is stable: snapshots were listed first, so they stay first at ties.\n timeline.sort((a, b) => a.tMs - b.tMs);\n\n const indexed: Indexed = { byDevice, timeline, timelineTimes: timeline.map((e) => e.tMs) };\n indexCache.set(rec, indexed);\n return indexed;\n}\n\n// An alarm as the fold holds it.\ninterface FoldedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: string;\n sev: string;\n raised: string | null;\n cleared: string | null;\n ack: boolean;\n // The reading that raised it, when the recording holds it.\n val: number | null;\n // The recording time this state is known as of: the event's time, or for an alarm taken\n // from a snapshot, when that snapshot's query was sent.\n asOfMs: number;\n}\n\n// ---- the data source ------------------------------------------------------\n\ninterface Subscription {\n // Called with the new cursor whenever the clock ticks forward.\n advance(cursor: number): void;\n dispose(): void;\n}\n\ninterface DeviceNames {\n deviceToken: string;\n names: Set<string>;\n}\n\nexport class RecordedDataSource implements WidgetDataSource {\n private readonly rec: BoardRecording;\n private readonly site: AnchorTarget | null;\n private readonly index: Indexed;\n private readonly startMs: number;\n private readonly devices: ReadonlySet<string>;\n private readonly kinds: ReadonlyMap<string, string>;\n private readonly subs = new Set<Subscription>();\n private readonly unlisten: () => void;\n private cursor: number;\n private stale = false;\n private readonly shiftMs: number;\n\n // `clock` is the shared replay clock; the view starts at its current time. `options`\n // names the site anchors this recording's devices belong to (see RecordedSiteOptions)\n // and the time shift, if any (see \"Re-timing\" above).\n constructor(rec: BoardRecording, clock: RecordedClock, options: RecordedSourceOptions = {}) {\n this.rec = rec;\n this.site = declaredSite(options, rec);\n this.index = indexOf(rec);\n this.cursor = clock.timeMs;\n const shift = options.timeShiftMs ?? 0;\n if (!Number.isFinite(shift)) throw new RangeError(`RecordedDataSource: timeShiftMs must be finite, got ${shift}`);\n this.shiftMs = shift;\n this.startMs = Date.parse(rec.startedAtUtc);\n this.devices = new Set(rec.devices.map((d) => d.token));\n this.kinds = new Map(rec.devices.map((d) => [d.token, d.kind]));\n this.unlisten = clock.subscribe((ev) => this.onClock(ev));\n }\n\n // True once the clock has been seeked since this view was built. A stale view emits\n // nothing further; the host builds a new view at the new cursor.\n get isStale(): boolean {\n return this.stale;\n }\n\n // The instant the view is at: samples are dated by the run (moved by the shift, if any), so a\n // time span is measured from here, not from the wall clock.\n now(): number {\n return this.startMs + this.cursor + this.shiftMs;\n }\n\n // The shift every instant is moved by (0 for the run's own times).\n get timeShiftMs(): number {\n return this.shiftMs;\n }\n\n private onClock(ev: ClockEvent): void {\n if (this.stale) return;\n if (ev.reason === 'seek') {\n this.stale = true;\n this.unlisten();\n return;\n }\n if (ev.reason !== 'tick') return;\n this.cursor = ev.timeMs;\n for (const sub of [...this.subs]) sub.advance(this.cursor);\n }\n\n private iso(offsetMs: number): string {\n return new Date(this.startMs + offsetMs + this.shiftMs).toISOString();\n }\n\n // A recorded instant (an alarm's raise or clear time), moved by the shift. Unshifted, the\n // recording's own string is kept, digits and all.\n private shifted(instant: string | null): string | null {\n if (instant === null || this.shiftMs === 0) return instant;\n return new Date(Date.parse(instant) + this.shiftMs).toISOString();\n }\n\n // open tracks a subscription and returns its disposer. `start` runs once, in a\n // microtask, so the disposer is in the caller's hands before anything is emitted (the\n // contract every source keeps: a synchronous disposer, and nothing emitted after it).\n // `advance` runs on each forward tick after that.\n private open(handlers: {\n start(cursor: number, isDisposed: () => boolean): void;\n advance(cursor: number, isDisposed: () => boolean): void;\n }): () => void {\n let disposed = false;\n let started = false;\n const isDisposed = (): boolean => disposed;\n const sub: Subscription = {\n advance: (cursor) => {\n if (started && !disposed) handlers.advance(cursor, isDisposed);\n },\n dispose: () => {\n disposed = true;\n this.subs.delete(sub);\n },\n };\n this.subs.add(sub);\n queueMicrotask(() => {\n if (disposed || this.stale) return;\n started = true;\n handlers.start(this.cursor, isDisposed);\n });\n return sub.dispose;\n }\n\n // Resolve a selector to the devices it names, synchronously and from the recording.\n // Mirrors the hub: unbound is zero devices, an unresolved slot is a loud error.\n private resolve(ds: DatasourceSelector): DeviceNames[] {\n switch (ds.kind) {\n case 'device':\n return [{ deviceToken: ds.deviceToken, names: new Set(ds.measurements) }];\n case 'anchor':\n return anchorMembers(this.rec, this.site, ds.anchor).map((deviceToken) => ({\n deviceToken,\n names: new Set(ds.measurements),\n }));\n case 'unbound':\n return [];\n case 'slot':\n throw new Error(\n `dashboard slot '${ds.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(`dashboard selector kind '${ds.kind}' is not supported yet`);\n }\n }\n\n // Report a failure on a sink, asynchronously and only if the subscription is still live.\n private failLater(err: unknown, sink: { error?: (e: unknown) => void }): () => void {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) sink.error?.(err);\n });\n return () => {\n disposed = true;\n };\n }\n\n // \u2500\u2500 measurements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n let groups: DeviceNames[];\n try {\n groups = this.resolve(datasource);\n for (const g of groups) {\n for (const name of g.names) {\n if (!this.rec.channels.measurements.includes(name)) {\n throw new NotInRecordingError('measurements', `no '${name}' series was recorded`);\n }\n }\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // Each selected series keeps its own next-row position, so a tick delivers exactly the\n // rows the cursor has newly passed.\n const picked: Array<{ s: Series; next: number }> = [];\n for (const g of groups) {\n for (const s of this.index.byDevice.get(g.deviceToken) ?? []) {\n if (g.names.size === 0 || g.names.has(s.name)) picked.push({ s, next: 0 });\n }\n }\n\n const { t, v } = this.rec.measurements;\n const flush = (cursor: number, isDisposed: () => boolean): void => {\n const due: Array<{ t: number; sample: MeasurementSample }> = [];\n for (const p of picked) {\n const { s } = p;\n while (p.next < s.times.length && s.times[p.next] <= cursor) {\n const row = s.rows[p.next++];\n due.push({\n t: t[row],\n sample: {\n id: `${s.device}|${s.name}|${row}`,\n deviceToken: s.device,\n eventType: 0,\n // The time the viewer applied the row: the recording holds no separate\n // occurred time for measurements.\n occurredTime: this.iso(t[row]),\n name: s.name,\n value: v[row],\n classifier: null,\n },\n });\n }\n }\n // Chronological across series (stable for equal times), so a widget's window and a\n // \"latest\" card agree on what arrived last.\n due.sort((a, b) => a.t - b.t);\n for (const d of due) {\n if (isDisposed()) return;\n sink.next(d.sample);\n }\n };\n\n return this.open({\n // Back-fill: rows from the preceding window up to the cursor, then play on.\n start: (cursor, isDisposed) => {\n for (const p of picked) p.next = lowerBound(p.s.times, cursor - RECORDED_HISTORY_WINDOW_MS);\n flush(cursor, isDisposed);\n },\n advance: flush,\n });\n }\n\n // \u2500\u2500 fleets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A fleet widget's roster (the run's devices) and then the same back-filled, ticking rows\n // subscribeWidget delivers, each carrying its device.\n //\n // \uD83D\uDD34 A member has NO NAME here, so the widget labels it by its token, exactly as the live board\n // does. A recorded device's `id` is its external id (the scene's binding key, \"SP-HL-0003\"),\n // not a device name: the live roster reads device NAMES, which the sim never sets, and every\n // other surface of the board (a gauge's subject line, the machine selector, an alarm's\n // originator) names a device by its token. Labelling the table's rows by external id made one\n // machine \"SP-HL-0003\" in the table and \"sp-hauler-03\" in the alarm beside it.\n //\n // A member's `deviceType` is its recorded KIND (\"Hauler\"), the key declaredMetrics answers by:\n // a recording names no device-type tokens, and nothing but the declarations reads it.\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n let members: Array<{ token: string; name: string | null; deviceType: string }>;\n try {\n members = this.resolve(datasource).map((g) => ({ token: g.deviceToken, name: null, deviceType: this.kinds.get(g.deviceToken) as string }));\n } catch (err) {\n return this.failLater(err, sink);\n }\n let stop: (() => void) | undefined;\n const off = this.open({\n start: () => {\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n stop = this.subscribeWidget(datasource, sink);\n },\n advance: () => {},\n });\n return () => {\n off();\n stop?.();\n };\n }\n\n // What a kind's profile declares, as the recording carries it (recording.ts, `declaredMetrics`),\n // or null when it does not say: a recording made before it carried declarations, or a kind it\n // has no entry for. Null is \"unknown\", which the fleet widgets read as declared -- never as\n // absent, so an old recording shows \"no data in range\" rather than a confident \"does not report\".\n async declaredMetrics(kind: string): Promise<readonly string[] | null> {\n const declared = this.rec.declaredMetrics;\n return declared && Object.prototype.hasOwnProperty.call(declared, kind) ? declared[kind] : null;\n }\n\n // The same for one device, through its recorded kind.\n async declaredMetricsForDevice(deviceToken: string): Promise<readonly string[] | null> {\n const kind = this.kinds.get(deviceToken);\n return kind === undefined ? null : this.declaredMetrics(kind);\n }\n\n // Each device's seed comes from the recording itself, with no read to make: its newest reading per\n // measurement inside the history window, and every measurement it reported in that window, which\n // is how \"does not report this\" (a dozer's tyres) is told from \"sent nothing\".\n async readMemberSeeds(\n deviceTokens: readonly string[],\n _plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null) => void,\n ): Promise<void> {\n const { t, v } = this.rec.measurements;\n for (const token of deviceTokens) {\n const seed: MemberSeed = { latest: {}, observed: [] };\n for (const s of this.index.byDevice.get(token) ?? []) {\n const at = upperBound(s.times, this.cursor) - 1;\n if (at < 0 || s.times[at] < this.cursor - RECORDED_HISTORY_WINDOW_MS) continue;\n const row = s.rows[at];\n seed.observed.push(s.name);\n seed.latest[s.name] = {\n id: `${token}|${s.name}|${row}`,\n deviceToken: token,\n eventType: 0,\n occurredTime: this.iso(t[row]),\n name: s.name,\n value: v[row],\n classifier: null,\n };\n }\n onSeed(token, seed);\n }\n }\n\n // \u2500\u2500 alarms \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n let scope: Set<string> | null = null; // null = tenant-wide (no datasource)\n try {\n if (!this.rec.channels.alarms) throw new NotInRecordingError('alarms');\n // A recording carries no device types, so it can neither filter nor count by one. Refused\n // rather than answered with every alarm, or with no type at all, either of which would read\n // as a real answer. (Resolving rows' types, enrichDeviceType, leaves them unresolved: an\n // unknown type is a state a row already has.)\n if (subscription.deviceType !== undefined || subscription.countByDeviceType) {\n throw new NotInRecordingError('alarms', 'device types');\n }\n if (subscription.datasource) {\n scope = new Set(this.resolve(subscription.datasource).map((g) => g.deviceToken));\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // The fold is monotone because a view only moves forward: apply each history entry\n // once, in order. A snapshot replaces the set; an event sets one alarm.\n const { timeline, timelineTimes } = this.index;\n const alarms = new Map<string, FoldedAlarm>();\n let applied = 0;\n let lastSignature: string | null = null;\n\n const apply = (a: RecordedAlarm, asOfMs: number): void => {\n const prior = alarms.get(a.id);\n alarms.set(a.id, {\n id: a.id,\n dev: a.dev,\n key: a.key,\n metric: a.metric,\n state: a.state,\n sev: a.sev,\n // `occ` is the time of THIS state: the raise time for an ACTIVE row, the clear time\n // for a CLEARED one. A cleared alarm keeps the raise time its ACTIVE row gave it\n // (null if the history never showed it being raised).\n raised: a.state === 'ACTIVE' ? a.occ : (prior?.raised ?? null),\n cleared: a.state === 'CLEARED' ? a.occ : null,\n // Each record's own `ack` decides; acknowledgement is not carried over from an\n // earlier record of the same alarm.\n ack: a.ack === true,\n // The reading that raised the alarm. A cleared alarm keeps its raise's reading, as the\n // platform's row does (a resolve carries no value).\n val: a.val ?? (a.state === 'CLEARED' ? (prior?.val ?? null) : null),\n asOfMs,\n });\n };\n\n const emit = (cursor: number, isDisposed: () => boolean): void => {\n const upTo = upperBound(timelineTimes, cursor);\n for (; applied < upTo; applied++) {\n const entry = timeline[applied];\n if ('snapshot' in entry) {\n // A snapshot is the answer to a query sent at requestedAtMs, so it knows nothing\n // of an alarm that changed after that: those keep the event's (newer) state, even\n // when the snapshot omits or contradicts them. Everything else is replaced.\n const { requestedAtMs } = entry.snapshot;\n const newer = [...alarms.values()].filter((a) => a.asOfMs > requestedAtMs);\n alarms.clear();\n for (const a of entry.snapshot.alarms) apply(a, requestedAtMs);\n for (const a of newer) alarms.set(a.id, a);\n } else {\n apply(entry.event, entry.event.tMs);\n }\n }\n const matches = [...alarms.values()].filter(\n (a) =>\n (scope === null || scope.has(a.dev)) &&\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.sev === subscription.severity) &&\n (subscription.acknowledged == null || a.ack === subscription.acknowledged) &&\n // The range is applied before paging, as device-management applies it, so `total`\n // counts the range. Measured against the view's now(), in the view's (shifted) time.\n (!subscription.window ||\n alarmOverlaps({ raisedTime: this.shifted(a.raised), clearedTime: this.shifted(a.cleared) }, subscription.window, this.now())),\n );\n // Newest first by raise time, compared as instants (not as text); the id breaks ties\n // so the order is deterministic.\n const when = (a: FoldedAlarm): string | null => a.raised ?? a.cleared;\n const byTime = (a: FoldedAlarm, b: FoldedAlarm): number => {\n const wa = when(a);\n const wb = when(b);\n if (wa === null || wb === null) return wa === wb ? 0 : wa === null ? 1 : -1;\n return compareInstants(wb, wa);\n };\n matches.sort((a, b) => byTime(a, b) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n if (subscription.countBySeverity) {\n // Counted from the same fold the rows come from, so the counts sum to what an alarm count\n // over this recording shows. (A type facet was refused above: the recording has no types.)\n const bySeverity = new Map<string, number>();\n for (const a of matches) bySeverity.set(a.sev, (bySeverity.get(a.sev) ?? 0) + 1);\n const severityCounts = [...bySeverity].map(([severity, count]) => ({ severity, count }));\n const signature = JSON.stringify(severityCounts);\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n sink.next({ alarms: [], total: matches.length, severityCounts });\n return;\n }\n const page = matches.slice(0, subscription.pageSize);\n // Whole snapshots, re-emitted only when the fold actually changed.\n const signature = JSON.stringify([matches.length, page]);\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n const snapshot: AlarmSnapshot = {\n alarms: page.map((a) => toAlarmRow({ ...a, raised: this.shifted(a.raised), cleared: this.shifted(a.cleared) })),\n total: matches.length,\n };\n sink.next(snapshot);\n };\n\n return this.open({ start: emit, advance: emit });\n }\n\n // \u2500\u2500 commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // The recording holds no command data, so this channel is never available.\n subscribeCommands(_subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n return this.failLater(new NotInRecordingError('commands'), sink);\n }\n\n // \u2500\u2500 locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A selector that names no location series asks for nothing, and one that resolves to no\n // device has nothing to place: both get the empty snapshot, as from the hub. Naming a\n // location series for devices that exist, on a recording with no positions, is a request\n // for data the recording does not have, which is an error, never an empty map.\n //\n // With positions recorded, each snapshot is every bound device's LAST position at the\n // cursor, as device-state answers the live map: a device not yet located is absent (the\n // service's contract), and the snapshot is sent again whenever one of them moves on.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n const ds = subscription.datasource;\n let tokens: string[] = [];\n if (ds?.location) {\n try {\n tokens = this.resolve(ds).map((g) => g.deviceToken);\n } catch (err) {\n return this.failLater(err, sink);\n }\n if (tokens.length > 0 && !this.rec.channels.locations) {\n return this.failLater(new NotInRecordingError('locations', 'no positions were recorded'), sink);\n }\n }\n let extra: RecordedExtra | null = null;\n let lastSignature: string | null = null;\n const emit = (cursor: number, isDisposed: () => boolean): void => {\n if (!extra) return;\n const { signature, locations } = extra.lastPositions(this.rec, tokens, cursor, (t) => this.iso(t));\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n sink.next({ kind: 'positions', deviceTokens: tokens, locations });\n };\n return this.open({\n start: (_cursor, isDisposed) => {\n // Nothing to place: the empty snapshot, as from the hub, and nothing more to fetch.\n if (tokens.length === 0) {\n if (!isDisposed()) sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return;\n }\n loadExtra().then(\n (m) => {\n extra = m;\n if (!this.stale) emit(this.cursor, isDisposed);\n },\n (err: unknown) => {\n if (!isDisposed()) sink.error?.(err);\n },\n );\n },\n advance: emit,\n });\n }\n\n // \u2500\u2500 aggregates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 buckets a stat widget folds into a span's aggregates, computed from the recording's\n // own rows: every (device, measurement) row whose time falls in the window AND at or\n // before the cursor (a replay must not know what happens next), grouped into buckets\n // `intervalSeconds` wide aligned on the epoch like the event store's, each with its exact\n // count, minimum, maximum and sum. Like the store, a bucket that holds no reading is not\n // returned. The window is in the source's own (shifted) time and is moved back by the\n // shift before the rows are read, so a re-timed board aggregates the same rows.\n async bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const groups = this.resolve(datasource);\n for (const g of groups) {\n for (const name of g.names) {\n if (!this.rec.channels.measurements.includes(name)) {\n throw new NotInRecordingError('measurements', `no '${name}' series was recorded`);\n }\n }\n }\n const { foldBuckets } = await loadExtra();\n return foldBuckets(this.rec, groups, this.index.byDevice, window, this.startMs + this.shiftMs, this.cursor);\n }\n\n // \u2500\u2500 availability \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A device selector is available iff the run has that device. Anchors, unbound slots and\n // no datasource have a legitimate empty state and are available, as on the hub.\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n if (datasource?.kind === 'device' && datasource.deviceToken) return this.devices.has(datasource.deviceToken);\n return true;\n }\n\n // Stop every subscription and leave the clock. Call when the view is replaced.\n disposeAll(): void {\n this.unlisten();\n for (const sub of [...this.subs]) sub.dispose();\n }\n}\n\nfunction toAlarmRow(a: FoldedAlarm): AlarmRow {\n return {\n token: a.id,\n originatorType: 'device',\n originatorToken: a.dev,\n alarmKey: a.key,\n metricKey: a.metric,\n state: a.state,\n acknowledged: a.ack,\n severity: a.sev,\n raisedTime: a.raised,\n clearedTime: a.cleared,\n // The recording notes THAT an alarm was acknowledged, not when or by whom.\n acknowledgedTime: null,\n acknowledgedBy: null,\n // The reading that raised it, as the recording holds it (recording.ts: `val`). A recording\n // without one plays with none; a nearby measurement would invent the integrator's state.\n // Null renders as an em dash.\n lastValue: a.val,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The portable half of a widget registration: a plain-data DESCRIPTOR naming what a widget\n// is, which data channel feeds it, what options it owns and how its data is bound. A\n// descriptor holds no React component, callback, import URL, token or executable code \u2014\n// it is JSON-serializable by construction (and checked to be so at runtime, see\n// validateWidgetDescriptor), because the definition parser and the headless validators\n// must be able to read one without loading, or trusting, any widget implementation.\n//\n// The trusted React implementation that goes with a descriptor lives in\n// @devicechain/widget-sdk; this module is the React-free vocabulary both sides share.\n\nimport type {\n AggregateStreamState,\n AlarmStreamState,\n CommandStreamState,\n LocationStreamState,\n MeasurementStreamState,\n} from './channel-snapshots';\nimport type { DefaultsOf, OptionConstraint, OptionKeysOfKind, WidgetOptionSpecs } from './option-schema';\nimport { WIDGET_TYPES, type WidgetType } from './types';\n\n// ---- Channels ---------------------------------------------------------------\n\n// A widget's data channel: which hub subscription binds it. `selection` and `none` open no\n// data channel at all \u2014 a selection widget drives a slot from the ambient candidate\n// provider, and a `none` widget is static.\nexport type WidgetChannel = 'measurement' | 'alarm' | 'control' | 'selection' | 'location' | 'aggregate' | 'none';\n\n// The resolved data a component on channel C receives. `null` marks the empty channel.\nexport type ChannelSnapshot<C extends WidgetChannel> = C extends 'measurement'\n ? MeasurementStreamState\n : C extends 'alarm'\n ? AlarmStreamState\n : C extends 'control'\n ? CommandStreamState\n : C extends 'location'\n ? LocationStreamState\n : C extends 'aggregate'\n ? AggregateStreamState\n : null;\n\n// ---- Ids --------------------------------------------------------------------\n\nexport type BuiltinWidgetId = WidgetType;\n// `scope/name`, e.g. 'acme/tank-level'. Namespace ownership is an operator convention, not\n// proof of publisher identity.\nexport type NamespacedWidgetId = `${string}/${string}`;\nexport type WidgetId = BuiltinWidgetId | NamespacedWidgetId;\n\nconst BUILTIN_IDS: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\nexport function isBuiltinWidgetId(id: unknown): id is BuiltinWidgetId {\n return typeof id === 'string' && BUILTIN_IDS.has(id);\n}\n\nconst NAMESPACED_ID = /^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9-]*$/;\n\nexport function isNamespacedWidgetId(id: unknown): id is NamespacedWidgetId {\n return typeof id === 'string' && NAMESPACED_ID.test(id);\n}\n\n// ---- Datasource -------------------------------------------------------------\n\nexport type DatasourceSelectorKind = 'device' | 'anchor' | 'slot';\n\n// Whether a widget binds a datasource, and which selector kinds its author may pick.\n// `none`/`selection` widgets must be `{mode:'none'}`; a control widget's command target\n// keeps its device-only requirement, so it offers only device and slot.\nexport type DatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly DatasourceSelectorKind[] };\n\ntype ControlDatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly ('device' | 'slot')[] };\n\nexport type DatasourcePolicyFor<C extends WidgetChannel> = C extends 'none' | 'selection'\n ? { mode: 'none' }\n : C extends 'control'\n ? ControlDatasourcePolicy\n : DatasourcePolicy;\n\n// ---- Binding policy ---------------------------------------------------------\n\n// A constant, or a reference to a declared option key. References are by NAME and checked\n// against the option's kind \u2014 the descriptor never carries a function or a query string.\nexport interface ConstBinding<T> {\n const: T;\n}\n\nexport interface NumberOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'number'>;\n default?: number;\n min?: number;\n}\n\nexport interface StringOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'string' | 'enum'>;\n default?: string;\n}\n\n// The alarm acknowledged filter is the STRING 'true'/'false' (see the built-in\n// `acknowledged` option), so it references an enum option whose legal values are a subset of\n// those two. The kind is checked at compile time; the value set at registration.\nexport interface AckOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'enum'>;\n default?: 'true' | 'false';\n}\n\ntype PageSize<S extends WidgetOptionSpecs> = ConstBinding<number> | NumberOptionRef<S>;\n\nexport type ChannelBindingPolicy<C extends WidgetChannel, S extends WidgetOptionSpecs = WidgetOptionSpecs> =\n C extends 'aggregate'\n ? {\n // The span the aggregates cover: the option pins it (seconds); unset, the board's range.\n windowSeconds?: NumberOptionRef<S>;\n }\n : C extends 'measurement'\n ? {\n window?: NumberOptionRef<S>;\n // Declaring it makes the widget TIME-RANGED: it draws a span rather than a sample count.\n // The referenced option pins the span (seconds); unset, the widget follows the board's\n // time range, which a viewer may change.\n windowSeconds?: NumberOptionRef<S>;\n // Makes the widget a FLEET widget: its datasource is read member by member (one row or bar\n // per device of an anchor) and `data.members` is set. `{ const: true }` always; or an option\n // reference with the value that turns the mode on (a bar chart's \"one bar per device\").\n members?: ConstBinding<true> | (StringOptionRef<S> & { equals: string });\n // Makes the widget a PRESENCE widget: it reads its devices' connectivity (online or offline,\n // and when each was last active) rather than their readings, and `data.members` is set with\n // each member's `presence`. `{ const: true }` only.\n presence?: ConstBinding<true>;\n // Makes the widget a HISTORY widget: it reads one device's stored history in time buckets over\n // its span (never the live feed), and is handed the buckets. `{ const: true }` only.\n history?: ConstBinding<true>;\n }\n : C extends 'alarm'\n ? {\n pageSize: PageSize<S>;\n state?: StringOptionRef<S>;\n severity?: StringOptionRef<S>;\n acknowledged?: AckOptionRef<S>;\n // Makes the widget group its alarms by the originating device's type (a tab strip):\n // the referenced option holding `equals` turns the mode on. `data.typeTabs` is then set.\n groupBy?: StringOptionRef<S> & { equals: string };\n // Makes the widget read the per-severity COUNTS of its alarms instead of alarm rows:\n // `data.severityCounts` is set and `data.alarms` is empty. `{ const: true }` only.\n countBySeverity?: ConstBinding<true>;\n // Only alarms on devices of the type (token) the referenced option holds; unset, all.\n deviceType?: StringOptionRef<S>;\n }\n : C extends 'control'\n ? { pageSize: PageSize<S> }\n : Record<string, never>;\n\n// ---- Interactions -----------------------------------------------------------\n\n// The host callbacks a widget asks for. Declaring one is a request, intersected with what\n// the host supports and the viewer is allowed \u2014 not a security boundary.\nexport interface WidgetInteractionDeclaration {\n alarmActions?: true;\n commandSend?: true;\n selection?: true;\n candidates?: true;\n}\n\n// `alarmActions` only makes sense on the alarm channel and `commandSend` only on control.\n// `'alarm' extends C` (rather than `C extends 'alarm'`) keeps the broad, unparameterized\n// descriptor able to name every key.\nexport type InteractionsFor<C extends WidgetChannel> = { selection?: true; candidates?: true } & ('alarm' extends C\n ? { alarmActions?: true }\n : unknown) &\n ('control' extends C ? { commandSend?: true } : unknown);\n\n// ---- Display ----------------------------------------------------------------\n\nexport interface WidgetDisplay {\n title: string;\n description?: string;\n category?: string;\n // Message ids a host may resolve in its own catalog before falling back to the text above.\n titleId?: string;\n categoryId?: string;\n}\n\n// ---- Size -------------------------------------------------------------------\n\n// A size in grid CELLS: columns across, rows down.\nexport interface WidgetSpan {\n cols: number;\n rows: number;\n}\n\n// Which axes a viewer-author may resize. 'both' is the default.\nexport type WidgetResizeMode = 'both' | 'horizontal' | 'vertical' | 'none';\n\n// How big a widget is allowed to be, declared by the widget and checked by the layout lint.\n// Plain JSON, validated when the catalog is built.\n//\n// Spans are counted in SQUARE cells, never smaller than the board's minCellSize (56 px by\n// default), so a minSpan sized at that cell is readable at every width the layout renders at.\n//\n// \uD83D\uDD11 The move to square cells did NOT bump SDK_API_VERSION, deliberately. The numbers here\n// changed meaning (they were cells of a 24-column grid with 40 px rows, roughly twice as many\n// columns), but the block is advisory size metadata: the editor and the lint read it, and\n// nothing renders from it. A package still declaring the old numbers keeps rendering, and a\n// board placing it outside them is a lint error, which is the right size of failure. A bump would\n// instead turn every stored widget of every extension into an unresolved placeholder, and refuse\n// every descriptor, until each package was re-released: a hard break for a soft change.\nexport interface WidgetLayoutSpec {\n // The size a freshly added widget takes.\n defaultSpan: WidgetSpan;\n // The resize floor. Absent means 1 x 1.\n minSpan?: WidgetSpan;\n // The resize ceiling. Absent means none.\n maxSpan?: WidgetSpan;\n // Absent means 'both'. A restricted mode PINS the other axis at defaultSpan: 'horizontal'\n // pins rows, 'vertical' pins columns, 'none' pins both. A minSpan or maxSpan, if declared,\n // must agree with defaultSpan on a pinned axis; neither is needed to pin it.\n resize?: WidgetResizeMode;\n}\n\n// The axes a resize mode holds at defaultSpan.\nexport function pinnedAxes(mode: WidgetResizeMode | undefined): readonly ('cols' | 'rows')[] {\n switch (mode) {\n case 'horizontal':\n return ['rows'];\n case 'vertical':\n return ['cols'];\n case 'none':\n return ['cols', 'rows'];\n default:\n return [];\n }\n}\n\n// Third-party widgets that declare no `layout` are added at this size, with no limits.\nexport const FALLBACK_DEFAULT_SPAN: Readonly<WidgetSpan> = Object.freeze({ cols: 4, rows: 2 });\n\n// ---- The descriptor ---------------------------------------------------------\n\nexport const SDK_API_VERSION = 1;\n\nexport interface WidgetDescriptor<\n C extends WidgetChannel = WidgetChannel,\n S extends WidgetOptionSpecs = WidgetOptionSpecs,\n> {\n id: WidgetId;\n // Strict MAJOR.MINOR.PATCH of this widget's release. Provenance, not a compatibility\n // key: a differing version still renders.\n version: string;\n // The contract generation, independent of platform and widget versions.\n sdkApiVersion: 1;\n // The persisted settings shape. Bump when stored options stop meaning the same thing.\n optionsVersion: number;\n display: WidgetDisplay;\n channel: C;\n datasource: DatasourcePolicyFor<C>;\n binding: ChannelBindingPolicy<C, S>;\n // The option schema this widget owns.\n options: S;\n // Rules spanning more than one option, as data.\n constraints?: readonly OptionConstraint<OptionKeysOfKind<S, 'number'>>[];\n // Starting values for a freshly added widget. Every key optional \u2014 see DefaultsOf.\n defaults: DefaultsOf<S>;\n interactions: InteractionsFor<C>;\n // Opt in to the frame's age readout (\"12 sec. ago\") for the widget's newest sample, and to\n // the stale treatment when the widget's `staleAfterSeconds` option sets a threshold. Only a\n // measurement widget has samples to age. Optional and absent by default, so a descriptor\n // that predates it, and every stored widget, is unchanged.\n freshness?: true;\n // Size contract (see WidgetLayoutSpec). Required of built-ins by a registry test; optional for\n // a third-party widget, which then gets FALLBACK_DEFAULT_SPAN and no limits.\n layout?: WidgetLayoutSpec;\n}\n\n// defineWidgetDescriptor is an identity that exists for inference: it infers the channel\n// and the schema from the literal, so a binding reference, a constraint or a default that\n// disagrees with the schema is a compile error. The registry-facing helper for a full\n// widget (descriptor + component) is `defineWidget` in @devicechain/widget-sdk.\nexport function defineWidgetDescriptor<const C extends WidgetChannel, const S extends WidgetOptionSpecs>(\n descriptor: WidgetDescriptor<C, S>,\n): WidgetDescriptor<C, S> {\n return descriptor;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// lintLayout: a pure check of a definition's placement against what each widget can show.\n//\n// The parser refuses what cannot be DRAWN (two grid footprints on one cell, a relative widget\n// with no host); this is the question it leaves open: is each placement a USABLE one? A widget\n// past the right edge, a widget smaller or larger than it declares it can be, a relative widget\n// pushed off the canvas. Answered once, here, for every producer of a definition (the sim\n// fixtures today; the editor and the publish gate next).\n//\n// Cells are square and of near-constant size, so a span in cells IS a readable size: a widget\n// that declares a sound minSpan cannot be too small to read at any width the board renders\n// without reflowing. There is therefore no pixel floor.\n//\n// Widget knowledge is INJECTED. `dashboards` must not depend on `widgets`, so the span limits a\n// widget type declares arrive through `spanLimitsOf`. Overlap is deliberately not a code: the\n// parser is where it is refused.\n\nimport {\n BASE_BREAKPOINT,\n breakpointColumns,\n gutterPx,\n layoutBreakpoints,\n layoutRects,\n resolvePlacement,\n spanPx,\n} from './grid';\nimport { sameLabel } from './same-label';\nimport { widgetBinding } from './slots';\nimport type { DashboardDefinition, Placement, WidgetInstance } from './types';\nimport { pinnedAxes, type WidgetLayoutSpec, type WidgetSpan } from './widget-descriptor';\n\nexport type LayoutLintSeverity = 'error' | 'warning' | 'info';\n\nexport type LayoutLintCode =\n | 'span-out-of-limits'\n | 'overrun-columns'\n | 'relative-off-canvas'\n | 'no-layout-declared'\n | 'title-duplicates-subtitle';\n\nexport interface LayoutFinding {\n code: LayoutLintCode;\n severity: LayoutLintSeverity;\n widgetId: string;\n message: string;\n // The breakpoint whose placement the finding is about, when it is not 'base'. An editor needs\n // it to point at the right placement, and two breakpoints' findings must not collapse into one.\n breakpoint?: string;\n}\n\n// The findings that BLOCK a publish: the placement is outside what the widget can show, or off the\n// canvas. A published version is immutable and is what viewers are served, so these are refused\n// there; the draft stays editable. The editor cannot normally produce them, so they catch what\n// arrives some other way (an import, an older descriptor, the API). The rest (`no-layout-declared`,\n// `title-duplicates-subtitle`) are advice.\nexport const PUBLISH_BLOCKING_LAYOUT_CODES: readonly LayoutLintCode[] = [\n 'span-out-of-limits',\n 'overrun-columns',\n 'relative-off-canvas',\n];\n\nexport function blockingLayoutFindings(findings: readonly LayoutFinding[]): LayoutFinding[] {\n return findings.filter((f) => PUBLISH_BLOCKING_LAYOUT_CODES.includes(f.code));\n}\n\n// What a widget type allows, per axis. Any bound may be absent, on either axis: a widget that\n// declares none can be any size, and one that pins only its rows has no column ceiling.\nexport interface SpanLimits {\n minSpan?: Partial<WidgetSpan>;\n maxSpan?: Partial<WidgetSpan>;\n}\n\nexport interface LayoutLintOptions {\n // The column count the `base` placements are checked against: the authored count, or a\n // reflow count. A breakpoint that declares its own count is checked at that count.\n columns: number;\n // undefined means the widget declares no limits at all, which is reported (info) rather\n // than assumed fine, so a type that forgot to declare them is visible.\n spanLimitsOf: (widget: WidgetInstance) => SpanLimits | undefined;\n}\n\n// The limits a descriptor's `layout` block declares, in the shape lintLayout takes: its\n// minSpan and maxSpan, with an axis its resize mode pins held at defaultSpan. A descriptor with\n// no block yields undefined (reported as `no-layout-declared`), so a host wires\n// `spanLimitsOf: (w) => spanLimitsOfLayout(registry.descriptor(w.type)?.layout)`.\nexport function spanLimitsOfLayout(layout: WidgetLayoutSpec | undefined): SpanLimits | undefined {\n if (!layout) return undefined;\n const min: Partial<WidgetSpan> = { ...layout.minSpan };\n const max: Partial<WidgetSpan> = { ...layout.maxSpan };\n for (const axis of pinnedAxes(layout.resize)) {\n min[axis] = layout.defaultSpan[axis];\n max[axis] = layout.defaultSpan[axis];\n }\n const limits: SpanLimits = {};\n if (Object.keys(min).length > 0) limits.minSpan = min;\n if (Object.keys(max).length > 0) limits.maxSpan = max;\n return limits;\n}\n\n// The cell sizes a relative widget's box is measured at. Its position is a straight line in the\n// cell size (an offset is in cell pitches, the gutter is fixed px), so a box inside the canvas at\n// the smallest cell and at a very large one is inside it at every size between: the smallest is\n// where the gutter weighs most, the large one where the cells do.\n// Exported to the editor model (not the package), so fitToLimits repairs against the same cells.\nexport const RELATIVE_PROBE_CELLS = (minCellSize: number) => [minCellSize, minCellSize * 16];\n\n// Rounding slack, in px, for a box that sits exactly on the canvas edge.\nconst EDGE_EPSILON = 0.01;\n\nexport function lintLayout(def: DashboardDefinition, options: LayoutLintOptions): LayoutFinding[] {\n const findings: LayoutFinding[] = [];\n const columnsAt = (bp: string) =>\n bp === BASE_BREAKPOINT || breakpointColumns(def.canvas, bp) === def.canvas.grid.columns\n ? options.columns\n : breakpointColumns(def.canvas, bp);\n const breakpoints = layoutBreakpoints(def.canvas.breakpoints, def.widgets);\n\n for (const widget of def.widgets) {\n const limits = options.spanLimitsOf(widget);\n if (!limits) {\n findings.push({\n code: 'no-layout-declared',\n severity: 'info',\n widgetId: widget.id,\n message: `${widget.type} declares no span limits, so its size cannot be checked`,\n });\n }\n\n for (const [bp, placement] of Object.entries(widget.layout)) {\n const where = bp === BASE_BREAKPOINT ? '' : ` at breakpoint ${bp}`;\n const at = bp === BASE_BREAKPOINT ? {} : { breakpoint: bp };\n const columns = columnsAt(bp);\n if (placement.mode === 'grid' && placement.col + placement.cols > columns) {\n findings.push({\n code: 'overrun-columns',\n severity: 'error',\n widgetId: widget.id,\n ...at,\n message: `spans columns ${placement.col}..${placement.col + placement.cols} of ${columns}${where}`,\n });\n }\n const span = spanOf(def, widget, bp, placement);\n if (limits && span) {\n const problem = spanProblem(span.cols, span.rows, limits);\n if (problem) {\n findings.push({\n code: 'span-out-of-limits',\n severity: 'error',\n widgetId: widget.id,\n ...at,\n message: `${span.cols}x${span.rows} ${problem}${where}`,\n });\n }\n }\n }\n\n const duplicate = titleDuplicatesSubtitle(def, widget);\n if (duplicate) {\n findings.push({\n code: 'title-duplicates-subtitle',\n severity: 'warning',\n widgetId: widget.id,\n message: duplicate,\n });\n }\n }\n\n // A relative widget is checked at every breakpoint it is SHOWN at, its inherited base placement\n // included, wherever that can differ from base: its host (or its host's host) may have its own\n // placement there, or the breakpoint may be narrower. Where nothing differs, base already said it.\n const byId = new Map(def.widgets.map((w) => [w.id, w]));\n const differsFromBase = (w: WidgetInstance, bp: string): boolean => {\n if (columnsAt(bp) !== options.columns) return true;\n for (let cur: WidgetInstance | undefined = w, hops = 0; cur && hops < 3; hops++) {\n if (Object.prototype.hasOwnProperty.call(cur.layout, bp)) return true;\n const p = resolvePlacement(cur.layout, bp);\n cur = p.mode === 'relative' ? byId.get(p.host) : undefined;\n }\n return false;\n };\n for (const bp of breakpoints) {\n const columns = columnsAt(bp);\n const gutter = gutterPx(def.canvas.grid);\n const off = new Set<string>();\n for (const cell of RELATIVE_PROBE_CELLS(def.canvas.grid.minCellSize)) {\n const width = spanPx(columns, cell, gutter.x);\n for (const [id, r] of layoutRects(def.widgets, bp, cell, gutter)) {\n const w = byId.get(id)!;\n if (resolvePlacement(w.layout, bp).mode !== 'relative' || off.has(id)) continue;\n if (bp !== BASE_BREAKPOINT && !differsFromBase(w, bp)) continue;\n if (r.x < -EDGE_EPSILON || r.y < -EDGE_EPSILON || r.x + r.w > width + EDGE_EPSILON) {\n off.add(id);\n const where = bp === BASE_BREAKPOINT ? '' : ` at breakpoint ${bp}`;\n findings.push({\n code: 'relative-off-canvas',\n severity: 'error',\n widgetId: id,\n ...(bp === BASE_BREAKPOINT ? {} : { breakpoint: bp }),\n message: `falls outside the ${columns}-column canvas${where} (left ${round(r.x)}, top ${round(r.y)}, right ${round(r.x + r.w)} of ${round(width)} px at ${round(cell)} px cells)`,\n });\n }\n }\n }\n }\n\n return findings;\n}\n\nfunction round(n: number): number {\n return Math.round(n * 10) / 10;\n}\n\n// The span a placement covers: a grid footprint's own, a relative widget's size, or for a\n// relative widget that fills its host, the host's span (one level of chaining at most).\nfunction spanOf(\n def: DashboardDefinition,\n widget: WidgetInstance,\n bp: string,\n p: Placement,\n): { cols: number; rows: number } | undefined {\n if (p.mode === 'grid') return { cols: p.cols, rows: p.rows };\n if (!('fill' in p.size)) return p.size;\n const host = def.widgets.find((w) => w.id === p.host);\n if (!host || host === widget) return undefined;\n return spanOf(def, host, bp, resolvePlacement(host.layout, bp));\n}\n\nfunction spanProblem(cols: number, rows: number, limits: SpanLimits): string | undefined {\n const { minSpan = {}, maxSpan = {} } = limits;\n const bound = (b: Partial<WidgetSpan>) =>\n b.cols === undefined ? `${b.rows}-row` : b.rows === undefined ? `${b.cols}-column` : `${b.cols}x${b.rows}`;\n if (cols < (minSpan.cols ?? 1) || rows < (minSpan.rows ?? 1)) {\n return `is below the ${bound(minSpan)} minimum`;\n }\n if (cols > (maxSpan.cols ?? Infinity) || rows > (maxSpan.rows ?? Infinity)) {\n return `is above the ${bound(maxSpan)} maximum`;\n }\n return undefined;\n}\n\n// The frame shows the title option and, under it, the token of the entity the widget is\n// bound to. Two ways of putting one identity on screen twice are visible from the\n// definition, and both are reported:\n// - the title IS the token (a renderer can also see this one and suppress the subject line);\n// - the title is the bound SLOT's label, and that slot is bound to the entity the subject\n// line names. A slot label is the author's name for the entity, so the card reads e.g.\n// \"SP-DZ-0001\" over \"sp-dozer-01\": the same machine under two names, which no\n// string compare in the renderer can catch.\n// What it cannot see is a title that names the entity some third way the definition does\n// not record (an external id typed straight into the title with no slot label behind it):\n// that is for whoever builds the board, not for this check.\nfunction titleDuplicatesSubtitle(def: DashboardDefinition, widget: WidgetInstance): string | undefined {\n const title = widget.options?.title;\n if (typeof title !== 'string' || title.trim() === '') return undefined;\n const binding = widgetBinding(def, widget);\n const subject = binding?.kind === 'device' ? binding.deviceToken : binding?.anchor.targetToken;\n if (!subject) return undefined;\n const ds = widget.datasource;\n const slotLabel = ds?.kind === 'slot' ? def.slots?.[ds.slot]?.label : undefined;\n const same = (a: string | undefined) => sameLabel(a, title);\n if (same(subject)) return `the title and the subject line both read \"${title.trim()}\"`;\n if (same(slotLabel)) {\n return `the title \"${title.trim()}\" is the slot's name for \"${subject}\", which the subject line already shows`;\n }\n return undefined;\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, retitle, add) is unit-testable; the editor UI\n// (the console) wires these to its drag/resize and the save mutation. Lives here \u2014\n// next to parse/serialize \u2014 because it is the definition's edit lifecycle, not\n// app-specific glue (ADR-039 authoring in the console).\n//\n// The editor edits the 'base' placements only; per-breakpoint editing is deferred. Every\n// transform keeps the one invariant the parser enforces: no two grid footprints overlap. A move,\n// a resize, an add or a narrower grid that would collide pushes the widgets in the way DOWN\n// (pushDown), so the editor can never produce a definition that fails to load.\n\nimport { BASE_BREAKPOINT, generateWidgetId } from './definition';\nimport {\n breakpointColumns,\n firstFreeSlot,\n gutterPx,\n layoutBreakpoints,\n pushAside,\n pushDown,\n relativeRect,\n resolvePlacement,\n layoutRects,\n spanPx,\n type Gutter,\n type PlacedGrid,\n} from './grid';\nimport { lintLayout, RELATIVE_PROBE_CELLS, type SpanLimits } from './layout-lint';\nimport { DEFAULT_CANVAS_DENSITY } from './types';\nimport type {\n CanvasDensity,\n CanvasGrid,\n CanvasSizing,\n DashboardDefinition,\n GridPlacement,\n Placement,\n PlacementAnchor,\n RelativePlacement,\n ResolvedWidgetInstance,\n WidgetInstance,\n} from './types';\nimport { FALLBACK_DEFAULT_SPAN, type WidgetDescriptor } from './widget-descriptor';\n\n// The base placement a widget is edited by.\nexport function basePlacement(widget: WidgetInstance): Placement {\n return widget.layout[BASE_BREAKPOINT];\n}\n\n// resolveCollisions pushes apart the grid footprints that overlap at one breakpoint, the `first`\n// widgets keeping their place. At a non-base breakpoint a widget that has no entry of its own\n// shows its base placement; if that one has to move, the move is written as an entry for the\n// breakpoint, so the base layout is not disturbed by an edit to another one.\nfunction resolveCollisions(\n def: DashboardDefinition,\n breakpoint: string,\n first: readonly string[],\n aside = false,\n): DashboardDefinition {\n const placed: PlacedGrid[] = [];\n for (const w of def.widgets) {\n const p = resolvePlacement(w.layout, breakpoint);\n if (p.mode === 'grid') placed.push({ id: w.id, placement: p });\n }\n const resolved = aside ? pushAside(placed, first) : pushDown(placed, first);\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const before = resolvePlacement(w.layout, breakpoint);\n const after = resolved.get(w.id);\n if (!after || after === before) return w;\n changed = true;\n return { ...w, layout: { ...w.layout, [breakpoint]: after } };\n });\n return changed ? { ...def, widgets } : def;\n}\n\n// resolveEverywhere resolves collisions at every breakpoint the parser checks, base first. An\n// edit to a base placement changes what every breakpoint without its own entry for that widget\n// shows, so a move that is clear at base can still land on another widget's override at a\n// breakpoint (and the parser refuses an overlap at any of them).\nexport function resolveEverywhere(def: DashboardDefinition, first: readonly string[], aside = false): DashboardDefinition {\n let next = def;\n // The swap is an edit of the base layout: a breakpoint arrangement the edit reaches only through\n // the inherited base placement is pushed apart, never rearranged.\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets))\n next = resolveCollisions(next, bp, first, aside && bp === BASE_BREAKPOINT);\n return next;\n}\n\n// How an edit settles the widgets around the one it changed. Either way the edited widget lands\n// exactly where it was put, only the widgets its new footprint covers move, and every other widget\n// keeps its place, so a gap the author left (to align or to group) is never closed by an\n// unrelated edit. By default a covered widget is pushed DOWN out of the way. With `nearest` it\n// settles at the free row nearest the one it had, up or down (pushAside), so dropping a widget\n// onto a neighbour swaps the two instead of shoving the neighbour further down the board: what an\n// Android launcher does. The editor's pointer and keyboard edits use `nearest`.\nexport interface EditOptions {\n nearest?: boolean;\n}\n\n// setWidgetPlacement replaces a widget's base placement. A grid placement pushes whatever it now\n// covers down out of its way, at base and at every breakpoint that shows it.\nexport function setWidgetPlacement(\n def: DashboardDefinition,\n id: string,\n placement: Placement,\n options: EditOptions = {},\n): DashboardDefinition {\n const next = {\n ...def,\n widgets: def.widgets.map((w) => (w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: placement } } : w)),\n };\n return placement.mode === 'grid' ? resolveEverywhere(next, [id], options.nearest) : next;\n}\n\n// clampSpan holds a span inside a widget's declared limits and inside the columns that remain to\n// the right of `col`. Where the two disagree the grid wins (a widget never leaves the canvas), so\n// a minSpan wider than what is left to the right of `col` yields to the edge: the caller moves\n// `col` back first when it can (see resizeWidget).\nexport function clampSpan(\n span: { cols: number; rows: number },\n limits: SpanLimits | undefined,\n room: number,\n): { cols: number; rows: number } {\n const cols = Math.min(room, Math.max(limits?.minSpan?.cols ?? 1, Math.min(limits?.maxSpan?.cols ?? Infinity, span.cols)));\n const rows = Math.max(limits?.minSpan?.rows ?? 1, Math.min(limits?.maxSpan?.rows ?? Infinity, span.rows));\n return { cols: Math.max(1, Math.round(cols)), rows: Math.max(1, Math.round(rows)) };\n}\n\n// moveWidget drags a grid widget so its top-left cell is (col, row): the span is kept, the\n// footprint is held inside the columns and above row 0, and whatever it now covers is pushed\n// down out of its way (setWidgetPlacement). A relative widget has no cell to drag, so it is\n// returned unchanged.\nexport function moveWidget(\n def: DashboardDefinition,\n id: string,\n to: { col: number; row: number },\n columns: number = def.canvas.grid.columns,\n options: EditOptions = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'grid') return def;\n const col = Math.min(Math.max(0, Math.round(to.col)), Math.max(0, columns - p.cols));\n const row = Math.max(0, Math.round(to.row));\n if (col === p.col && row === p.row) return def;\n return setWidgetPlacement(def, id, { ...p, col, row }, options);\n}\n\n// resizeWidget resizes a grid widget to `target` (a footprint, e.g. a snapped drag of a handle),\n// stopping at the widget's minSpan and maxSpan and at the canvas edge. `fromLeft` / `fromTop`\n// say which edge the handle moved: that edge follows the pointer and the opposite one stays put,\n// so a widget clamped at its minimum does not slide away from the edge it was held by.\n// Neighbours are pushed down.\nexport function resizeWidget(\n def: DashboardDefinition,\n id: string,\n target: { col: number; row: number; cols: number; rows: number },\n limits: SpanLimits | undefined,\n edges: { fromLeft?: boolean; fromTop?: boolean } = {},\n columns: number = def.canvas.grid.columns,\n options: EditOptions = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'grid') return def;\n const right = target.col + target.cols;\n const bottom = target.row + target.rows;\n const room = edges.fromLeft ? Math.max(1, right) : Math.max(1, columns - Math.max(0, target.col));\n const { cols, rows } = clampSpan(target, limits, Math.min(columns, room));\n const col = edges.fromLeft ? Math.max(0, right - cols) : Math.max(0, target.col);\n const row = edges.fromTop ? Math.max(0, bottom - rows) : Math.max(0, target.row);\n if (col === p.col && row === p.row && cols === p.cols && rows === p.rows) return def;\n return setWidgetPlacement(def, id, { mode: 'grid', col, row, cols, rows }, options);\n}\n\n// deleteWidget removes a widget by id. A widget that was placed relative to it would be left\n// with no host, which the parser refuses, so each one is put back on the grid instead, at its\n// own size (a `fill` one at its host's), in the first free slot.\nexport function deleteWidget(def: DashboardDefinition, id: string): DashboardDefinition {\n const gone = def.widgets.find((w) => w.id === id);\n let next: DashboardDefinition = { ...def, widgets: def.widgets.filter((w) => w.id !== id) };\n if (!gone) return next;\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets)) {\n const hostPlacement = resolvePlacement(gone.layout, bp);\n const columns = breakpointColumns(def.canvas, bp);\n for (const w of next.widgets) {\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'relative' || p.host !== id) continue;\n const size = 'fill' in p.size ? hostSpan(hostPlacement, next, bp) : p.size;\n const occupied = next.widgets\n .map((o) => resolvePlacement(o.layout, bp))\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const cols = Math.min(size.cols, columns);\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n const grid: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n next = { ...next, widgets: next.widgets.map((o) => (o.id === w.id ? { ...o, layout: { ...o.layout, [bp]: grid } } : o)) };\n }\n }\n // A slot free at one breakpoint can be taken at another that inherits it.\n return resolveEverywhere(next, []);\n}\n\n// The span a (possibly relative) placement covers, for re-homing a `fill` widget.\nexport function hostSpan(p: Placement, def: DashboardDefinition, bp: string): { cols: number; rows: number } {\n if (p.mode === 'grid') return { cols: p.cols, rows: p.rows };\n if (!('fill' in p.size)) return p.size;\n const host = def.widgets.find((w) => w.id === p.host);\n return host ? hostSpan(resolvePlacement(host.layout, bp), def, bp) : { cols: 1, rows: 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 into the canvas, returning a new definition. It\n// owns the grid invariants (a live edit never round-trips through parse): columns floors to an\n// integer >= 1 and minCellSize to >= 1. When columns SHRINKS, every placement in the grid's own\n// column count is clamped back inside it, and anything the clamp makes collide is pushed down.\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.minCellSize !== undefined) grid.minCellSize = Math.max(1, patch.minCellSize);\n let next: DashboardDefinition = { ...def, canvas: { ...def.canvas, grid } };\n if (grid.columns >= def.canvas.grid.columns) return next;\n // Breakpoints that declare their own column count are unaffected.\n const affected = layoutBreakpoints(def.canvas.breakpoints, def.widgets).filter(\n (bp) => breakpointColumns(next.canvas, bp) === grid.columns,\n );\n for (const bp of affected) {\n next = {\n ...next,\n widgets: next.widgets.map((w) => {\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'grid' || p.col + p.cols <= grid.columns) return w;\n const col = Math.min(p.col, grid.columns - 1);\n return { ...w, layout: { ...w.layout, [bp]: { ...p, col, cols: Math.min(p.cols, grid.columns - col) } } };\n }),\n };\n next = resolveCollisions(next, bp, []);\n }\n return next;\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// setCanvasDensity sets the authored density. `comfortable` (the default) is stored as absent so\n// a board nobody densified serializes exactly as before.\nexport function setCanvasDensity(def: DashboardDefinition, density: CanvasDensity): DashboardDefinition {\n const { density: _prev, ...rest } = def.canvas;\n return { ...def, canvas: density === DEFAULT_CANVAS_DENSITY ? rest : { ...rest, density } };\n}\n\n// updateWidget replaces the widget with the matching id, returning a new definition.\nexport function updateWidget(def: DashboardDefinition, id: string, next: WidgetInstance): DashboardDefinition {\n return { ...def, widgets: def.widgets.map((w) => (w.id === id ? next : w)) };\n}\n\n// addWidget appends a new widget of the described kind at the descriptor's default span (no\n// wider than the grid), in the first free slot scanning rows top to bottom, so it never lands on\n// top of another widget. Datasource left undefined. Its starting options are the descriptor's own\n// `defaults` \u2014 the widget's declaration, not a table in the editor keyed by widget id. Returns\n// the new definition and the new widget's id so the caller can select it.\n//\n// The new widget records the registration it was authored against: its release, its settings\n// version and its SDK generation (`widget: { version, optionsVersion, sdkApiVersion }`). For\n// an extension that record is what lets the next load match it to the installed package at\n// all \u2014 a registered extension stored without it is incomplete and parses to a placeholder.\n// `optionsVersion` and `sdkApiVersion` are the compatibility key (with the id), so both are\n// recorded: a reference without the SDK generation could never be refused by a host that has\n// moved to a newer one. `version` is the provenance the editor's version notice compares.\nexport function addWidget(\n def: DashboardDefinition,\n descriptor: Pick<WidgetDescriptor, 'id' | 'defaults' | 'version' | 'optionsVersion' | 'sdkApiVersion' | 'layout'>,\n): { definition: DashboardDefinition; id: string } {\n const id = generateWidgetId();\n const span = descriptor.layout?.defaultSpan ?? FALLBACK_DEFAULT_SPAN;\n const columns = def.canvas.grid.columns;\n const cols = Math.min(span.cols, columns);\n const occupied = def.widgets.map(basePlacement).filter((p): p is GridPlacement => p.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, span.rows, columns);\n const placement: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: span.rows };\n const common = {\n id,\n widget: {\n version: descriptor.version,\n optionsVersion: descriptor.optionsVersion,\n sdkApiVersion: descriptor.sdkApiVersion,\n },\n layout: { [BASE_BREAKPOINT]: placement },\n options: { ...descriptor.defaults },\n };\n // The id came from a registered descriptor, so it names either a built-in or a namespaced\n // extension; which one is the registry's knowledge, and this transform does not branch on it.\n const widget = { ...common, type: descriptor.id } as ResolvedWidgetInstance;\n // The slot is free at base; a breakpoint override elsewhere may cover it, so resolve there too\n // (the new widget keeps its place, and whatever it covers moves).\n return { definition: resolveEverywhere({ ...def, widgets: [...def.widgets, widget] }, [id]), id };\n}\n\n// A pixel rectangle a drag/resize reports.\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: the live (square) cell and the gutters. What the\n// canvas measures so it can map placements to and from the pixel rects its drag/resize works in.\nexport interface GridGeometry {\n cell: number;\n gutter: Gutter;\n}\n\n// gridPlacementToPx renders a grid placement to a pixel rect, relative to the grid's top-left\n// corner. The stride between cell starts is (cell + gutter); a span of N covers N cells and the\n// N-1 gutters between them.\nexport function gridPlacementToPx(p: GridPlacement, geom: GridGeometry): PixelRect {\n return {\n x: p.col * (geom.cell + geom.gutter.x),\n y: p.row * (geom.cell + geom.gutter.y),\n w: spanPx(p.cols, geom.cell, geom.gutter.x),\n h: spanPx(p.rows, geom.cell, geom.gutter.y),\n };\n}\n\n// pxToGridPlacement snaps a pixel rect back to a grid placement. Clamped so a widget can't leave\n// the canvas (col,row >= 0) or vanish (spans >= 1); when `columns` is given, also clamped to the\n// RIGHT edge (col <= columns-1, cols <= columns-col) so a boundary drag can't commit a footprint\n// that overruns the grid.\nexport function pxToGridPlacement(px: PixelRect, geom: GridGeometry, columns?: number): GridPlacement {\n const colStride = Math.max(1, geom.cell + geom.gutter.x);\n const rowStride = Math.max(1, geom.cell + geom.gutter.y);\n let col = Math.max(0, Math.round(px.x / colStride));\n let cols = Math.max(1, Math.round((px.w + geom.gutter.x) / colStride));\n if (columns !== undefined) {\n col = Math.min(col, columns - 1);\n cols = Math.min(cols, columns - col);\n }\n return {\n mode: 'grid',\n col,\n row: Math.max(0, Math.round(px.y / rowStride)),\n cols,\n rows: Math.max(1, Math.round((px.h + geom.gutter.y) / rowStride)),\n };\n}\n\n// ---- Size readout -------------------------------------------------------------------\n\n// formatSpan is the \"2\u00D73\" readout shown while a widget is resized, and the footprint the add-widget\n// picker lists: columns by rows, with the multiplication sign.\nexport function formatSpan(span: { cols: number; rows: number }): string {\n return `${span.cols}\u00D7${span.rows}`;\n}\n\n// ---- Placement mode -----------------------------------------------------------------\n\n// The step a relative widget's offset moves in: whole cells, or quarter cells for fine placement.\nexport const OFFSET_STEP = 1;\nexport const OFFSET_STEP_FINE = 0.25;\n\n// snapOffset rounds an offset (in cell pitches) to the step: whole cells, or quarter cells with\n// `fine` (the Shift key). Never returns -0.\nexport function snapOffset(value: number, fine = false): number {\n const step = fine ? OFFSET_STEP_FINE : OFFSET_STEP;\n const snapped = Math.round(value / step) * step;\n return snapped === 0 ? 0 : snapped;\n}\n\n// What a relative widget's box is measured in, to keep it on the canvas as it is dragged.\nexport interface RelativeBounds {\n cell: number;\n gutter: Gutter;\n columns: number;\n}\n\nfunction replaceBase(def: DashboardDefinition, id: string, placement: Placement): DashboardDefinition {\n return {\n ...def,\n widgets: def.widgets.map((w) => (w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: placement } } : w)),\n };\n}\n\n// setRelativeOffset moves a relative widget by setting its offset, snapped to whole cells or, with\n// `fine`, quarter cells. When `bounds` is given the offset is then pulled back, in whole steps, until\n// the box is inside the canvas at that cell size (the editor cannot produce an off-canvas widget by\n// dragging; the lint still checks the extremes of cell size). A grid widget has no offset, so it is\n// returned unchanged. A zero offset is stored as absent.\nexport function setRelativeOffset(\n def: DashboardDefinition,\n id: string,\n offset: { x: number; y: number },\n options: { fine?: boolean; bounds?: RelativeBounds } = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'relative') return def;\n const step = options.fine ? OFFSET_STEP_FINE : OFFSET_STEP;\n let x = snapOffset(offset.x, options.fine);\n let y = snapOffset(offset.y, options.fine);\n const { bounds } = options;\n if (bounds) {\n const host = layoutRects(def.widgets, BASE_BREAKPOINT, bounds.cell, bounds.gutter).get(p.host);\n if (host) {\n const r = relativeRect({ ...p, offset: { x, y } }, host, bounds.cell, bounds.gutter);\n const px = (bounds.cell + bounds.gutter.x) * step;\n const py = (bounds.cell + bounds.gutter.y) * step;\n const width = spanPx(bounds.columns, bounds.cell, bounds.gutter.x);\n if (r.x < 0) x += Math.ceil(-r.x / px) * step;\n else if (r.x + r.w > width) x -= Math.ceil((r.x + r.w - width) / px) * step;\n if (r.y < 0) y += Math.ceil(-r.y / py) * step;\n }\n }\n const next: RelativePlacement = { ...p };\n if (x === 0 && y === 0) delete next.offset;\n else next.offset = { x, y };\n if ((next.offset?.x ?? 0) === (p.offset?.x ?? 0) && (next.offset?.y ?? 0) === (p.offset?.y ?? 0)) return def;\n return replaceBase(def, id, next);\n}\n\n// A widget can host another when it is a grid widget at every breakpoint it is shown at (the\n// parser allows one level of chaining only, and a host that is itself relative could close a\n// chain), and is not the widget being placed.\nfunction canHost(def: DashboardDefinition, host: WidgetInstance | undefined, id: string): host is WidgetInstance {\n if (!host || host.id === id) return false;\n return layoutBreakpoints(def.canvas.breakpoints, def.widgets).every(\n (bp) => resolvePlacement(host.layout, bp).mode === 'grid',\n );\n}\n\n// canHostRelative says whether `hostId` is a valid host for the widget `id` (what placeRelative\n// would accept), so an editor can offer only the widgets that will work.\nexport function canHostRelative(def: DashboardDefinition, id: string, hostId: string): boolean {\n return canHost(def, def.widgets.find((x) => x.id === hostId), id);\n}\n\n// placeRelative puts a widget against a HOST: it keeps its current size, sits at the host's\n// `anchor` point (the same point of itself, so a badge at `top-right` sits in that corner), and\n// leaves the grid, so it no longer pushes anything. Placing an already-relative widget re-homes\n// it (a new host and anchor, the size kept, the offset dropped). A host that is missing, is the\n// widget itself, or is not a grid widget at every breakpoint is refused: the definition comes back\n// unchanged. Only the base placement is edited; a breakpoint with its own entry for the widget\n// keeps it.\nexport function placeRelative(\n def: DashboardDefinition,\n id: string,\n hostId: string,\n anchor: PlacementAnchor,\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n if (!canHost(def, def.widgets.find((x) => x.id === hostId), id)) return def;\n const p = basePlacement(w);\n const size = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : p.size;\n return replaceBase(def, id, { mode: 'relative', host: hostId, anchor, size });\n}\n\n// setRelativeAnchor changes the host point a relative widget hangs from, keeping the rest.\nexport function setRelativeAnchor(def: DashboardDefinition, id: string, anchor: PlacementAnchor): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n const p = w && basePlacement(w);\n if (!p || p.mode !== 'relative' || p.anchor === anchor) return def;\n return replaceBase(def, id, { ...p, anchor });\n}\n\n// placeOnGrid is the way back: a relative widget takes its own size (a `fill` one its host's) in\n// the first free slot of the grid, pushing nothing. A grid widget is returned unchanged.\nexport function placeOnGrid(def: DashboardDefinition, id: string): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'relative') return def;\n const columns = def.canvas.grid.columns;\n const size = 'fill' in p.size ? hostSpan(p, def, BASE_BREAKPOINT) : p.size;\n const cols = Math.min(size.cols, columns);\n const occupied = def.widgets\n .filter((o) => o.id !== id)\n .map(basePlacement)\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n const grid: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n return resolveEverywhere(replaceBase(def, id, grid), [id]);\n}\n\n// ---- Fit to limits ------------------------------------------------------------------\n\n// fitToLimits is the one-click repair for a board whose widgets are outside what they declare they\n// can be (a converted legacy board, an import, a definition the API wrote), and the way out of\n// every finding that blocks a publish:\n// - every placement, at every breakpoint, has its span clamped into the widget's limits and into\n// the columns, and a footprint that overran the right edge is pulled back inside it;\n// - a relative widget that `fill`s a host its own limits do not allow is given an explicit size,\n// the host's span clamped into those limits;\n// - a relative widget off the canvas is moved back onto it, keeping as much of its offset as the\n// canvas allows, at every cell size the lint measures (fitRelativeOnCanvas). At a breakpoint\n// where it shows its base placement, the fix is written as an entry for that breakpoint, so\n// base is not disturbed;\n// - any collision a grown span causes is then pushed apart through the model. Nothing is pulled\n// up into a gap: a board the author spaced on purpose keeps its spacing, and a widget that\n// shrank leaves the room it gave up.\n// Returns the same object when nothing needed fixing, so a caller can tell \"already fits\" from\n// \"fixed\".\nexport function fitToLimits(\n def: DashboardDefinition,\n spanLimitsOf: (widget: WidgetInstance) => SpanLimits | undefined,\n): DashboardDefinition {\n let changed = false;\n const columnsAt = (bp: string) => breakpointColumns(def.canvas, bp);\n const clamped: DashboardDefinition = {\n ...def,\n widgets: def.widgets.map((w) => {\n const limits = spanLimitsOf(w);\n const layout: Record<string, Placement> = {};\n let touched = false;\n for (const [bp, p] of Object.entries(w.layout)) {\n const columns = columnsAt(bp);\n let q: Placement = p;\n if (p.mode === 'grid') {\n const span = clampSpan(p, limits, columns);\n const col = Math.max(0, Math.min(p.col, columns - span.cols));\n if (span.cols !== p.cols || span.rows !== p.rows || col !== p.col) q = { ...p, ...span, col };\n } else if (!('fill' in p.size)) {\n const size = clampSpan(p.size, limits, columns);\n if (size.cols !== p.size.cols || size.rows !== p.size.rows) q = { ...p, size };\n }\n if (q !== p) touched = true;\n layout[bp] = q;\n }\n if (!touched) return w;\n changed = true;\n return { ...w, layout };\n }),\n };\n // A `fill` widget's span is its host's, read once the hosts are clamped.\n let next: DashboardDefinition = {\n ...clamped,\n widgets: clamped.widgets.map((w) => {\n const limits = spanLimitsOf(w);\n if (!limits) return w;\n let layout: WidgetInstance['layout'] | undefined;\n for (const [bp, p] of Object.entries(w.layout)) {\n if (p.mode !== 'relative' || !('fill' in p.size)) continue;\n const span = hostSpan(p, clamped, bp);\n const size = clampSpan(span, limits, columnsAt(bp));\n if (size.cols === span.cols && size.rows === span.rows) continue;\n layout = { ...(layout ?? w.layout), [bp]: { ...p, size } };\n }\n if (!layout) return w;\n changed = true;\n return { ...w, layout };\n }),\n };\n // A relative widget off the canvas is moved back onto it. Its host may itself be a relative\n // widget being moved, so a second pass sees the host where it ends up.\n for (let pass = 0; pass < 3; pass++) {\n const off = lintLayout(next, { columns: def.canvas.grid.columns, spanLimitsOf }).filter(\n (f) => f.code === 'relative-off-canvas',\n );\n if (off.length === 0) break;\n for (const f of off) {\n const fixed = fitRelativeOnCanvas(next, f.widgetId, f.breakpoint ?? BASE_BREAKPOINT);\n if (fixed !== next) changed = true;\n next = fixed;\n }\n }\n if (!changed) return def;\n return resolveEverywhere(next, []);\n}\n\n// fitRelativeOnCanvas moves the relative widget `id`, as it is shown at `bp`, back inside the\n// canvas at every cell size the lint measures. A box's position is a straight line in the cell\n// size and in its offset, so the offsets that keep it inside are, per axis, a range at each probed\n// cell; the one kept is the widget's own offset clamped into the intersection of those ranges, in\n// quarter cells. When no quarter-cell offset fits every cell size (a box as wide as the canvas\n// whose `self` point differs from its anchor), the widget is put on the grid at that breakpoint,\n// in the first free slot, at its own size: the one placement that is always on the canvas.\nfunction fitRelativeOnCanvas(def: DashboardDefinition, id: string, bp: string): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'relative') return def;\n const columns = breakpointColumns(def.canvas, bp);\n const gutter = gutterPx(def.canvas.grid);\n const { offset, ...bare } = p;\n let loX = -Infinity;\n let hiX = Infinity;\n let loY = -Infinity;\n for (const cell of RELATIVE_PROBE_CELLS(def.canvas.grid.minCellSize)) {\n const host = layoutRects(def.widgets, bp, cell, gutter).get(p.host);\n if (!host) return def;\n const r = relativeRect(bare, host, cell, gutter);\n const width = spanPx(columns, cell, gutter.x);\n loX = Math.max(loX, -r.x / (cell + gutter.x));\n hiX = Math.min(hiX, (width - r.x - r.w) / (cell + gutter.x));\n loY = Math.max(loY, -r.y / (cell + gutter.y));\n }\n const step = OFFSET_STEP_FINE;\n const up = (v: number) => Math.ceil(v / step - 1e-9) * step;\n const down = (v: number) => Math.floor(v / step + 1e-9) * step;\n let placement: Placement;\n if (up(loX) <= down(hiX)) {\n const x = Math.min(Math.max(offset?.x ?? 0, up(loX)), down(hiX)) || 0;\n const y = Math.max(offset?.y ?? 0, up(loY)) || 0;\n placement = x === 0 && y === 0 ? bare : { ...bare, offset: { x, y } };\n } else {\n const size = 'fill' in p.size ? hostSpan(p, def, bp) : p.size;\n const cols = Math.min(size.cols, columns);\n const occupied = def.widgets\n .filter((o) => o.id !== id)\n .map((o) => resolvePlacement(o.layout, bp))\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n placement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n }\n return {\n ...def,\n widgets: def.widgets.map((x) => (x.id === id ? { ...x, layout: { ...x.layout, [bp]: placement } } : x)),\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The editor's undo/redo history: a bounded stack of SNAPSHOTS over the pure editor-model\n// transforms. Every state it holds is one the working copy really had, so undo restores a\n// definition exactly (never a recomputed approximation) and can only ever bring back a state that\n// was on screen before, including one the publish gate blocks only if the author really had it.\n//\n// Generic over the state and the label, and free of React and the DOM: the console wires it to\n// its working copy and keyboard. Kept in its own module, outside anything the viewer imports.\n//\n// - `past` holds, newest last, the state BEFORE each edit and the edit's label; `future` holds,\n// newest last, the state an undo left and the label of the edit undone.\n// - An edit committed with a `key` equal to the one still open (the previous commit's) merges\n// into that entry: typing into one field is one step. Undo, redo and `sealHistory` close the\n// key, so the next commit opens a new entry even for the same field.\n// - The depth is bounded; the oldest entries fall off.\n\nexport const DEFAULT_HISTORY_LIMIT = 100;\n\nexport interface HistoryEntry<T, L> {\n state: T;\n label: L;\n}\n\nexport interface EditHistory<T, L> {\n readonly past: readonly HistoryEntry<T, L>[];\n readonly present: T;\n readonly future: readonly HistoryEntry<T, L>[];\n // The coalescing key of the last commit, while it may still be extended.\n readonly open?: string;\n readonly limit: number;\n}\n\nexport function createEditHistory<T, L = string>(present: T, options: { limit?: number } = {}): EditHistory<T, L> {\n return { past: [], present, future: [], limit: Math.max(1, Math.floor(options.limit ?? DEFAULT_HISTORY_LIMIT)) };\n}\n\nexport interface CommitOptions<T> {\n // Commits with the same key, one after another, are one entry.\n key?: string;\n // When two states are the same edit-wise. Defaults to identity.\n equals?: (a: T, b: T) => boolean;\n}\n\n// commitEdit records `next` as the new present. A commit that changes nothing returns the history\n// unchanged; a new edit drops whatever could have been redone.\nexport function commitEdit<T, L>(\n h: EditHistory<T, L>,\n next: T,\n label: L,\n options: CommitOptions<T> = {},\n): EditHistory<T, L> {\n const equals = options.equals ?? Object.is;\n if (next === h.present || equals(next, h.present)) return h;\n const last = h.past[h.past.length - 1];\n if (options.key !== undefined && options.key === h.open && last) {\n // Typed back to where the entry started: the entry is no edit at all. The key closes with it,\n // or the next keystroke would merge into the unrelated entry beneath.\n if (equals(next, last.state)) return { past: h.past.slice(0, -1), present: next, future: [], limit: h.limit };\n return { ...h, present: next, future: [] };\n }\n const past = [...h.past, { state: h.present, label }];\n if (past.length > h.limit) past.splice(0, past.length - h.limit);\n return { past, present: next, future: [], open: options.key, limit: h.limit };\n}\n\n// sealHistory closes the open key: the next commit is a new entry whatever its key.\nexport function sealHistory<T, L>(h: EditHistory<T, L>): EditHistory<T, L> {\n if (h.open === undefined) return h;\n const { open: _closed, ...rest } = h;\n return rest;\n}\n\nexport interface Step<T, L> {\n history: EditHistory<T, L>;\n // The edit that was undone or redone; undefined when there was none.\n label?: L;\n}\n\nexport function undoEdit<T, L>(h: EditHistory<T, L>): Step<T, L> {\n const last = h.past[h.past.length - 1];\n if (!last) return { history: h };\n return {\n history: {\n past: h.past.slice(0, -1),\n present: last.state,\n future: [...h.future, { state: h.present, label: last.label }],\n limit: h.limit,\n },\n label: last.label,\n };\n}\n\nexport function redoEdit<T, L>(h: EditHistory<T, L>): Step<T, L> {\n const next = h.future[h.future.length - 1];\n if (!next) return { history: h };\n return {\n history: {\n past: [...h.past, { state: h.present, label: next.label }],\n present: next.state,\n future: h.future.slice(0, -1),\n limit: h.limit,\n },\n label: next.label,\n };\n}\n\nexport const canUndo = (h: EditHistory<unknown, unknown>): boolean => h.past.length > 0;\nexport const canRedo = (h: EditHistory<unknown, unknown>): boolean => h.future.length > 0;\n\n// rebaseHistory applies a change that is not an edit (a save retiring the \"converted\" mark) to\n// every state the history holds, so neither undo nor redo can bring the old form back.\nexport function rebaseHistory<T, L>(h: EditHistory<T, L>, fn: (state: T) => T): EditHistory<T, L> {\n const map = (e: HistoryEntry<T, L>) => ({ ...e, state: fn(e.state) });\n return { ...h, past: h.past.map(map), present: fn(h.present), future: h.future.map(map) };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Duplicate, copy and paste for the canvas editor: pure transforms over a definition, free of\n// React and the DOM (the console owns the keyboard and the system clipboard). Kept in its own\n// module, outside anything the viewer imports.\n//\n// What a copy carries is the widget as a STORED widget, so a paste reads it back through the same\n// parser a load does, against the pasting console's widget registry: a type that console does not\n// have, or a release it cannot run, is refused and named rather than pasted as something else.\n//\n// Decisions a reader needs:\n// - A pasted or duplicated widget gets a new id and lands at its own span (no wider than the\n// grid) in the first free slot, scanning rows, exactly as an added widget does.\n// - A RELATIVE widget is copied ONTO THE GRID, at its own size (a `fill` one at its host's\n// span). Kept relative, a duplicate would sit exactly on top of the original, invisible, and\n// a paste on another board would name a host that is not there. The author re-hosts it with\n// one click. Only the base placement is copied; per-breakpoint entries are left behind.\n// - A slot binding is KEPT when the target board has the slot: on the same board, a slot of that\n// name and kind; on another board, a slot of that name, kind AND default binding (slot names\n// are minted `slot-N` per board, so the same name on two boards usually means two different\n// devices, and following the name would silently show the wrong one). Otherwise the widget is\n// pasted UNBOUND: it points at a new slot of the same kind with nothing bound to it, and the\n// paste reports it, so the editor can say why. Never dropped to \"no datasource\": to an alarm\n// widget an absent datasource means the whole tenant; an unbound slot means no device yet.\n// - \uD83D\uDD34 A copy is stamped with the TENANT it was made in, and a paste into any other tenant keeps\n// NO entity reference at all. Device and entity tokens are only unique within a tenant, so a\n// binding carried across would quietly show the same-named device of the other tenant (or\n// publish the first tenant's identifiers on a board the second tenant's users can read). One\n// browser tab can change tenant, and its clipboard outlives the change, so this is decided at\n// paste time, from the stamp, never assumed. Every slot reference, and every concrete device or\n// entity selector, becomes a new unbound slot with no label (a slot's label is often the token\n// it was bound to), and the board stamp is ignored: board tokens are per tenant too.\n\nimport { CURRENT_SCHEMA_VERSION } from './types';\nimport { BASE_BREAKPOINT, generateWidgetId, parseDashboardDefinition, storedDefinition, type ParseDefinitionOptions } from './definition';\nimport { basePlacement, hostSpan, resolveEverywhere } from './editor-model';\nimport { firstFreeSlot } from './grid';\nimport { nextSlotName, sameBinding } from './slots';\nimport {\n UNKNOWN_WIDGET_TYPE,\n type Canvas,\n type DashboardDefinition,\n type GridPlacement,\n type ResolvedWidgetInstance,\n type SlotDefinition,\n type WidgetInstance,\n} from './types';\n\nexport const WIDGET_CLIPBOARD_FORMAT = 'devicechain/dashboard-widgets';\nexport const WIDGET_CLIPBOARD_VERSION = 1;\n\n// Where a copy was made, or where a paste lands: the tenant and the board token within it.\nexport interface ClipboardOrigin {\n tenant: string;\n board: string;\n}\n\n// What a copy holds: the widgets (on the grid, ids as they were), the slots they reference as the\n// source board defined them, the tenant and board they came from and the canvas they were authored on.\nexport interface WidgetClipboard {\n tenant: string;\n board: string;\n canvas: Canvas;\n widgets: ResolvedWidgetInstance[];\n slots: Record<string, SlotDefinition>;\n}\n\n// A widget a paste could not take: its type and why.\nexport interface RefusedWidget {\n type: string;\n reason: string;\n}\n\n// A pasted widget that lost its binding: it now points at `slot`, a new slot with nothing bound\n// to it, because the board has no slot `from` (`missing`), has one bound to something else, or\n// of the other kind (`different`), or the copy was made in another tenant (`tenant`: `from` is\n// then the source slot's name, or empty for a widget that named its device directly).\nexport interface UnboundPaste {\n widgetId: string;\n slot: string;\n from: string;\n reason: 'missing' | 'different' | 'tenant';\n}\n\nexport interface PasteResult {\n definition: DashboardDefinition;\n ids: string[];\n unbound: UnboundPaste[];\n}\n\n// The slot names a widget references: the one its datasource binds, and the one a selector widget\n// re-points (`options.selectionTarget`).\nfunction slotReferences(w: WidgetInstance): string[] {\n const out: string[] = [];\n if (w.datasource?.kind === 'slot' && w.datasource.slot) out.push(w.datasource.slot);\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target !== '' && !out.includes(target)) out.push(target);\n return out;\n}\n\nconst own = <V>(map: Record<string, V> | undefined, key: string): V | undefined =>\n map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n\n// copyWidget takes widget `id` of `def` (on `origin`'s board) into a clipboard. A placeholder for a\n// widget this console cannot show is not copied: it carries no settings this build can vouch for.\nexport function copyWidget(def: DashboardDefinition, id: string, origin: ClipboardOrigin): WidgetClipboard | undefined {\n const w = def.widgets.find((x) => x.id === id);\n if (!w || w.type === UNKNOWN_WIDGET_TYPE) return undefined;\n const p = basePlacement(w);\n const size = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : 'fill' in p.size ? hostSpan(p, def, BASE_BREAKPOINT) : p.size;\n const placement: GridPlacement = { mode: 'grid', col: 0, row: 0, ...size };\n const widget = structuredClone({ ...w, layout: { [BASE_BREAKPOINT]: placement } }) as ResolvedWidgetInstance;\n const slots: Record<string, SlotDefinition> = {};\n for (const name of slotReferences(w)) {\n const slot = own(def.slots, name);\n if (slot) slots[name] = structuredClone(slot);\n }\n return { tenant: origin.tenant, board: origin.board, canvas: structuredClone(def.canvas), widgets: [widget], slots };\n}\n\n// serializeWidgetClipboard is the text a copy puts on the system clipboard.\nexport function serializeWidgetClipboard(clip: WidgetClipboard): string {\n const definition = storedDefinition({\n schemaVersion: CURRENT_SCHEMA_VERSION,\n title: '',\n canvas: clip.canvas,\n widgets: clip.widgets,\n ...(Object.keys(clip.slots).length > 0 ? { slots: clip.slots } : {}),\n });\n return JSON.stringify({\n format: WIDGET_CLIPBOARD_FORMAT,\n version: WIDGET_CLIPBOARD_VERSION,\n tenant: clip.tenant,\n board: clip.board,\n definition,\n });\n}\n\n// parseWidgetClipboard reads clipboard text back, validating every widget against the registry\n// `options.catalog` names (the built-ins without one). Text that is not a widget clipboard, of\n// another version, or whose definition does not parse, is undefined: the caller leaves the paste\n// alone, and so is text with no tenant stamp: a paste must know where a copy came from. A widget\n// the registry cannot resolve is refused and named.\n//\n// \uD83D\uDD34 One exception to \"undefined\": a copy made by a NEWER release (its definition's schemaVersion\n// above this build's) THROWS the parser's DashboardDefinitionError, code 'schemaTooNew', exactly as\n// a load of that board would. It is a widget clipboard, so \"nothing to paste\" would be a lie, and\n// reading it as the current format would paste it drawn wrong; the caller says why it refused.\nexport function parseWidgetClipboard(\n text: string,\n options: ParseDefinitionOptions = {},\n): { clipboard: WidgetClipboard; refused: RefusedWidget[] } | undefined {\n let env: unknown;\n try {\n env = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (typeof env !== 'object' || env === null) return undefined;\n const { format, version, tenant, board, definition } = env as Record<string, unknown>;\n if (format !== WIDGET_CLIPBOARD_FORMAT || version !== WIDGET_CLIPBOARD_VERSION) return undefined;\n if (typeof tenant !== 'string' || tenant === '' || typeof board !== 'string') return undefined;\n let parsed: DashboardDefinition;\n try {\n parsed = parseDashboardDefinition(definition, options);\n } catch (err) {\n // By `code`, not instanceof: it holds across two bundled copies of this package.\n if ((err as { code?: unknown } | null)?.code === 'schemaTooNew') throw err;\n return undefined;\n }\n const widgets: ResolvedWidgetInstance[] = [];\n const refused: RefusedWidget[] = [];\n for (const w of parsed.widgets) {\n if (w.type !== UNKNOWN_WIDGET_TYPE) {\n widgets.push(w);\n continue;\n }\n const type = typeof w.raw.type === 'string' ? w.raw.type : '';\n refused.push({ type, reason: w.reason?.message ?? '' });\n }\n return { clipboard: { tenant, board, canvas: parsed.canvas, widgets, slots: parsed.slots ?? {} }, refused };\n}\n\n// A selector that names its entity directly rather than through a slot.\nconst CONCRETE_KINDS: ReadonlySet<string> = new Set(['device', 'anchor', 'devices', 'relatedTraversal']);\n\n// pasteWidgets adds the clipboard's widgets to `def`, the board `at`: each with a new id, in the\n// first free slot at its span, its slot references kept or unbound as described above.\nexport function pasteWidgets(def: DashboardDefinition, clip: WidgetClipboard, at: ClipboardOrigin): PasteResult {\n // An empty stamp is never a match: a paste that cannot tell where a copy came from treats it as\n // another tenant's.\n const sameTenant = clip.tenant !== '' && clip.tenant === at.tenant;\n const sameBoard = sameTenant && clip.board === at.board;\n const columns = def.canvas.grid.columns;\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n // One decision per source slot, so widgets pasted together that shared a slot still share one.\n const mapped = new Map<string, { slot: string; reason?: UnboundPaste['reason'] }>();\n const mapSlot = (from: string) => {\n const known = mapped.get(from);\n if (known) return known;\n const source = own(clip.slots, from);\n const target = own(slots, from);\n const keep =\n sameTenant &&\n target !== undefined &&\n source !== undefined &&\n target.type === source.type &&\n (sameBoard || sameBinding(target.defaultBinding, source.defaultBinding));\n let result: { slot: string; reason?: UnboundPaste['reason'] };\n if (keep) result = { slot: from };\n else {\n const slot = nextSlotName(slots);\n // Another tenant's label is not carried: it is often the token the slot was bound to.\n const label = sameTenant ? source?.label : undefined;\n slots[slot] = { type: source?.type ?? 'device', ...(label !== undefined ? { label } : {}) };\n result = { slot, reason: !sameTenant ? 'tenant' : target ? 'different' : 'missing' };\n }\n mapped.set(from, result);\n return result;\n };\n\n const occupied = def.widgets.map(basePlacement).filter((p): p is GridPlacement => p.mode === 'grid');\n const added: WidgetInstance[] = [];\n const ids: string[] = [];\n const unbound: UnboundPaste[] = [];\n for (const source of clip.widgets) {\n const id = generateWidgetId();\n const p = basePlacement(source);\n const span = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : 'fill' in p.size ? { cols: 1, rows: 1 } : p.size;\n const cols = Math.min(span.cols, columns);\n const slot = firstFreeSlot(occupied, cols, span.rows, columns);\n const placement: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: span.rows };\n occupied.push(placement);\n const w = structuredClone(source) as ResolvedWidgetInstance;\n w.id = id;\n w.layout = { [BASE_BREAKPOINT]: placement };\n for (const from of slotReferences(source)) {\n const m = mapSlot(from);\n if (m.reason) unbound.push({ widgetId: id, slot: m.slot, from, reason: m.reason });\n if (m.slot === from) continue;\n if (w.datasource?.kind === 'slot' && w.datasource.slot === from) w.datasource = { ...w.datasource, slot: m.slot };\n if (w.options?.selectionTarget === from) w.options = { ...w.options, selectionTarget: m.slot };\n }\n // A widget from another tenant that names its device or entity directly keeps the series it\n // reads and loses the entity: it points at a new unbound slot of the matching kind.\n const ds = w.datasource;\n if (!sameTenant && ds && CONCRETE_KINDS.has(ds.kind) && ds.kind !== 'slot' && ds.kind !== 'unbound') {\n const slot = nextSlotName(slots);\n slots[slot] = { type: ds.kind === 'anchor' ? 'anchor' : 'device' };\n w.datasource = {\n kind: 'slot',\n slot,\n measurements: [...ds.measurements],\n ...(ds.location ? { location: { ...ds.location } } : {}),\n };\n unbound.push({ widgetId: id, slot, from: '', reason: 'tenant' });\n }\n added.push(w);\n ids.push(id);\n }\n const next: DashboardDefinition = { ...def, widgets: [...def.widgets, ...added] };\n if (Object.keys(slots).length > 0) next.slots = slots;\n return { definition: resolveEverywhere(next, ids), ids, unbound };\n}\n\n// duplicateWidget pastes a copy of widget `id` onto its own board. Undefined for a widget that\n// cannot be copied (missing, or a placeholder).\nexport function duplicateWidget(\n def: DashboardDefinition,\n id: string,\n): { definition: DashboardDefinition; id: string; unbound: UnboundPaste[] } | undefined {\n // The widget's own board, in its own tenant.\n const here: ClipboardOrigin = { tenant: 'here', board: 'here' };\n const clip = copyWidget(def, id, here);\n if (!clip) return undefined;\n const r = pasteWidgets(def, clip, here);\n return { definition: r.definition, id: r.ids[0], unbound: r.unbound };\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 a CUSTOMER anchor reads customerDevices(customer, relationship); an area\n// or asset anchor reads anchorDevices(targetType, target), each row being a\n// member device (the Hub opens one measurementStream per token, per ADR-044).\n//\n// \uD83D\uDD11 ONE PATH PER ANCHOR KIND, WHOEVER IS VIEWING. The relationship graph is closed to a member\n// limited to some customers (a customer user), so an anchor read through it showed that member\n// nothing. customerDevices and anchorDevices answer that member about the devices it can see, and\n// for every other viewer they answer exactly what the relationship search does, so there is no\n// viewer-dependent branch here beyond one: anchorDevices refuses a relationship type from a scoped\n// caller, which readAreaOrAssetPage retries without. An anchor the platform still declines\n// (FORBIDDEN, reason SCOPE_UNSUPPORTED) is reported by the hub as an unsupported binding\n// (isDatasourceSupported) rather than as empty.\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, isScopeUnsupportedError } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport type { MemberRef } from './members';\nimport {\n ANCHOR_DEVICES,\n CUSTOMER_DEVICES,\n DECLARED_METRICS,\n DEVICES_BY_TOKEN,\n DEVICE_NAMES,\n type AnchorDevicesResult,\n type CustomerDevicesResult,\n type DeclaredMetricsResult,\n type DeviceNamesResult,\n type DevicesByTokenResult,\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.\nexport const ANCHOR_PAGE_SIZE = 500;\n\n// One page of an anchor's members: their tokens, the anchor's true size, their names and their\n// device type tokens (both anchor reads carry them, so the roster needs no second read).\ninterface AnchorPage {\n tokens: string[];\n total: number;\n names: Map<string, string | null>;\n types: Map<string, string | null>;\n}\n\n// The most device types one declaration read asks for.\nconst DECLARED_BATCH_SIZE = 100;\n\n// A server that reports no total is taken at its word: the page is the fleet.\nconst trueSize = (reported: number | null | undefined, page: number) => Math.max(reported ?? page, page);\n\nfunction readAnchorPage(anchor: AnchorTarget): Promise<AnchorPage> {\n if (anchor.targetType === 'customer') {\n return gql('device-management', CUSTOMER_DEVICES, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n customer: anchor.targetToken,\n relationshipType: anchor.relationship,\n },\n }).then((r: CustomerDevicesResult) => {\n const rows = r.customerDevices.results;\n return {\n tokens: rows.map((d) => d.token),\n total: trueSize(r.customerDevices.pagination?.totalRecords, rows.length),\n names: new Map(rows.map((d) => [d.token, d.name])),\n types: new Map(rows.map((d) => [d.token, d.deviceType?.token ?? null])),\n };\n });\n }\n return readAreaOrAssetPage(anchor);\n}\n\n// An area or an asset: anchorDevices, which answers every viewer. The relationship type is sent\n// first, so an unscoped viewer gets exactly the devices linked by that type, as the relationship\n// search it replaces did. A member limited to some customers is refused the argument (it would let\n// them probe which edge types link their devices) and is answered the tracked ownership edges\n// instead, so that refusal is retried once without it. A second SCOPE_UNSUPPORTED is the platform\n// declining the anchor outright and surfaces as it always has.\nfunction readAreaOrAssetPage(anchor: AnchorTarget): Promise<AnchorPage> {\n const read = (relationshipType?: string) =>\n gql('device-management', ANCHOR_DEVICES, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n targetType: anchor.targetType,\n target: anchor.targetToken,\n relationshipType,\n },\n });\n return read(anchor.relationship)\n .catch((err: unknown) => {\n if (!isScopeUnsupportedError(err)) throw err;\n return read();\n })\n .then((r: AnchorDevicesResult) => {\n const rows = r.anchorDevices.results;\n return {\n tokens: rows.map((d) => d.token),\n total: trueSize(r.anchorDevices.pagination?.totalRecords, rows.length),\n names: new Map(rows.map((d) => [d.token, d.name])),\n types: new Map(rows.map((d) => [d.token, d.deviceType?.token ?? null])),\n };\n });\n}\n\nexport function createDeviceResolver(): DeviceResolver {\n const anchorCache = new Map<string, Promise<AnchorPage>>();\n const memberCache = new Map<string, Promise<{ members: MemberRef[]; total: number }>>();\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 const typeCache = new Map<string, Promise<string | null>>();\n\n // The device types asked for declarations in the current tick, and who is waiting on each.\n type DeclaredWaiter = { resolve: (names: string[] | null) => void; reject: (err: unknown) => void };\n let declaredBatch: Map<string, DeclaredWaiter[]> | null = null;\n\n function anchorKey(anchor: AnchorTarget): string {\n return `${anchor.relationship}|${anchor.targetType}|${anchor.targetToken}`;\n }\n\n // One page of the anchor's members and the anchor's true size, memoized per anchor.\n function anchorPage(anchor: AnchorTarget): Promise<AnchorPage> {\n const key = anchorKey(anchor);\n let pending = anchorCache.get(key);\n if (!pending) {\n pending = readAnchorPage(anchor)\n .catch((err) => {\n anchorCache.delete(key);\n throw err;\n });\n anchorCache.set(key, pending);\n }\n return pending;\n }\n\n return {\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]> {\n return anchorPage(anchor).then((page) => page.tokens);\n },\n\n // The members of an anchor with their names and the anchor's true size: ONE page, shared with\n // devicesForAnchor, which carries every member's name and device type.\n anchorMembers(anchor: AnchorTarget): Promise<{ members: MemberRef[]; total: number }> {\n const key = anchorKey(anchor);\n let pending = memberCache.get(key);\n if (!pending) {\n pending = anchorPage(anchor)\n .then(({ tokens, total, names, types }) => ({\n members: tokens.map((token) => ({ token, name: names.get(token) ?? null, deviceType: types.get(token) ?? null })),\n total,\n }))\n .catch((err) => {\n memberCache.delete(key);\n throw err;\n });\n memberCache.set(key, pending);\n }\n return pending;\n },\n\n // The names a device type's active profile version declares, or null when it has nothing to\n // say: no profile, never published, not found, or a published version that declares no metric\n // at all (an undeclared measurement is still accepted, so an empty declaration rules nothing\n // out). A failed read rejects: the caller treats that as unknown too, and does not cache it.\n // Every type asked in the same tick goes in ONE read, so a board of many types costs one\n // request rather than one per type.\n declaredMetrics(deviceType: string): Promise<string[] | null> {\n return new Promise((resolve, reject) => {\n let batch = declaredBatch;\n if (!batch) {\n batch = declaredBatch = new Map();\n queueMicrotask(() => {\n const asked = declaredBatch!;\n declaredBatch = null;\n const tokens = [...asked.keys()];\n for (let i = 0; i < tokens.length; i += DECLARED_BATCH_SIZE) {\n const chunk = tokens.slice(i, i + DECLARED_BATCH_SIZE);\n gql('device-management', DECLARED_METRICS, { tokens: chunk }).then(\n (r: DeclaredMetricsResult) => {\n for (const token of chunk) {\n const version = r.deviceTypesByToken.find((t) => t.token === token)?.profile?.activeProfileVersion;\n const names = version && version.metrics.length > 0 ? version.metrics.map((m) => m.metricKey) : null;\n for (const w of asked.get(token)!) w.resolve(names);\n }\n },\n (err: unknown) => {\n for (const token of chunk) for (const w of asked.get(token)!) w.reject(err);\n },\n );\n }\n });\n }\n const waiting = batch.get(deviceType) ?? [];\n waiting.push({ resolve, reject });\n batch.set(deviceType, waiting);\n });\n },\n\n // The device type's token, from the same names read the roster uses. A failed read rejects (the\n // caller treats that as unknown) and is not cached.\n deviceType(deviceToken: string): Promise<string | null> {\n let pending = typeCache.get(deviceToken);\n if (!pending) {\n pending = gql('device-management', DEVICE_NAMES, { tokens: [deviceToken] })\n .then((r: DeviceNamesResult) => r.devicesByToken.find((d) => d.token === deviceToken)?.deviceType?.token ?? null)\n .catch((err) => {\n typeCache.delete(deviceToken);\n throw err;\n });\n typeCache.set(deviceToken, 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 { DEFAULT_TIME_RANGE_SECONDS, bucketSecondsFor, rangeWindow } from './time-range';\nimport type { MeasurementSample, WidgetInstance } from './types';\n\nexport interface HistoryWindow {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n}\n\n// historyWindowFor is the seed window for a span: `spanSeconds` back from `end` (epoch ms),\n// bucketed at the width bucketSecondsFor picks so the read stays within MAX_SEED_BUCKETS per\n// measurement whatever the span (7 days is 240 buckets, not 10 080 minutes). `end` is the\n// moment the board is frozen at while paused, otherwise now.\nexport function historyWindowFor(spanSeconds: number, end: number): HistoryWindow {\n const { startTime, endTime } = rangeWindow({ spanSeconds, paused: false }, end);\n return { startTime, endTime, intervalSeconds: bucketSecondsFor(spanSeconds) };\n}\n\n// The default backfill: the last hour bucketed per minute (~60 points), enough to\n// give a chart shape on load without a heavy query. What a widget that is not time-ranged\n// (a card, a gauge: they need the latest value, not a span) is seeded with.\nexport function defaultHistoryWindow(): HistoryWindow {\n return historyWindowFor(DEFAULT_TIME_RANGE_SECONDS, Date.now());\n}\n\n// fetchWidgetHistory returns seed samples for one widget, or [] when it has no\n// device datasource (label/image, an anchor selector, an unbound slot). Never rejects\n// into the render path \u2014 a failed backfill just yields an empty seed and the live stream\n// still fills the widget.\n//\n// It takes the widget with its CONCRETE datasource \u2014 a slot already resolved through the\n// bindings (resolveWidgetDatasource), as the renderer passes it \u2014 so the seed is for\n// exactly the device the widget's live stream is for. A raw slot seeds nothing.\n//\n// `rethrow` makes a failed read reject instead. A caller REPLACING a seed it already shows (the\n// re-read after a live feed was interrupted) must not mistake a failure for \"no history\": an empty\n// answer would wipe the history on screen and claim the outage held no readings.\nexport async function fetchWidgetHistory(\n widget: WidgetInstance,\n window: HistoryWindow,\n options: { rethrow?: boolean } = {},\n): Promise<MeasurementSample[]> {\n const ds = widget.datasource;\n if (!ds || ds.kind !== 'device') return [];\n\n try {\n // measurementStream and bucketedMeasurements are keyed by device token (ADR-044),\n // so the token goes straight into the criteria \u2014 no token\u2192id hop.\n const deviceToken = ds.deviceToken;\n\n // Seed each requested measurement (or all, when the widget lists none).\n const names: Array<string | undefined> = ds.measurements.length ? ds.measurements : [undefined];\n const pages = await Promise.all(\n names.map((name) =>\n gql('event-management', BUCKETED_MEASUREMENTS, {\n criteria: {\n deviceToken,\n name,\n startTime: window.startTime,\n endTime: window.endTime,\n intervalSeconds: window.intervalSeconds,\n },\n }).then((r) => r.bucketedMeasurements),\n ),\n );\n\n return pages\n .flat()\n .filter((b) => b.avg != null)\n .map((b) => ({\n id: `${deviceToken}-${b.name}-${b.bucketStart}`,\n deviceToken,\n eventType: 0,\n occurredTime: b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n bucketSeconds: window.intervalSeconds,\n }))\n .sort((a, b) => (a.occurredTime! < b.occurredTime! ? -1 : 1));\n } catch (err) {\n if (options.rethrow) throw err;\n return [];\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The option-schema vocabulary and the engine that checks an options bag against one.\n//\n// A widget's stored `options` is `Record<string, unknown>`: dashboard-management keeps a\n// definition as opaque JSON and the definition parser does not look inside it. That leaves\n// every option key owned by NOTHING unless something declares it, and this module is the\n// language the declaration is written in. It is React-free and knows no widget type, so a\n// headless validator, the widget SDK and the built-in widget package all read one engine \u2014\n// the built-in table (WIDGET_OPTIONS in @devicechain/widgets) is just one schema among the\n// ones a registry can hold.\n//\n// The schema is JSON-serializable data. Rules that span more than one option are therefore\n// declared as `constraints` (data), never as functions.\n\n// ---- Spec vocabulary --------------------------------------------------------\n\n// `required` marks an option whose ABSENCE leaves the widget with nothing to show.\n// The renderer never throws on a missing option \u2014 an image with no `url` renders \"No\n// image URL\", a command-button with no `commandName` renders \"No command configured\" \u2014\n// which is deliberate graceful degradation for a hand-edited definition, but is exactly\n// the failure a generated dashboard must not ship. So `required` is an AUTHORING\n// completeness contract, not a claim that the renderer rejects the option's absence.\n//\n// Absent here means absent AS THE RENDERER SEES IT, which for a required string includes\n// the EMPTY string: every one of the four required built-in options is read into a\n// truthiness test (`url ? \u2026 : 'No image URL'`, `!commandName`, `!target`), so `''`\n// produces the same placeholder as an omitted key. Treating `''` as present would let a\n// generated dashboard carrying `\"url\": \"\"` validate clean and render nothing.\ninterface OptionSpecBase {\n // What the option does. Short: this feeds the config panel's help text and the\n // published \"available widgets\" page. Required, and present on every descriptor a package\n // publishes. The one exception is not a type: @devicechain/widgets's render-only definitions\n // (`builtinWidgetDefinitionsWithoutDocs`) omit it so a host that only renders boards never\n // downloads prose it never shows, which is why the validator excuses a BUILT-IN id's missing doc.\n doc: string;\n required?: true;\n // Portable default text for the option's label and help. A host that cannot translate\n // an option it has never heard of falls back to these, so a third-party option is\n // understandable without a host message catalog. Optional so existing schemas stay valid.\n label?: string;\n help?: string;\n // Message ids a host MAY look up in its own catalog before falling back to the text\n // above. Built-ins declare today's console keys here; the host resolves them.\n labelId?: string;\n helpId?: string;\n // Names a SPECIALIZED authoring workflow for this option, chosen by the descriptor and never\n // inferred from the option's key. Absent means the generic field the spec kind implies.\n //\n // OPEN vocabulary, deliberately: any non-empty string registers, and a host that does not\n // implement the named workflow renders the generic field for that option. A closed list\n // here would make every package that names a newer control unregistrable on an older\n // host. Name a package's own controls in a namespace ('acme/valve-curve'); the built-in\n // names below are unprefixed.\n control?: string;\n}\n\n// The specialized workflows the DeviceChain console implements \u2014 what a built-in may name\n// and expect honoured. Not a validation whitelist: a control outside this list is valid and\n// simply falls back. `command-vocabulary` picks a device command and bakes its key, label and\n// parameters together; `slot-target` picks one of the dashboard's slots; `basemap` marks the\n// raster tile source/credit pair; `device-type` picks a device type from the tenant's catalogue and\n// stores its token (on a string option, or a string field of a list item).\nexport const HOST_OPTION_CONTROLS = ['command-vocabulary', 'slot-target', 'basemap', 'device-type'] as const;\nexport type HostOptionControl = (typeof HOST_OPTION_CONTROLS)[number];\n\nexport interface StringOptionSpec extends OptionSpecBase {\n kind: 'string';\n // 'json' additionally requires the string to parse as JSON. Used for the command\n // button's baked parameterSchema, where a malformed schema is silently swallowed\n // (parseParameterSchema returns []) and renders as a bare Send button.\n format?: 'json';\n}\n\nexport interface NumberOptionSpec extends OptionSpecBase {\n kind: 'number';\n min?: number;\n max?: number;\n integer?: true;\n}\n\nexport interface BooleanOptionSpec extends OptionSpecBase {\n kind: 'boolean';\n}\n\nexport interface EnumOptionSpec<V extends string = string> extends OptionSpecBase {\n kind: 'enum';\n values: readonly V[];\n // Portable text and optional host message ids for the choices and for the \"unset\" entry,\n // by the same lookup-then-fall-back rule as `label`. Absent, a choice shows its raw value.\n valueLabels?: Readonly<Record<string, string>>;\n valueLabelIds?: Readonly<Record<string, string>>;\n placeholder?: string;\n placeholderId?: string;\n}\n\n// The scalar kinds a list item's fields may take. Not `list` itself: nesting is not offered,\n// so every option stays authorable by one flat editor row.\nexport type ListItemFieldSpec = StringOptionSpec | NumberOptionSpec | BooleanOptionSpec | EnumOptionSpec;\n\n// An ordered list of small records, e.g. thresholds: `[{ value, level, label? }]`. Each item is\n// an object whose fields are declared once in `item`; a field marked `required` must be present\n// in every item, and a key the item does not declare is refused (a free `color` next to a\n// severity `level` is exactly what this exists to turn away).\nexport interface ListOptionSpec extends OptionSpecBase {\n kind: 'list';\n item: Readonly<Record<string, ListItemFieldSpec>>;\n maxItems?: number;\n // The ORDER of the entries means something (tab order, say), so an editor offers move up /\n // move down on each. Absent or false, a list is a set the editor need not let the author reorder.\n ordered?: boolean;\n}\n\nexport type OptionSpec = StringOptionSpec | NumberOptionSpec | BooleanOptionSpec | EnumOptionSpec | ListOptionSpec;\n\nexport type WidgetOptionSpecs = Record<string, OptionSpec>;\n\n// A rule spanning more than one option, as DATA. `greater-than` requires the value of\n// `key` to be strictly greater than the value of `than`, and is only judged when both are\n// finite numbers (an absent or mistyped value is reported by its own spec, not twice).\nexport interface GreaterThanConstraint<K extends string = string> {\n kind: 'greater-than';\n key: K;\n than: K;\n}\n\nexport type OptionConstraint<K extends string = string> = GreaterThanConstraint<K>;\n\n// ---- Derived compile-time types ---------------------------------------------\n\n// The value type a spec accepts. Enum first: an EnumOptionSpec's values carry the literal\n// union, which is the whole point of declaring one.\nexport type SpecValue<S> = S extends { kind: 'list'; item: infer I }\n ? OptionsOf<I>[]\n : S extends { kind: 'enum'; values: readonly (infer V)[] }\n ? V extends string\n ? V\n : never\n : S extends { kind: 'number' }\n ? number\n : S extends { kind: 'boolean' }\n ? boolean\n : S extends { kind: 'string' }\n ? string\n : never;\n\nexport type RequiredOptionKeys<S> = {\n [K in keyof S]: S[K] extends { required: true } ? K : never;\n}[keyof S];\n\n// The typed options bag a schema describes: every declared key at its declared type,\n// required ones mandatory.\nexport type OptionsOf<S> = {\n [K in RequiredOptionKeys<S>]: SpecValue<S[K]>;\n} & {\n [K in Exclude<keyof S, RequiredOptionKeys<S>>]?: SpecValue<S[K]>;\n};\n\n// A descriptor's starting values: every declared key at its declared type, but EVERY key\n// optional. A fresh widget is allowed to be authoring-incomplete \u2014 an image has no `url`\n// until the author supplies one \u2014 so `required` cannot bind the defaults.\nexport type DefaultsOf<S> = { [K in keyof S]?: SpecValue<S[K]> };\n\n// The declared keys of a given kind, for typed references from a binding policy. The `infer`\n// makes the test distribute over a union of specs, so the broad WidgetOptionSpecs (every\n// key a union of the four kinds) still names its number keys instead of collapsing to never.\nexport type OptionKeysOfKind<S, Kind extends OptionSpec['kind']> = {\n [K in keyof S & string]: S[K] extends infer X ? (X extends { kind: Kind } ? K : never) : never;\n}[keyof S & string];\n\n// ---- Runtime validation -----------------------------------------------------\n\nexport type OptionIssueCode =\n // a key the renderer for this widget type reads nothing from\n | 'unknown'\n // present, but the wrong JavaScript type \u2014 read back as undefined, rendered as default\n | 'type'\n // a string/number outside the option's declared legal values or bounds\n | 'enum'\n | 'range'\n // a `required` option absent, so the widget renders its \"not configured\" placeholder\n | 'missing'\n // a format:'json' string that does not parse\n | 'json'\n // a rule spanning more than one option (see OptionConstraint)\n | 'invariant';\n\nexport interface OptionIssue {\n // The id of the widget whose schema the bag was checked against (a built-in type or a\n // namespaced extension id). NOT a widget instance id.\n widgetId: string;\n // The offending option key. For a constraint, the key the fix belongs on.\n key: string;\n code: OptionIssueCode;\n message: string;\n}\n\nexport interface ValidateOptionsContext {\n constraints?: readonly OptionConstraint[];\n // Names the widget in messages and on each issue.\n label: string;\n}\n\n// hasOwnProperty.call rather than `in` or Object.hasOwn: every specs table inherits\n// Object.prototype, so `in` reports 'toString', 'constructor' and friends as declared.\n// A stored definition arrives via JSON.parse, which makes '__proto__' an ordinary own key\n// \u2014 a shape a real definition can carry. (And the package targets ES2020.)\nexport function hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\n// validateOptions reports every way an options bag disagrees with its schema. An empty\n// result means every key present is declared, at a value the schema accepts, and no\n// required option is missing.\n//\n// It is deliberately a REPORT rather than a throw: the renderer is tolerant by design and\n// must stay that way for a hand-edited definition, so the strictness belongs to whoever\n// produces definitions (fixtures, generators, the config panel) and to the tests that\n// gate them. Callers decide which codes are fatal \u2014 a stored dashboard carrying an\n// 'unknown' leftover is untidy; a generated one carrying 'missing' is broken.\nexport function validateOptions(\n specs: WidgetOptionSpecs,\n options: Record<string, unknown> | undefined,\n context: ValidateOptionsContext,\n): OptionIssue[] {\n const { label: widgetId } = context;\n const issues: OptionIssue[] = [];\n const bag = options ?? {};\n\n for (const [key, spec] of Object.entries(specs)) {\n const value = bag[key];\n // '' counts as absent for a required option \u2014 see OptionSpecBase.\n if (value === undefined || (spec.required && value === '')) {\n if (spec.required) {\n issues.push({ widgetId, key, code: 'missing', message: `${key} is required but not set` });\n }\n continue;\n }\n issues.push(...checkValue(widgetId, key, spec, value));\n }\n\n for (const key of Object.keys(bag)) {\n if (hasOwn(specs, key)) continue;\n issues.push({\n widgetId,\n key,\n code: 'unknown',\n message: `${key} is not read by a ${widgetId} widget`,\n });\n }\n\n for (const constraint of context.constraints ?? []) {\n const issue = checkConstraint(constraint, bag);\n if (issue) issues.push({ widgetId, ...issue });\n }\n\n return issues;\n}\n\nfunction checkConstraint(\n constraint: OptionConstraint,\n bag: Record<string, unknown>,\n): Pick<OptionIssue, 'key' | 'code' | 'message'> | undefined {\n switch (constraint.kind) {\n case 'greater-than': {\n const value = bag[constraint.key];\n const than = bag[constraint.than];\n if (typeof value !== 'number' || typeof than !== 'number') return undefined;\n if (!Number.isFinite(value) || !Number.isFinite(than)) return undefined;\n if (value > than) return undefined;\n return {\n key: constraint.key,\n code: 'invariant',\n message: `${constraint.key} (${value}) must be greater than ${constraint.than} (${than})`,\n };\n }\n }\n}\n\nfunction checkValue(widgetId: string, key: string, spec: OptionSpec, value: unknown): OptionIssue[] {\n const issue = (code: OptionIssueCode, message: string): OptionIssue[] => [{ widgetId, key, code, message }];\n\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (spec.format === 'json' && !parsesAsJson(value)) {\n return issue('json', `${key} must be valid JSON`);\n }\n return [];\n\n case 'number':\n // Number.isFinite mirrors optNumber: NaN and \u00B1Infinity survive JSON round-trips\n // through some producers and are read back as undefined by the renderer.\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return issue('type', `${key} must be a finite number, got ${describe(value)}`);\n }\n if (spec.integer && !Number.isInteger(value)) {\n return issue('range', `${key} must be a whole number, got ${value}`);\n }\n if (spec.min !== undefined && value < spec.min) {\n return issue('range', `${key} must be at least ${spec.min}, got ${value}`);\n }\n if (spec.max !== undefined && value > spec.max) {\n return issue('range', `${key} must be at most ${spec.max}, got ${value}`);\n }\n return [];\n\n case 'boolean':\n // optBoolean treats anything but literal `true` as false, so a wrong type here is a\n // silently-disabled feature rather than a visible error.\n if (typeof value !== 'boolean') {\n return issue('type', `${key} must be a boolean, got ${describe(value)}`);\n }\n return [];\n\n case 'enum':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (!spec.values.includes(value)) {\n return issue('enum', `${key} must be one of ${spec.values.join(', ')}, got ${JSON.stringify(value)}`);\n }\n return [];\n\n case 'list': {\n if (!Array.isArray(value)) return issue('type', `${key} must be a list, got ${describe(value)}`);\n if (spec.maxItems !== undefined && value.length > spec.maxItems) {\n return issue('range', `${key} holds at most ${spec.maxItems} entries, got ${value.length}`);\n }\n const out: OptionIssue[] = [];\n value.forEach((entry, i) => {\n const at = `${key}[${i}]`;\n // Reported under the ENTRY's own key (`thresholds[0].level`), so the author is pointed at the cell.\n const at_ = (path: string, code: OptionIssueCode, message: string): OptionIssue => ({ widgetId, key: path, code, message });\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n out.push(at_(at, 'type', `${at} must be an object, got ${describe(entry)}`));\n return;\n }\n const record = entry as Record<string, unknown>;\n for (const [field, fieldSpec] of Object.entries(spec.item)) {\n const fieldValue = record[field];\n if (fieldValue === undefined || (fieldSpec.required && fieldValue === '')) {\n if (fieldSpec.required) out.push(at_(`${at}.${field}`, 'missing', `${at}.${field} is required but not set`));\n continue;\n }\n out.push(...checkValue(widgetId, `${at}.${field}`, fieldSpec, fieldValue));\n }\n for (const field of Object.keys(record)) {\n if (!hasOwn(spec.item, field)) {\n out.push(at_(`${at}.${field}`, 'unknown', `${at}.${field} is not a field of ${key} entries`));\n }\n }\n });\n return out;\n }\n }\n}\n\nfunction parsesAsJson(value: string): boolean {\n try {\n JSON.parse(value);\n return true;\n } catch {\n return false;\n }\n}\n\n// describe names the offending value's type in a message, quoting a string so an empty\n// one is visible and distinguishing null from an object.\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (typeof value === 'string') return `string ${JSON.stringify(value)}`;\n if (Array.isArray(value)) return 'an array';\n return `${typeof value} ${JSON.stringify(value)}`;\n}\n\n// numberSpec returns the declared bounds for a numeric option, so an authoring UI can\n// constrain its input from the schema rather than from a second, hand-written copy of the\n// same numbers.\n//\n// This exists because the console's numeric inputs were unbounded: a `precision` of\n// -1 is a legal number, reaches Number.prototype.toFixed, and throws a RangeError\n// DURING RENDER. formatValue clamps so the renderer survives, but the definition is\n// still wrong and the author had no way to know. A panel that reads the schema cannot\n// offer the value at all.\nexport function numberSpec(specs: WidgetOptionSpecs, key: string): NumberOptionSpec | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'number' ? spec : undefined;\n}\n\n// clampNumberOption folds a raw number into what a numeric option's schema allows, or\n// returns undefined when it is not a number at all.\n//\n// Separate from the input that calls it because the correction is the part worth\n// testing: an authoring UI's keystroke handling is awkward to exercise, and the\n// question that actually matters \u2014 what value gets STORED for a given input \u2014 is a\n// pure function of the schema and the number.\nexport function clampNumberOption(spec: NumberOptionSpec | undefined, value: number): number | undefined {\n if (!Number.isFinite(value)) return undefined;\n if (!spec) return value;\n let next = spec.integer ? Math.trunc(value) : value;\n if (spec.min !== undefined) next = Math.max(spec.min, next);\n if (spec.max !== undefined) next = Math.min(spec.max, next);\n return next;\n}\n\n// enumValues returns the legal values of an enum option, so an authoring UI can build its\n// dropdown from the schema instead of a private copy of the same list.\n//\n// A dropdown offering a value the schema rejects lets a panel author a definition the\n// validator refuses; one MISSING a value silently removes a filter an author could\n// previously express. Both are invisible without a shared source.\nexport function enumValues(specs: WidgetOptionSpecs, key: string): readonly string[] | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'enum' ? spec.values : undefined;\n}\n\n// applyOptionPatch is the VALIDATED write a registered editor is given: it returns the\n// options bag with `patch` applied, or throws on a write the schema cannot hold.\n//\n// An editor is developer code running in the host page, so a mistake in it should be loud at\n// the moment it is made, not a half-configured widget discovered at publish. The rules:\n// - a key the schema does not declare is refused (an editor cannot grow the stored bag);\n// - a value of the wrong kind, an enum value outside `values`, or a `format: 'json'`\n// string that does not parse, is refused;\n// - a number is folded into its bounds, as the generic field does;\n// - `undefined`, an empty string, and `false` DROP the key (an unset option reads as its\n// default, and an opt-in flag is absent-when-off), as the generic fields do.\n// Keys the patch does not name \u2014 including ones outside the schema that an older release\n// stored \u2014 are carried through untouched.\nexport function applyOptionPatch(\n specs: WidgetOptionSpecs,\n options: Readonly<Record<string, unknown>> | undefined,\n patch: Readonly<Record<string, unknown>>,\n label = 'widget',\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...(options ?? {}) };\n for (const [key, value] of Object.entries(patch)) {\n if (!hasOwn(specs, key)) throw new Error(`${label}: \"${key}\" is not a declared option`);\n const spec = specs[key];\n if (\n value === undefined ||\n value === '' ||\n (spec.kind === 'boolean' && value === false) ||\n (spec.kind === 'list' && Array.isArray(value) && value.length === 0)\n ) {\n delete next[key];\n continue;\n }\n const refuse = (expected: string): never => {\n throw new TypeError(`${label}: option \"${key}\" takes ${expected}, got ${JSON.stringify(value)}`);\n };\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') refuse('a string');\n // A `format: 'json'` option is judged as the option validator judges it: a string\n // that does not parse is refused here rather than flagged later at publish.\n if (spec.format === 'json' && !parsesAsJson(value as string)) refuse('a JSON string');\n next[key] = value;\n break;\n case 'boolean':\n if (typeof value !== 'boolean') refuse('a boolean');\n next[key] = value;\n break;\n case 'number': {\n if (typeof value !== 'number') refuse('a finite number');\n const clamped = clampNumberOption(spec, value as number);\n if (clamped === undefined) refuse('a finite number');\n next[key] = clamped;\n break;\n }\n case 'enum':\n if (typeof value !== 'string' || !spec.values.includes(value)) {\n refuse(`one of ${spec.values.map((v) => JSON.stringify(v)).join(', ')}`);\n }\n next[key] = value;\n break;\n case 'list': {\n // Judged exactly as the validator judges a stored list: a wrong-typed field, a missing\n // required one, or a key the entries do not declare is refused here, not found at publish.\n const bad = checkValue(label, key, spec, value);\n if (bad.length > 0) refuse(`a list of ${key} entries (${bad[0].message})`);\n next[key] = value;\n break;\n }\n }\n }\n return next;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The React-free catalog of widget descriptors: what the definition parser and the\n// headless validators see of a widget registry. Constructing one validates EVERY\n// descriptor and the set as a whole, reports every problem together, and returns a deeply\n// frozen, independent instance \u2014 no module state, no global registration, so two hosts on\n// one page can hold two catalogs that do not affect each other.\n\nimport { validateOptions, hasOwn, type OptionIssue, type WidgetOptionSpecs } from './option-schema';\nimport {\n SDK_API_VERSION,\n isBuiltinWidgetId,\n isNamespacedWidgetId,\n pinnedAxes,\n type WidgetChannel,\n type WidgetResizeMode,\n type WidgetDescriptor,\n} from './widget-descriptor';\nimport { isRecord } from './is-record';\n\nexport interface DescriptorIssue {\n // The widget id the issue is about, or `#<index>` when the descriptor has no usable id.\n id: string;\n // Where in the descriptor, e.g. `defaults.max` or `binding.window.option`. Empty for an\n // issue about the descriptor as a whole.\n path: string;\n message: string;\n}\n\nexport interface WidgetCatalog {\n has(id: string): boolean;\n get(id: string): WidgetDescriptor | undefined;\n // Descriptors in the order they were supplied.\n list(): readonly WidgetDescriptor[];\n}\n\nexport class WidgetCatalogError extends Error {\n readonly issues: readonly DescriptorIssue[];\n\n constructor(issues: readonly DescriptorIssue[], subject = 'widget catalog') {\n super(describeIssues(subject, issues));\n this.name = 'WidgetCatalogError';\n this.issues = Object.freeze([...issues]);\n }\n}\n\nexport function describeIssues(subject: string, issues: readonly DescriptorIssue[]): string {\n const lines = issues.map((i) => ` - ${i.id}${i.path ? `.${i.path}` : ''}: ${i.message}`);\n return `invalid ${subject} (${issues.length} problem${issues.length === 1 ? '' : 's'}):\\n${lines.join('\\n')}`;\n}\n\n// ---- Descriptor validation --------------------------------------------------\n\nconst CHANNELS: readonly WidgetChannel[] = ['measurement', 'alarm', 'control', 'selection', 'location', 'aggregate', 'none'];\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\nconst SELECTOR_KINDS = ['device', 'anchor', 'slot'];\n\n// The keys each channel's binding policy may carry, mirroring ChannelBindingPolicy.\nconst BINDING_KEYS: Record<WidgetChannel, readonly string[]> = {\n measurement: ['window', 'windowSeconds', 'members', 'presence', 'history'],\n alarm: ['pageSize', 'state', 'severity', 'acknowledged', 'groupBy', 'countBySeverity', 'deviceType'],\n control: ['pageSize'],\n location: [],\n aggregate: ['windowSeconds'],\n selection: [],\n none: [],\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!isRecord(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nconst join = (base: string, key: string): string => (base ? `${base}.${key}` : key);\n\n// validateWidgetDescriptor checks one descriptor at runtime and returns every problem it\n// finds \u2014 the same invariants the types express, because JSON from a package manifest or a\n// JS caller bypasses the compiler. Accepts `unknown` for that reason.\nexport function validateWidgetDescriptor(descriptor: unknown): DescriptorIssue[] {\n const rawId = isRecord(descriptor) && typeof descriptor.id === 'string' ? descriptor.id : '';\n return collectDescriptorIssues(descriptor, rawId || '#?');\n}\n\nfunction collectDescriptorIssues(d: unknown, id: string): DescriptorIssue[] {\n const issues: DescriptorIssue[] = [];\n const add = (path: string, message: string): void => {\n issues.push({ id, path, message });\n };\n\n if (!isPlainObject(d)) {\n add('', 'a descriptor must be a plain object');\n return issues;\n }\n\n jsonOnly(d, '', add, new Set());\n\n // id \u2014 shape and the reserved rule (a trusted-code convention, not an enforcement).\n if (typeof d.id !== 'string' || d.id === '') {\n add('id', 'id must be a non-empty string');\n } else if (!isBuiltinWidgetId(d.id) && !isNamespacedWidgetId(d.id)) {\n add('id', `id ${JSON.stringify(d.id)} must be a reserved built-in id or a namespaced \"scope/name\" id`);\n }\n\n if (typeof d.version !== 'string' || !SEMVER.test(d.version)) {\n add('version', `version must be MAJOR.MINOR.PATCH, got ${JSON.stringify(d.version)}`);\n }\n if (d.sdkApiVersion !== SDK_API_VERSION) {\n add('sdkApiVersion', `unsupported sdkApiVersion ${JSON.stringify(d.sdkApiVersion)}; this build supports ${SDK_API_VERSION}`);\n }\n if (typeof d.optionsVersion !== 'number' || !Number.isInteger(d.optionsVersion) || d.optionsVersion < 1) {\n add('optionsVersion', `optionsVersion must be a positive integer, got ${JSON.stringify(d.optionsVersion)}`);\n }\n\n if (!isRecord(d.display)) {\n add('display', 'display must be an object');\n } else {\n if (typeof d.display.title !== 'string' || d.display.title === '') {\n add('display.title', 'display.title must be a non-empty string');\n }\n // A host renders these as text and groups its add menu by category, so a non-string\n // here is a crash (or an \"[object Object]\" heading) in someone else's console.\n for (const text of ['description', 'category', 'titleId', 'categoryId'] as const) {\n const value = d.display[text];\n if (value !== undefined && typeof value !== 'string') add(`display.${text}`, `display.${text} must be a string`);\n }\n }\n\n const channel = CHANNELS.find((c) => c === d.channel);\n if (!channel) add('channel', `channel must be one of ${CHANNELS.join(', ')}, got ${JSON.stringify(d.channel)}`);\n\n // A built-in is documented in the widgets package's own table (see its builtin-option-docs.ts), not on\n // the descriptor the renderer loads, so only an extension's descriptor must carry its option docs.\n const specs = checkOptions(d.options, !isBuiltinWidgetId(d.id), add);\n const constraints = checkConstraints(d.constraints, specs, add);\n\n if (specs) checkDefaults(d.defaults, specs, constraints, typeof d.id === 'string' ? d.id : id, add);\n else if (!isRecord(d.defaults)) add('defaults', 'defaults must be an object');\n\n checkDatasource(d.datasource, channel, add);\n if (channel) checkBinding(d.binding, channel, specs, add);\n checkInteractions(d.interactions, channel, add);\n checkLayout(d.layout, add);\n\n if (d.freshness !== undefined) {\n if (d.freshness !== true) add('freshness', 'freshness must be true when present');\n else if (channel && channel !== 'measurement') add('freshness', 'freshness applies only to the measurement channel');\n }\n\n return issues;\n}\n\ntype Add = (path: string, message: string) => void;\n\n// Descriptor values must survive JSON.stringify unchanged: no function, symbol, bigint,\n// undefined, NaN/Infinity or non-plain object (a class instance, Date, Map\u2026).\nfunction jsonOnly(value: unknown, path: string, add: Add, ancestors: Set<object>): void {\n const here = path || '(descriptor)';\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return;\n case 'number':\n if (!Number.isFinite(value)) add(path, `${here} is ${value}; descriptors hold JSON values only`);\n return;\n case 'undefined':\n add(path, `${here} is undefined; omit the key instead (descriptors hold JSON values only)`);\n return;\n case 'function':\n case 'symbol':\n case 'bigint':\n add(path, `${here} is a ${typeof value}; descriptors hold JSON values only`);\n return;\n }\n if (value === null) return;\n const obj = value as object;\n if (ancestors.has(obj)) {\n add(path, `${here} is a circular reference; descriptors hold JSON values only`);\n return;\n }\n ancestors.add(obj);\n if (Array.isArray(obj)) {\n obj.forEach((item, i) => jsonOnly(item, `${path}[${i}]`, add, ancestors));\n } else if (!isPlainObject(obj)) {\n add(path, `${here} is not a plain object; descriptors hold JSON values only`);\n } else {\n if (Object.getOwnPropertySymbols(obj).length > 0) add(path, `${here} has a symbol key; descriptors hold JSON values only`);\n for (const [key, item] of Object.entries(obj)) jsonOnly(item, join(path, key), add, ancestors);\n }\n ancestors.delete(obj);\n}\n\n// Returns the schema when it is structurally sound enough to check references and defaults\n// against, else undefined (the faults are already reported).\nfunction checkOptions(options: unknown, requireDoc: boolean, add: Add): WidgetOptionSpecs | undefined {\n if (!isRecord(options)) {\n add('options', 'options must be an object mapping option keys to specs');\n return undefined;\n }\n return checkOptionsAt(options, 'options.', requireDoc, add) ? (options as unknown as WidgetOptionSpecs) : undefined;\n}\n\n// Checks a table of option specs, reporting each fault under `prefix` + key. True when the table\n// is structurally sound. The same rules judge a list option's item fields (prefix\n// `options.<key>.item.`), so a field cannot be declared more loosely than an option.\nfunction checkOptionsAt(options: Record<string, unknown>, prefix: string, requireDoc: boolean, add: Add): boolean {\n let sound = true;\n for (const [key, spec] of Object.entries(options)) {\n const at = `${prefix}${key}`;\n if (!isRecord(spec)) {\n add(at, 'an option spec must be an object');\n sound = false;\n continue;\n }\n if (typeof spec.doc !== 'string' && (requireDoc || spec.doc !== undefined)) {\n add(`${at}.doc`, 'doc must be a string');\n }\n for (const text of ['label', 'help', 'labelId', 'helpId', 'placeholder', 'placeholderId'] as const) {\n if (spec[text] !== undefined && typeof spec[text] !== 'string') add(`${at}.${text}`, `${text} must be a string`);\n }\n // Any non-empty string: a control this host does not implement is a fallback to the\n // generic field, not a fault (see OptionSpecBase.control).\n if (spec.control !== undefined && (typeof spec.control !== 'string' || spec.control === '')) {\n add(`${at}.control`, `control must be a non-empty string, got ${JSON.stringify(spec.control)}`);\n }\n switch (spec.kind) {\n case 'string':\n case 'boolean':\n break;\n case 'number':\n for (const bound of ['min', 'max'] as const) {\n if (spec[bound] !== undefined && typeof spec[bound] !== 'number') add(`${at}.${bound}`, `${bound} must be a number`);\n }\n break;\n case 'enum':\n if (!Array.isArray(spec.values) || spec.values.length === 0 || !spec.values.every((v) => typeof v === 'string')) {\n add(`${at}.values`, 'an enum option needs a non-empty list of string values');\n sound = false;\n }\n for (const table of ['valueLabels', 'valueLabelIds'] as const) {\n const labels = spec[table];\n if (labels === undefined) continue;\n if (!isRecord(labels)) {\n add(`${at}.${table}`, `${table} must be an object mapping enum values to strings`);\n continue;\n }\n for (const [value, label] of Object.entries(labels)) {\n if (typeof label !== 'string') add(`${at}.${table}.${value}`, `${table} entries must be strings`);\n else if (Array.isArray(spec.values) && !spec.values.includes(value)) {\n add(`${at}.${table}.${value}`, `${JSON.stringify(value)} is not one of this option's values`);\n }\n }\n }\n break;\n case 'list': {\n if (spec.maxItems !== undefined && (typeof spec.maxItems !== 'number' || !Number.isInteger(spec.maxItems) || spec.maxItems < 1)) {\n add(`${at}.maxItems`, 'maxItems must be a whole number of at least 1');\n }\n // A non-boolean `ordered` (\"yes\", 1) would be read as truthy by one host and ignored by\n // another; refuse it so every host sees the same list.\n if (spec.ordered !== undefined && typeof spec.ordered !== 'boolean') {\n add(`${at}.ordered`, 'ordered must be true or false');\n }\n if (!isRecord(spec.item) || Object.keys(spec.item).length === 0) {\n add(`${at}.item`, 'a list option needs an item object declaring at least one field');\n sound = false;\n break;\n }\n // A field is a scalar spec checked with the same rules as a top-level option, but\n // nesting a list is not offered: the editor row is flat.\n const fields = spec.item as Record<string, unknown>;\n const flat = Object.fromEntries(Object.entries(fields).filter(([, f]) => !isRecord(f) || f.kind !== 'list'));\n for (const key of Object.keys(fields)) {\n if (!(key in flat)) add(`${at}.item.${key}`, 'a list item field cannot itself be a list');\n }\n if (Object.keys(flat).length !== Object.keys(fields).length) {\n sound = false;\n break;\n }\n if (!checkOptionsAt(flat, `${at}.item.`, requireDoc, add)) sound = false;\n break;\n }\n default:\n add(`${at}.kind`, `kind must be string, number, boolean, enum or list, got ${JSON.stringify(spec.kind)}`);\n sound = false;\n }\n }\n return sound;\n}\n\nfunction checkConstraints(\n constraints: unknown,\n specs: WidgetOptionSpecs | undefined,\n add: Add,\n): { kind: 'greater-than'; key: string; than: string }[] {\n if (constraints === undefined) return [];\n if (!Array.isArray(constraints)) {\n add('constraints', 'constraints must be a list');\n return [];\n }\n const valid: { kind: 'greater-than'; key: string; than: string }[] = [];\n constraints.forEach((c, i) => {\n const at = `constraints[${i}]`;\n if (!isRecord(c) || c.kind !== 'greater-than') {\n add(`${at}.kind`, `constraint kind must be \"greater-than\", got ${JSON.stringify(isRecord(c) ? c.kind : c)}`);\n return;\n }\n let ok = true;\n for (const field of ['key', 'than'] as const) {\n const name = c[field];\n if (typeof name !== 'string') {\n add(`${at}.${field}`, `${field} must name a declared option`);\n ok = false;\n } else if (specs && (!hasOwn(specs, name) || specs[name].kind !== 'number')) {\n add(`${at}.${field}`, `${field} ${JSON.stringify(name)} must name a declared number option`);\n ok = false;\n }\n }\n if (ok) valid.push({ kind: 'greater-than', key: c.key as string, than: c.than as string });\n });\n return valid;\n}\n\nfunction checkDefaults(\n defaults: unknown,\n specs: WidgetOptionSpecs,\n constraints: { kind: 'greater-than'; key: string; than: string }[],\n id: string,\n add: Add,\n): void {\n if (!isRecord(defaults)) {\n add('defaults', 'defaults must be an object');\n return;\n }\n // 'missing' is not a defaults fault: a fresh widget may be authoring-incomplete (an image\n // has no url until the author gives one), so `required` binds a SAVED widget, not the\n // starting point.\n const found: OptionIssue[] = validateOptions(specs, defaults, { constraints, label: id }).filter(\n (issue) => issue.code !== 'missing',\n );\n for (const issue of found) add(`defaults.${issue.key}`, issue.message);\n}\n\nfunction checkDatasource(datasource: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(datasource)) {\n add('datasource', 'datasource must be an object');\n return;\n }\n const mode = datasource.mode;\n if (mode !== 'none' && mode !== 'optional' && mode !== 'required') {\n add('datasource.mode', `mode must be none, optional or required, got ${JSON.stringify(mode)}`);\n return;\n }\n if ((channel === 'none' || channel === 'selection') && mode !== 'none') {\n add('datasource.mode', `a ${channel}-channel widget opens no data channel, so its datasource mode must be \"none\"`);\n }\n if (mode === 'none') {\n if (datasource.selectors !== undefined) add('datasource.selectors', 'a datasource mode of \"none\" takes no selectors');\n return;\n }\n const selectors = datasource.selectors;\n if (!Array.isArray(selectors) || selectors.length === 0) {\n add('datasource.selectors', `a datasource mode of \"${mode}\" needs a non-empty list of selector kinds`);\n return;\n }\n selectors.forEach((s, i) => {\n if (typeof s !== 'string' || !SELECTOR_KINDS.includes(s)) {\n add(`datasource.selectors[${i}]`, `selector must be one of ${SELECTOR_KINDS.join(', ')}, got ${JSON.stringify(s)}`);\n } else if (channel === 'control' && s !== 'device' && s !== 'slot') {\n add(`datasource.selectors[${i}]`, 'a command target must be a device, so a control widget accepts only device and slot selectors');\n }\n });\n}\n\nfunction checkBinding(binding: unknown, channel: WidgetChannel, specs: WidgetOptionSpecs | undefined, add: Add): void {\n if (!isRecord(binding)) {\n add('binding', 'binding must be an object');\n return;\n }\n const allowed = BINDING_KEYS[channel];\n for (const key of Object.keys(binding)) {\n if (!allowed.includes(key)) add(`binding.${key}`, `a ${channel}-channel widget has no binding policy \"${key}\"`);\n }\n if (allowed.includes('pageSize') && binding.pageSize === undefined) {\n add('binding.pageSize', `a ${channel}-channel widget must declare binding.pageSize`);\n }\n\n const ref = (key: string, accept: (spec: WidgetOptionSpecs[string]) => boolean, expect: string, allowConst: boolean): void => {\n const policy = binding[key];\n if (policy === undefined) return;\n const at = `binding.${key}`;\n if (!isRecord(policy)) {\n add(at, `${key} must be an object`);\n return;\n }\n const hasConst = policy.const !== undefined;\n const hasOption = policy.option !== undefined;\n if (hasConst) {\n if (!allowConst) add(`${at}.const`, `${key} takes an option reference, not a constant`);\n else if (typeof policy.const !== 'number' || !Number.isInteger(policy.const) || policy.const < 1) {\n add(`${at}.const`, `const must be a positive integer, got ${JSON.stringify(policy.const)}`);\n }\n if (hasOption) add(at, `${key} must be a constant or an option reference, not both`);\n return;\n }\n if (!hasOption) {\n add(at, `${key} must be a constant or an option reference`);\n return;\n }\n const name = policy.option;\n if (typeof name !== 'string') {\n add(`${at}.option`, 'option must name a declared option key');\n } else if (specs) {\n if (!hasOwn(specs, name)) add(`${at}.option`, `option ${JSON.stringify(name)} is not a declared option key`);\n else if (!accept(specs[name])) add(`${at}.option`, `option ${JSON.stringify(name)} must be ${expect}`);\n }\n if (policy.min !== undefined && typeof policy.min !== 'number') add(`${at}.min`, 'min must be a number');\n if (policy.default !== undefined) {\n const wantsNumber = key === 'window' || key === 'windowSeconds' || key === 'pageSize';\n if (typeof policy.default !== (wantsNumber ? 'number' : 'string')) {\n add(`${at}.default`, `default must be a ${wantsNumber ? 'number' : 'string'}`);\n }\n }\n };\n\n const isNumber = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'number';\n const isText = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'string' || s.kind === 'enum';\n const isBoolEnum = (s: WidgetOptionSpecs[string]): boolean =>\n s.kind === 'enum' && s.values.every((v) => v === 'true' || v === 'false');\n\n ref('window', isNumber, 'a number option', false);\n ref('windowSeconds', isNumber, 'a number option', false);\n ref('pageSize', isNumber, 'a number option', true);\n ref('state', isText, 'a string or enum option', false);\n ref('severity', isText, 'a string or enum option', false);\n ref('deviceType', isText, 'a string or enum option', false);\n ref('acknowledged', isBoolEnum, 'an enum option whose values are only \"true\" and \"false\"', false);\n\n // A MODE binding turns a behaviour on: `members` makes a measurement widget a fleet widget\n // (`{ const: true }` always, or `{ option, equals }`), and `groupBy` makes an alarm widget group by\n // device type (`{ option, equals }` only). Either way a declared option holding `equals` is the switch.\n const modeBinding = (key: 'members' | 'groupBy'): void => {\n const mode = binding[key];\n if (mode === undefined) return;\n const m = isRecord(mode) ? mode : {};\n const at = `binding.${key}`;\n const allowConst = key === 'members';\n const isConst = m.const !== undefined;\n if (isConst ? !allowConst || m.option !== undefined || m.equals !== undefined : typeof m.option !== 'string' || typeof m.equals !== 'string') {\n add(at, `${key} must be ${allowConst ? '{ const: true } or ' : ''}{ option, equals } naming a declared option and a value of it`);\n } else if (isConst) {\n if (m.const !== true) add(`${at}.const`, `const must be true, got ${JSON.stringify(m.const)}`);\n } else if (specs) {\n const spec = hasOwn(specs, m.option as string) ? specs[m.option as string] : undefined;\n if (!spec) add(`${at}.option`, `option ${JSON.stringify(m.option)} is not a declared option key`);\n else if (!isText(spec)) add(`${at}.option`, `option ${JSON.stringify(m.option)} must be a string or enum option`);\n else if (spec.kind === 'enum' && !spec.values.includes(m.equals as string)) {\n add(`${at}.equals`, `${JSON.stringify(m.equals)} is not one of the option's values`);\n }\n }\n };\n // `presence` and `history` are constant switches and nothing else: `{ const: true }`. `presence` makes a\n // measurement widget read its devices' connectivity (online or offline, last seen) in place of their\n // readings; `history` makes it read one device's stored history in time buckets, not the live feed.\n for (const key of ['presence', 'history'] as const) {\n const flag = binding[key];\n if (flag !== undefined && !(isRecord(flag) && flag.const === true && Object.keys(flag).length === 1)) {\n add(`binding.${key}`, 'must be { const: true }');\n }\n }\n modeBinding('members');\n modeBinding('groupBy');\n // `countBySeverity` is a constant switch and nothing else: `{ const: true }`.\n const counts = binding.countBySeverity;\n if (counts !== undefined && !(isRecord(counts) && counts.const === true && Object.keys(counts).length === 1)) {\n add('binding.countBySeverity', 'must be { const: true }');\n }\n}\n\nfunction readSpan(value: unknown, path: string, add: Add): { cols: number; rows: number } | undefined {\n if (!isRecord(value)) {\n add(path, `${path} must be an object with cols and rows`);\n return undefined;\n }\n let ok = true;\n for (const axis of ['cols', 'rows'] as const) {\n const n = value[axis];\n if (typeof n !== 'number' || !Number.isInteger(n) || n < 1) {\n add(`${path}.${axis}`, `${axis} must be a positive integer, got ${JSON.stringify(n)}`);\n ok = false;\n }\n }\n for (const key of Object.keys(value)) {\n if (key !== 'cols' && key !== 'rows') add(`${path}.${key}`, `unknown span field \"${key}\"`);\n }\n return ok ? { cols: value.cols as number, rows: value.rows as number } : undefined;\n}\n\nfunction checkLayout(layout: unknown, add: Add): void {\n if (layout === undefined) return;\n if (!isRecord(layout)) {\n add('layout', 'layout must be an object');\n return;\n }\n for (const key of Object.keys(layout)) {\n if (!['defaultSpan', 'minSpan', 'maxSpan', 'resize'].includes(key)) add(`layout.${key}`, `unknown layout field \"${key}\"`);\n }\n let def: { cols: number; rows: number } | undefined;\n if (layout.defaultSpan === undefined) add('layout.defaultSpan', 'layout needs a defaultSpan');\n else def = readSpan(layout.defaultSpan, 'layout.defaultSpan', add);\n const min = layout.minSpan === undefined ? undefined : readSpan(layout.minSpan, 'layout.minSpan', add);\n const max = layout.maxSpan === undefined ? undefined : readSpan(layout.maxSpan, 'layout.maxSpan', add);\n\n const mode = layout.resize ?? 'both';\n if (mode !== 'both' && mode !== 'horizontal' && mode !== 'vertical' && mode !== 'none') {\n add('layout.resize', `resize must be both, horizontal, vertical or none, got ${JSON.stringify(layout.resize)}`);\n return;\n }\n\n // minSpan <= defaultSpan <= maxSpan, per axis. An absent minSpan is 1 x 1.\n const floor = min ?? { cols: 1, rows: 1 };\n for (const axis of ['cols', 'rows'] as const) {\n if (def && floor[axis] > def[axis]) add('layout.minSpan', `minSpan.${axis} (${floor[axis]}) is above defaultSpan.${axis} (${def[axis]})`);\n if (def && max && def[axis] > max[axis]) add('layout.maxSpan', `maxSpan.${axis} (${max[axis]}) is below defaultSpan.${axis} (${def[axis]})`);\n if (!def && min && max && min[axis] > max[axis]) add('layout.maxSpan', `maxSpan.${axis} is below minSpan.${axis}`);\n }\n\n // A restricted mode pins an axis at defaultSpan, so a declared bound must not contradict it.\n // The free axis stays as open as the bounds leave it: no maxSpan means no ceiling there.\n if (!def) return;\n for (const axis of pinnedAxes(mode as WidgetResizeMode)) {\n for (const [name, bound] of [['minSpan', min], ['maxSpan', max]] as const) {\n if (bound && bound[axis] !== def[axis]) {\n add('layout.resize', `resize \"${mode}\" pins ${axis} at ${def[axis]}, but ${name}.${axis} is ${bound[axis]}`);\n }\n }\n }\n}\n\nfunction checkInteractions(interactions: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(interactions)) {\n add('interactions', 'interactions must be an object');\n return;\n }\n const only: Record<string, WidgetChannel> = { alarmActions: 'alarm', commandSend: 'control' };\n for (const [key, value] of Object.entries(interactions)) {\n const at = `interactions.${key}`;\n if (key !== 'alarmActions' && key !== 'commandSend' && key !== 'selection' && key !== 'candidates') {\n add(at, `unknown interaction \"${key}\"`);\n } else if (value !== true) {\n add(at, `${key} must be true when declared (omit it otherwise)`);\n } else if (channel && only[key] && only[key] !== channel) {\n add(at, `${key} is only available to ${only[key]}-channel widgets, not ${channel}`);\n }\n }\n}\n\n// ---- Catalog ----------------------------------------------------------------\n\n// Freezes in place, recursively. Idempotent, so a table shared between registries (the\n// built-in option specs are) is simply frozen again. Descriptors hold JSON values only, so\n// this never meets a function or a cycle.\n//\n// It descends even into an object that is ALREADY frozen: Object.freeze is shallow, so a\n// caller's `Object.freeze(descriptor)` says nothing about `descriptor.defaults`, and\n// stopping at the first frozen object would leave those children mutable.\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n for (const child of Object.values(value)) deepFreeze(child);\n }\n return value;\n}\n\n// createWidgetCatalog validates every descriptor and the set (duplicate ids), then throws\n// ONE WidgetCatalogError carrying every issue, or returns a sealed catalog.\nexport function createWidgetCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const issues = collectCatalogIssues(descriptors);\n if (issues.length > 0) throw new WidgetCatalogError(issues);\n return sealCatalog(descriptors);\n}\n\n// The issues for a set of descriptors: each descriptor's own, plus duplicate ids. A pure\n// report \u2014 it cannot produce a catalog. Exposed so the widget registry can report descriptor\n// and implementation faults together before it builds anything.\nexport function collectCatalogIssues(descriptors: readonly unknown[]): DescriptorIssue[] {\n if (!Array.isArray(descriptors)) {\n return [{ id: '#?', path: '', message: 'descriptors must be an array' }];\n }\n const issues: DescriptorIssue[] = [];\n const seen = new Map<string, number>();\n descriptors.forEach((d, index) => {\n const id = isRecord(d) && typeof d.id === 'string' && d.id !== '' ? d.id : `#${index}`;\n issues.push(...collectDescriptorIssues(d, id));\n if (id.startsWith('#')) return;\n const first = seen.get(id);\n if (first === undefined) seen.set(id, index);\n else issues.push({ id, path: 'id', message: `duplicate widget id (first registered at position ${first}, again at ${index})` });\n });\n return issues;\n}\n\n// Builds the sealed catalog from descriptors ALREADY validated by collectCatalogIssues.\n// Module-private on purpose: the only way to obtain a catalog is createWidgetCatalog, which\n// validates first, so the published contract has no unvalidated path to a catalog.\nfunction sealCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const byId = new Map<string, WidgetDescriptor>();\n for (const d of descriptors) byId.set(d.id, deepFreeze(d));\n const ordered: readonly WidgetDescriptor[] = Object.freeze([...byId.values()]);\n return Object.freeze({\n has: (id: string) => byId.has(id),\n get: (id: string) => byId.get(id),\n list: () => ordered,\n });\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Adaptive columns and reflow, as pure functions.\n//\n// Cells keep a roughly constant size: a board authored in N columns renders as authored while a\n// cell at N columns is at least minCellSize wide (the cells stretch to fill the width, so a wall\n// gets bigger cells and bigger text, not more widgets). Narrower than that the column count\n// drops to what fits, and the base layout is poured into it in reading order. A span in cells\n// therefore means a readable size at every width.\n//\n// The container's width is what is measured, never the window's: an embedded board answers to the\n// space it is actually given.\n\nimport {\n BASE_BREAKPOINT,\n PHONE_MIN_COLUMNS,\n activeBreakpoint,\n breakpointColumns,\n cellSizeAt,\n gridFootprintsOverlap,\n gutterPx,\n resolvePlacement,\n} from './grid';\nimport type { SpanLimits } from './layout-lint';\nimport type { CanvasGrid, DashboardDefinition, GridPlacement, WidgetInstance } from './types';\n\n// The column count a container width gets: the authored count while its cells would be at least\n// minCellSize (scaled by the density), otherwise as many columns as fit at minCellSize, no fewer\n// than PHONE_MIN_COLUMNS (a board authored in fewer keeps its own count) and never more than the\n// authored count.\nexport function liveColumns(grid: CanvasGrid, containerWidth: number, authored: number, scale = 1): number {\n const minCell = grid.minCellSize * scale;\n if (cellSizeAt(grid, containerWidth, authored, scale) >= minCell) return authored;\n const gx = gutterPx(grid, scale).x;\n // The inverse of cellSizeAt: the most columns whose cells are still minCell wide.\n const room = cellSizeAt(grid, containerWidth, 1, scale);\n const fit = Math.floor((room + gx) / (minCell + gx));\n return Math.min(authored, Math.max(PHONE_MIN_COLUMNS, fit));\n}\n\nexport interface ReflowItem {\n id: string;\n placement: GridPlacement;\n // The widget's declared limits; a span is clamped into them. Absent = unbounded.\n limits?: SpanLimits;\n}\n\nconst clampInto = (n: number, min: number | undefined, max: number | undefined) =>\n Math.min(max ?? Infinity, Math.max(min ?? 1, n));\n\n// The container width below which a reflowed board is drawn as a single column: the compact\n// window class phones fall in. Above it, narrow widgets pair up side by side; below it two of\n// them would each get half a phone, so every widget takes its own row at the full width.\nexport const STACK_BELOW_WIDTH = 600;\n\n// reflowGrid pours grid footprints into `columns` columns. Widgets go in reading order (row, then\n// column, then their order in the input), each keeping its span clamped into its declared limits\n// and then into the columns (a widget wider than the whole grid takes the full width, below its\n// minSpan if it has to), at the first free slot at or below the row the previous widget landed in.\n// The row never moves back up, so the order a viewer reads is the order authored. Nothing\n// overlaps. Deterministic: the same input gives the same output.\n//\n// With `stack` (a phone), each widget instead takes its own row under the previous one, as wide\n// as the grid and its maxSpan allow: a single column, still in reading order, heights kept.\nexport function reflowGrid(\n items: readonly ReflowItem[],\n columns: number,\n options: { stack?: boolean } = {},\n): Map<string, GridPlacement> {\n const order = items\n .map((item, index) => ({ item, index }))\n .sort(\n (a, b) =>\n a.item.placement.row - b.item.placement.row ||\n a.item.placement.col - b.item.placement.col ||\n a.index - b.index,\n );\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n let floor = 0;\n let bottom = 0;\n for (const { item } of order) {\n const { placement: p, limits } = item;\n const want = options.stack ? columns : p.cols;\n const cols = Math.min(columns, clampInto(want, limits?.minSpan?.cols, limits?.maxSpan?.cols));\n const rows = clampInto(p.rows, limits?.minSpan?.rows, limits?.maxSpan?.rows);\n if (options.stack) {\n const spot: GridPlacement = { mode: 'grid', col: 0, row: bottom, cols, rows };\n out.set(item.id, spot);\n bottom += rows;\n continue;\n }\n let spot: GridPlacement | undefined;\n for (let row = floor; !spot; row++) {\n for (let col = 0; col + cols <= columns; col++) {\n const probe: GridPlacement = { mode: 'grid', col, row, cols, rows };\n if (!placed.some((q) => gridFootprintsOverlap(probe, q))) {\n spot = probe;\n break;\n }\n }\n }\n placed.push(spot);\n out.set(item.id, spot);\n floor = spot.row;\n }\n return out;\n}\n\n// The SHAPE of the layout at a width: everything about it a width decides. Two widths with the\n// same shape draw the same layout (the cells differ in size, which CSS follows on its own), so a\n// renderer redraws only when the shape changes, not on every pixel of a resize.\nexport interface LayoutShape {\n // The breakpoint whose placements are shown: chosen from the container's width.\n breakpoint: string;\n // The column count actually drawn.\n columns: number;\n // True when the board was poured into fewer columns than that breakpoint authored.\n reflowed: boolean;\n // True when the reflow is a single column (a container narrower than STACK_BELOW_WIDTH).\n stacked: boolean;\n}\n\n// layoutShape is the shape a container width gives a definition's canvas. A width of 0 or less\n// means \"not measured yet\": the authored base breakpoint at its own columns.\nexport function layoutShape(\n canvas: Pick<DashboardDefinition['canvas'], 'grid' | 'breakpoints'>,\n containerWidth: number,\n scale = 1,\n): LayoutShape {\n const measured = containerWidth > 0;\n const breakpoint = measured ? activeBreakpoint(canvas.breakpoints, containerWidth) : BASE_BREAKPOINT;\n const authored = breakpointColumns(canvas, breakpoint);\n const columns = measured ? liveColumns(canvas.grid, containerWidth, authored, scale) : authored;\n const reflowed = columns < authored;\n return { breakpoint, columns, reflowed, stacked: reflowed && containerWidth < STACK_BELOW_WIDTH };\n}\n\nexport interface EffectiveLayout extends LayoutShape {\n // The grid footprint of every grid widget as drawn (the authored one when not reflowed).\n grid: Map<string, GridPlacement>;\n}\n\n// effectiveLayout is what a renderer draws for a container width. A width of 0 or less means \"not\n// measured yet\" and draws the authored base layout. A breakpoint the author defined for this\n// width wins over the automatic reflow: it is drawn as authored while its cells fit, and is itself\n// poured into fewer columns only if even its own count does not fit. A reflow in a container\n// narrower than STACK_BELOW_WIDTH is a single column. Relative widgets are not in `grid`: they\n// follow their host.\nexport function effectiveLayout(\n def: DashboardDefinition,\n containerWidth: number,\n options: { scale?: number; limitsOf?: (w: WidgetInstance) => SpanLimits | undefined } = {},\n): EffectiveLayout {\n const shape = layoutShape(def.canvas, containerWidth, options.scale ?? 1);\n const items: ReflowItem[] = [];\n for (const w of def.widgets) {\n const p = resolvePlacement(w.layout, shape.breakpoint);\n if (p.mode === 'grid') items.push({ id: w.id, placement: p, limits: options.limitsOf?.(w) });\n }\n const grid = shape.reflowed\n ? reflowGrid(items, shape.columns, { stack: shape.stacked })\n : new Map(items.map((i) => [i.id, i.placement]));\n return { ...shape, grid };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The command-delivery lifecycle vocabulary, as the frontend sees it.\n//\n// \uD83D\uDD34 THIS IS THE ONE COPY. It lives in `dashboards` rather than `widgets` because\n// `widgets` depends on `dashboards` (never the reverse), so this is the lowest layer\n// both the widget package and the console can reach. It previously existed as three\n// hand-maintained sets \u2014 the console's DeviceCommandsPanel, the widgets' command-status\n// helper, and an ad-hoc expression in the synthetic preview \u2014 which is exactly how the\n// console kept offering a Cancel button on an already-cancelled command: a status was\n// added to the service and only some of the copies learned about it.\n//\n// The service declares no GraphQL enum for status, so it crosses the wire as a plain\n// string and an UNRECOGNIZED value must stay survivable: nothing here throws on one. What\n// an unknown status MEANS differs per question, though \u2014 see the two predicates at the\n// bottom, which answer for it in opposite directions on purpose.\n//\n// \uD83D\uDD34 \"IN FLIGHT\" AND \"CANCELLABLE\" ARE NOT THE SAME SET, and reading one off the other is\n// the bug this file is shaped to prevent. SENT is in flight (the device has not answered\n// yet) but NOT cancellable: the command is already at the device, and letting the platform\n// call it off would only make it discard the real answer that is on its way back.\n\n// Every lifecycle state the command-delivery service can persist. Ordered as a command\n// travels: the non-terminal states first, then the terminal ones.\nexport const COMMAND_STATUSES = [\n // \u2500\u2500 Non-terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // Accepted; awaiting its first dispatch decision. Genuinely transient.\n 'QUEUED',\n // The platform is deliberately WITHHOLDING dispatch because the device is known\n // absent. This is where an offline fleet's backlog accumulates \u2014 it can sit for days,\n // and it is the honest answer to \"why hasn't my command arrived?\".\n 'HELD',\n // Dispatched toward a device believed reachable; awaiting its response.\n 'SENT',\n // Published, and it went NOWHERE: the device turned out not to be reachable, so the\n // transport had nothing to hand it to. The platform still holds the command and will\n // deliver it when the device next wakes. Distinct from HELD (where dispatch was never\n // attempted, because the device was already known absent) and from TIMEOUT (where the\n // command reached a device that then said nothing).\n 'PARKED',\n // \u2500\u2500 Terminal \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n // The device answered.\n 'SUCCESSFUL',\n 'FAILED',\n // Dispatched, never answered in time.\n 'TIMEOUT',\n // The TTL elapsed before it ever went out.\n 'EXPIRED',\n // An operator or tenant called it off. Distinct from EXPIRED because it is a\n // different ACTOR, not a different outcome. Cancellation used to write EXPIRED, so\n // BOTH values appear in real data \u2014 historical rows are not backfilled.\n 'CANCELLED',\n] as const;\n\nexport type CommandStatus = (typeof COMMAND_STATUSES)[number];\n\n// The states a command can never leave: no further transition is permitted, so nothing\n// more will happen to it. Terminal implies not cancellable; the converse does NOT hold.\nexport const TERMINAL_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'SUCCESSFUL',\n 'FAILED',\n 'TIMEOUT',\n 'EXPIRED',\n 'CANCELLED',\n]);\n\n// The states from which the platform will still accept a cancellation \u2014 a POSITIVE list,\n// mirroring the service's own gate. All three are states in which the command has not\n// reached the device: QUEUED (not yet dispatched), HELD (dispatch withheld), PARKED\n// (published into a void and waiting for the device to wake). Cancelling any of them\n// takes back something nobody else is holding.\n//\n// SENT is deliberately absent. It is in flight, so !isTerminalCommandStatus('SENT') is\n// true \u2014 which is exactly why a cancel control must not be gated on that expression.\nexport const CANCELLABLE_COMMAND_STATUSES: ReadonlySet<string> = new Set<string>([\n 'QUEUED',\n 'HELD',\n 'PARKED',\n]);\n\n// \uD83D\uDD34 THE TWO PREDICATES ANSWER FOR AN UNRECOGNIZED STATUS IN OPPOSITE DIRECTIONS, on\n// purpose. Status crosses the wire as a plain string with no GraphQL enum behind it, so a\n// value this build has never heard of is always reachable \u2014 a newer service, a hand-edited\n// row \u2014 and each question has its own safe side:\n//\n// isTerminalCommandStatus negative membership \u21D2 unknown reads NON-terminal, i.e.\n// STILL IN FLIGHT. A status the service adds is one it added\n// because something is still happening to the command, and\n// the cost of guessing wrong is only a row that lingers in an\n// \"outstanding\" list instead of settling.\n//\n// isCancellableCommandStatus positive membership \u21D2 unknown reads NOT CANCELLABLE. This\n// file used to argue the other way \u2014 that refusing to offer\n// cancel for a state we do not recognize would strand a live\n// command \u2014 and that argument is now false. The service gates\n// cancellation on the same positive list, and \uD83D\uDD34 OUTSIDE THAT\n// LIST IT DOES NOT REFUSE: it SUCCEEDS and returns the command\n// UNCHANGED. So a Cancel button offered on an unknown status is\n// a no-op that answers the click with a SUCCESS TOAST for a\n// command nobody cancelled \u2014 a worse outcome than an error,\n// because the operator has no way to tell. The command is\n// stranded either way; this way we do not also claim otherwise.\n// A host that offers the button anyway must therefore check the\n// status that CAME BACK, not the fact that the call resolved.\nexport function isTerminalCommandStatus(status: string): boolean {\n return TERMINAL_COMMAND_STATUSES.has(status);\n}\n\n// isCancellableCommandStatus reports whether a cancel request would be accepted. Gate every\n// cancel control on THIS, never on !isTerminalCommandStatus \u2014 the two differ by SENT, and\n// by every status added after this build shipped.\nexport function isCancellableCommandStatus(status: string): boolean {\n return CANCELLABLE_COMMAND_STATUSES.has(status);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The dashboard-definition contract (ADR-039). These types are the canonical\n// shape of the JSON document that dashboard-management stores opaquely \u2014 the\n// backend only validates it is well-formed JSON, so this package owns the shape.\n// Kept deliberately fluid pre-GA. See research/dashboard-phase1-design-2026-07.md.\n\n// ---- Datasource selectors ---------------------------------------------------\n//\n// A selector is a tagged union (discriminated on `kind`). The Hub resolves\n// `device` and `anchor`, and treats `unbound` as zero devices. A `slot` never reaches\n// it: the renderer resolves each slot through the settled bindings first (to a device,\n// an anchor, or `unbound`). The remaining kinds are reserved \u2014 present so definitions\n// stay forward-compatible, but the Hub rejects them until implemented.\n\n// ---- Location series selection (ADR-078 decision 9) -------------------------\n//\n// A selector's `measurements: string[]` names NAMED SCALAR SERIES, and a position is\n// not one of those: it is an atomic triple on its own event class and its own\n// hypertable, deliberately never a metric. Naming a location through `measurements`\n// would therefore be a lie the whole way down \u2014 the hub would resolve it against the\n// measurement stream, which carries no coordinates at all.\n//\n// So a selector names a location series in its own field. The vocabulary is CLOSED\n// rather than free text: a device has exactly one position track, so there is nothing\n// to disambiguate by name, and an open string would invite an author to type a\n// measurement name here and get silence.\n//\n// 'latest' \u2014 the last-known position, read from device-state's O(1) projection.\n//\n// A second member ('track', the position history from event-management) is the shape\n// this exists to leave room for; the map widget reads 'latest' only.\nexport type LocationSeries = 'latest';\n\n// The location series a selector names. An OBJECT rather than a bare string so a\n// future member can carry its own parameters (a track's window) without changing\n// every selector again.\nexport interface LocationSelection {\n series: LocationSeries;\n}\n\n// device \u2014 one device's measurements (latest-card, gauge, single-series chart), and/or\n// its position when the selector names a location series.\nexport interface DeviceSelector {\n kind: 'device';\n deviceToken: string;\n measurements: string[];\n // Additive and OPTIONAL, deliberately: every dashboard definition stored before the\n // location channel existed carries no `location`, and must keep parsing and\n // rendering exactly as it did. Absence means \"this selector names no location\n // series\", which is the correct reading of every one of them.\n location?: LocationSelection;\n}\n\n// The dimension a dashboard aggregates over \u2014 a tracked relationship to a\n// customer / area / asset. An anchor is not an opaque alias: it is a graph edge\n// the platform already resolves events against.\nexport interface AnchorTarget {\n relationship: string;\n targetType: 'customer' | 'area' | 'asset';\n targetToken: string;\n}\n\nexport interface AnchorAggregation {\n window: string; // e.g. '1m'\n fn: 'avg' | 'min' | 'max' | 'sum' | 'count';\n}\n\n// anchor \u2014 an anchor dimension aggregated over its member devices.\nexport interface AnchorSelector {\n kind: 'anchor';\n anchor: AnchorTarget;\n measurements: string[];\n location?: LocationSelection;\n // RESERVED (Phase 2): server-side aggregation of the member devices into one\n // series. Phase 1 resolves an anchor to its member devices and streams each\n // one's raw samples; the Hub does NOT read this field yet. Present so a\n // definition authored against the final shape still round-trips.\n aggregation?: AnchorAggregation;\n}\n\n// ---- Reserved selectors (Phase 2) -------------------------------------------\n// Schema-valid and part of the union so a stored definition never fails to parse,\n// but the Hub throws \"not supported yet\" until Phase 2 wires them.\n\nexport interface DevicesSelector {\n kind: 'devices';\n deviceTokens: string[];\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport interface RelatedTraversalSelector {\n kind: 'relatedTraversal';\n from: string;\n relationship: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// slot \u2014 a NAMED reference into the dashboard's `slots` section, resolved to a\n// concrete entity at MOUNT by the host's binding manifest (ADR-039 runtime\n// binding). This makes a definition a reusable TEMPLATE: the widget names the\n// entity role (`slot`) and the measurements it wants, and two mounts of the same\n// definition can bind the slot to two different devices. The RENDERER resolves it\n// through the settled bindings (a slot's defaultBinding, overridable by the host and\n// by a selection) before the data source sees it, so re-pointing a slot re-subscribes\n// only the widgets bound to it; an unbound slot becomes an `unbound` selector and\n// renders as an empty placeholder.\nexport interface SlotSelector {\n kind: 'slot';\n slot: string;\n measurements: string[];\n location?: LocationSelection;\n}\n\n// unbound \u2014 a slot that has NO binding, as the renderer hands it to a data source.\n//\n// It is never authored: the renderer produces it when it resolves a widget's `slot`\n// selector through the settled bindings and finds nothing there (see\n// resolveWidgetDatasource). Every live channel resolves it to ZERO devices \u2014 no stream,\n// an empty alarm snapshot, no command target, no positions, and no availability check.\n//\n// \uD83D\uDD34 It is the OPPOSITE of an absent datasource, and the difference is not cosmetic. A\n// widget with no datasource at all is tenant-wide on the alarm channel, so an unbound slot\n// that collapsed into `undefined` would show every alarm in the tenant under a subtitle\n// that implies one machine. Unbound is therefore a resolved state of its own, never a\n// missing one.\n//\n// It carries the widget's series (measurement names, and the location series when one is\n// named) so an offline preview source, which never resolves a device, can still fabricate\n// the values a widget asks for. The live hub reads none of it.\nexport interface UnboundSelector {\n kind: 'unbound';\n measurements: string[];\n location?: LocationSelection;\n}\n\nexport type DatasourceSelector =\n | DeviceSelector\n | AnchorSelector\n | DevicesSelector\n | RelatedTraversalSelector\n | SlotSelector\n | UnboundSelector;\n\n// ---- Canvas + widgets -------------------------------------------------------\n\n// The built-in widget types. Kept as a runtime array so parse/validation and the\n// widget registry share one source of truth (WidgetType is derived from it).\n//\n// Widgets fall into data CHANNELS (see WIDGET_CHANNEL in @devicechain/widgets): the\n// measurement widgets stream telemetry samples; the alarm widgets consume the raised-\n// alarm surface (ADR-041) via the hub's alarm channel; the control widget (command-\n// button) issues commands and tracks their delivery lifecycle via the command channel;\n// the map widget reads device positions via the location channel (ADR-078 decision 9).\n// A widget's channel decides which hook/registry the renderer binds it through.\nexport const WIDGET_TYPES = [\n 'timeseries-chart',\n 'latest-card',\n 'gauge',\n 'table',\n 'label',\n 'image',\n 'alarm-table',\n 'alarm-count',\n 'alarm-severity',\n 'command-button',\n 'entity-selector',\n 'map',\n 'linear-gauge',\n 'level-gauge',\n 'battery',\n 'status-light',\n 'signal-bars',\n 'compass',\n 'progress-ring',\n 'command-slider',\n 'command-toggle',\n 'stat-card',\n 'bar-chart',\n 'pie-chart',\n 'fleet-table',\n 'fleet-count',\n 'device-status',\n 'online-doughnut',\n 'history-table',\n] as const;\n\nexport type WidgetType = (typeof WIDGET_TYPES)[number];\n\n// ---- Placement --------------------------------------------------------------\n//\n// Every widget is placed by an explicit MODE, modelled on CSS `position` and CSS anchor\n// positioning, so whether a widget may overlap another is something it DECLARES rather than\n// something a check has to catch afterwards:\n//\n// grid aligned to cells and occupying a footprint. Two grid footprints never overlap: the\n// parser refuses a definition where they do, so no producer (the editor, an import,\n// a Go builder, the API) can store one. The only mode that takes part in collision,\n// push-reflow and adaptive reflow.\n// relative positioned against a HOST widget's live box, so it moves when the host moves or\n// reflows. It may sit over the host (a badge on a map) or beside it (a caption under\n// a chart). It never collides: overlapping is what the mode is for. It paints above\n// every grid widget.\n//\n// The union is OPEN: RESERVED_PLACEMENT_MODES names the modes a later release may add (`pinned`,\n// fixed to a canvas edge; `sticky`, a grid widget that stays visible while the board scrolls).\n// This release refuses them by name rather than mis-placing them as something else.\n//\n// There is no z-order and no pixel nudge: layering is the relative mode, and every distance is\n// in cells, so an arrangement survives every screen size.\n\n// A point on a box. For `anchor` it is a point on the HOST; for `self`, the point on the\n// relative widget that is placed there.\nexport const PLACEMENT_ANCHORS = [\n 'top-left',\n 'top',\n 'top-right',\n 'left',\n 'center',\n 'right',\n 'bottom-left',\n 'bottom',\n 'bottom-right',\n] as const;\nexport type PlacementAnchor = (typeof PLACEMENT_ANCHORS)[number];\n\nexport const PLACEMENT_MODES = ['grid', 'relative'] as const;\nexport type PlacementMode = (typeof PLACEMENT_MODES)[number];\n// Named so a definition from a release that implements them fails with a reason, not as a typo.\nexport const RESERVED_PLACEMENT_MODES = ['pinned', 'sticky'] as const;\n\n// A footprint on the grid. `col`/`row` are the 0-based start cell; `cols`/`rows` how many cells\n// it covers. Cells are square, so a span fixes the widget's SHAPE as well as its share.\nexport interface GridPlacement {\n mode: 'grid';\n col: number;\n row: number;\n cols: number;\n rows: number;\n}\n\n// A relative widget's own size: a span in cells (the grid's rule: n cells = n*cell +\n// (n-1)*gutter), or exactly the host's box.\nexport type RelativeSize = { cols: number; rows: number } | { fill: 'host' };\n\n// A widget placed against another. The geometry, in order: `anchor` picks a point on the host's\n// box, `self` the point on this widget placed there, and `offset` translates it. An offset is a\n// DISTANCE in cell PITCHES (one unit = live cell + gutter on that axis), so a whole-number offset\n// lands exactly on a grid line, and fractions are allowed: `{x: -0.25, y: 0.25}` with\n// anchor = self = 'top-right' insets a badge a quarter pitch from the host's corner at every\n// cell size. The host's area plays no part.\nexport interface RelativePlacement {\n mode: 'relative';\n // The id of the widget this one is anchored to. It must exist; a relative host may itself be\n // relative only when ITS host is a grid widget (one level of chaining, no deeper).\n host: string;\n anchor: PlacementAnchor;\n // Absent = the same point as `anchor`.\n self?: PlacementAnchor;\n offset?: { x: number; y: number };\n size: RelativeSize;\n // Paint order among the relative widgets on the same host. Absent = 0.\n layer?: number;\n}\n\nexport type Placement = GridPlacement | RelativePlacement;\n\n// A widget's placement per breakpoint, keyed by breakpoint name. `base` is required; a widget\n// with no entry for a breakpoint shows its `base` placement there.\n//\n// \uD83D\uDD11 A BREAKPOINT LAYOUT IS AN AUTHORED ARRANGEMENT AT ITS OWN COLUMN COUNT. Placements at a\n// breakpoint are written in the column count canvas.breakpoints declares for it\n// (breakpointColumns; `base` is always canvas.grid.columns), so a phone arrangement pinned at 4\n// columns stays a 4-column arrangement. While the container is wide enough for the active\n// breakpoint's column count at minCellSize, that arrangement renders as authored and takes\n// precedence over the automatic reflow; only a container too narrow even for it reflows it.\n// A board authored only at `base` simply reflows.\nexport type WidgetLayout = Record<string, Placement>;\n\n// ---- Frames -----------------------------------------------------------------\n//\n// How a widget's card looks. `canvas.frame` sets the board's default and `widget.frame`\n// overrides it field by field (resolveFrame). Every colour, radius and spacing is a TOKEN, never\n// a free value: the parser refuses `#f00`, so dark mode, the ISA-101 theme and tenant branding\n// stay coherent (a border of `sev-critical` is fine; a hex code is not).\n//\n// The border and padding sit INSIDE the widget's footprint, so changing them never moves the\n// layout.\n\n// The colour tokens a host defines, and the only colours a definition may name.\n// @devicechain/widget-sdk's ThemeVar is this list.\nexport const THEME_COLOR_TOKENS = [\n 'background',\n 'foreground',\n 'card',\n 'card-foreground',\n 'muted',\n 'muted-foreground',\n 'primary',\n 'primary-foreground',\n 'border',\n 'destructive',\n 'chart-1',\n 'chart-2',\n 'chart-3',\n 'chart-4',\n 'chart-5',\n 'sev-critical',\n 'sev-major',\n 'sev-minor',\n 'sev-warning',\n] as const;\nexport type ThemeColorToken = (typeof THEME_COLOR_TOKENS)[number];\n\n// The spacing steps, in px at density 1; every one multiplies by the density scale. `space-0`\n// is zero, so a seamless tiled board is `gutter: 'space-0'` (or `gutter: 0`).\nexport const SPACE_TOKEN_PX = {\n 'space-0': 0,\n 'space-1': 4,\n 'space-2': 8,\n 'space-3': 12,\n 'space-4': 16,\n} as const;\nexport type SpaceTokenName = keyof typeof SPACE_TOKEN_PX;\n\nexport const FRAME_VARIANTS = ['card', 'flat', 'none'] as const;\nexport type FrameVariant = (typeof FRAME_VARIANTS)[number];\nexport const FRAME_RADII = ['none', 'sm', 'md', 'lg'] as const;\nexport type FrameRadius = (typeof FRAME_RADII)[number];\nexport const FRAME_BORDER_WIDTHS = [0, 1, 2, 3] as const;\nexport type FrameBorderWidth = (typeof FRAME_BORDER_WIDTHS)[number];\nexport const FRAME_HEADERS = ['auto', 'show', 'hide'] as const;\nexport type FrameHeader = (typeof FRAME_HEADERS)[number];\n\nexport interface FrameBorder {\n width?: FrameBorderWidth;\n radius?: FrameRadius;\n color?: ThemeColorToken;\n}\n\n// Every field optional: an absent field inherits (widget, then canvas, then the variant's preset).\n// variant card = border + background + header (the default); flat = a card with no border;\n// none = frameless (no border, transparent, no header), the usual choice for a\n// relative badge or caption.\n// header auto = the size-aware rules (the subtitle, then the title, give way as the widget\n// gets short); show = always; hide = never (the title stays the frame's aria-label).\nexport interface WidgetFrameSpec {\n variant?: FrameVariant;\n border?: FrameBorder;\n padding?: SpaceTokenName;\n background?: ThemeColorToken | 'transparent';\n header?: FrameHeader;\n}\n\n// A frame with every field decided: what the renderer hands the widget frame.\nexport interface ResolvedFrame {\n variant: FrameVariant;\n border: Required<FrameBorder>;\n padding: SpaceTokenName;\n background: ThemeColorToken | 'transparent';\n header: FrameHeader;\n}\n\n// The type a widget is given when its stored type is not one this build knows \u2014 one a\n// newer release added. Deliberately NOT a member of WIDGET_TYPES: it is never authored,\n// never offered in an add menu, and has no row in any per-type table. See\n// UnknownWidgetInstance.\nexport const UNKNOWN_WIDGET_TYPE = 'unknown-widget';\nexport type UnknownWidgetType = typeof UNKNOWN_WIDGET_TYPE;\n\n// The implementation a widget was authored against, stored beside its type as `widget`.\n// `version` is provenance (the widget release the author used); `optionsVersion` and\n// `sdkApiVersion` are the compatibility key together with the id \u2014 see checkWidgetCompatibility.\n// A definition predating this field is a legacy built-in and carries none.\nexport interface WidgetImplementationRef {\n version: string;\n optionsVersion: number;\n sdkApiVersion?: number;\n}\n\n// A namespaced id (`scope/name`) a registered extension package owns. Kept apart from\n// WidgetType so the built-in union stays exhaustive for the compiler.\nexport type ExtensionWidgetType = `${string}/${string}`;\n\n// A widget of a type this build knows how to render.\nexport interface KnownWidgetInstance {\n id: string;\n type: WidgetType;\n // Absent on a legacy (unversioned) built-in.\n widget?: WidgetImplementationRef;\n layout: WidgetLayout;\n // label/image widgets carry no datasource.\n datasource?: DatasourceSelector;\n // Widget-specific options (series colors, unit, thresholds, \u2026). Owned by the\n // widget package; opaque here.\n options?: Record<string, unknown>;\n // This widget's frame overrides, field by field over canvas.frame. Absent = the canvas's.\n frame?: WidgetFrameSpec;\n raw?: undefined;\n reason?: undefined;\n}\n\n// A widget from an installed extension package, recognized through the catalog the\n// definition was parsed with: registered, versioned, and compatible (optionsVersion and\n// SDK generation). Same envelope as a built-in; the id is a namespaced one.\nexport interface ExtensionWidgetInstance {\n id: string;\n type: ExtensionWidgetType;\n widget: WidgetImplementationRef;\n layout: WidgetLayout;\n datasource?: DatasourceSelector;\n options?: Record<string, unknown>;\n frame?: WidgetFrameSpec;\n raw?: undefined;\n reason?: undefined;\n}\n\n// Why a stored widget could not be resolved to an installed implementation.\n// unregistered - no registration for the id in the catalog (or the type is unknown)\n// unversioned - a registered extension stored without version metadata\n// invalid-metadata - `widget` is present but malformed, or an extension widget uses a\n// top-level field name the format reserves (`raw`, `reason`)\n// options-version - the stored settings shape differs from the installed one\n// sdk-incompatible - authored against a different SDK contract generation\nexport type UnresolvedWidgetCode =\n | 'unregistered'\n | 'unversioned'\n | 'invalid-metadata'\n | 'options-version'\n | 'sdk-incompatible';\n\nexport interface UnresolvedWidgetReason {\n code: UnresolvedWidgetCode;\n // A human-readable, specific explanation (names the id and the versions involved).\n message: string;\n}\n\n// A widget that cannot be resolved to an installed implementation. The parser keeps the\n// stored object VERBATIM in `raw`, and the serializer writes `raw` back, so a host on an\n// older release or without the package that loads a board and saves it does not delete or\n// rewrite the widget it cannot read.\n//\n// `id` and `layout` are the only parsed fields, because every widget has them and the\n// canvas needs them to place, select, move and delete the placeholder. An edit to\n// either is written back into `raw` on save; nothing else about the widget is ever\n// read or changed. It carries no datasource and no options: whatever the widget\n// binds lives in `raw`, where no hub channel can mistake it for a selector this build\n// understands. `reason` says why it is unresolved (derived at parse time, never stored).\nexport interface UnknownWidgetInstance {\n id: string;\n type: UnknownWidgetType;\n layout: WidgetLayout;\n raw: Record<string, unknown>;\n reason?: UnresolvedWidgetReason;\n widget?: undefined;\n datasource?: undefined;\n options?: undefined;\n frame?: undefined;\n}\n\nexport type WidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance | UnknownWidgetInstance;\n\n// A widget some installed implementation resolved: a built-in or a registered extension, i.e.\n// everything but a placeholder. What an editor panel is typed against.\nexport type ResolvedWidgetInstance = KnownWidgetInstance | ExtensionWidgetInstance;\n\nexport interface CanvasBackground {\n color?: string | null;\n imageUrl?: string | null;\n}\n\n// A length on the canvas: px at density 1 (multiplied by the density scale, like every size\n// token), or a spacing token.\nexport type GridLength = number | SpaceTokenName;\n\n// The canvas grid (the Android home-screen model): square cells of roughly constant size.\n//\n// columns the AUTHORED column count every `base` placement is written in.\n// minCellSize px at density 1. While (container width - padding - (columns-1)*gutter.x) /\n// columns is at least this, the authored layout renders as authored and the cells\n// STRETCH (a wall gets bigger cells, hence bigger text). Narrower than that, the\n// board reflows onto fewer live columns. The gutters come out of the cell, never\n// the other way round, so widening them can never push a cell below this size.\n// gutter the space between cells: one length, or one per axis.\n// padding the margin around the board: one length, or one per side.\n//\n// Cells are square: a row is as tall as the live cell is wide.\nexport interface CanvasGrid {\n columns: number;\n minCellSize: number;\n gutter: GridLength | { x: GridLength; y: GridLength };\n padding: GridLength | { top: GridLength; right: GridLength; bottom: GridLength; left: GridLength };\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// A breakpoint: the container width it starts at, and optionally the column count its layout is\n// authored in. A bare number is the width alone, and its layout is in canvas.grid.columns.\n// `base` starts at 0 and is always in canvas.grid.columns.\nexport type BreakpointSpec = number | { minWidth: number; columns: number };\n\n// Breakpoint name \u2192 spec. 'base' is required.\nexport type Breakpoints = Record<string, BreakpointSpec>;\n\n// How roomy the board renders: a scale applied to every size token (font, spacing) and to the\n// floor of the value-font curve. `comfortable` is the default and is not written when it is\n// the choice; a wall display picks `wall`. A viewer can override it (the /dash app's\n// `?density=`), so this is the AUTHORED default, not a fixed property of the board.\nexport const CANVAS_DENSITIES = ['compact', 'comfortable', 'wall'] as const;\nexport type CanvasDensity = (typeof CANVAS_DENSITIES)[number];\nexport const DEFAULT_CANVAS_DENSITY: CanvasDensity = 'comfortable';\nexport const DENSITY_SCALE: Readonly<Record<CanvasDensity, number>> = {\n compact: 0.9,\n comfortable: 1,\n wall: 1.5,\n};\n\nexport function isCanvasDensity(value: unknown): value is CanvasDensity {\n return typeof value === 'string' && (CANVAS_DENSITIES as readonly string[]).includes(value);\n}\n\nexport interface Canvas {\n background?: CanvasBackground;\n // Absent = `comfortable`.\n density?: CanvasDensity;\n grid: CanvasGrid;\n sizing: CanvasSizing;\n breakpoints: Breakpoints;\n // The board's default frame. Absent = a card.\n frame?: WidgetFrameSpec;\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 // A ROOT anchor slot's picker may list only the areas of one area type, or the assets of one\n // asset type (a Floor picker that does not offer buildings and plants). Absent = every one the\n // viewer may see. Ignored for a customer anchor, a device slot and a scoped slot.\n candidates?: SlotCandidates;\n}\n\n// The type filter a root slot's picker applies, by the type's token: the area type or asset type\n// of an anchor slot, the device type of a device slot.\nexport interface SlotCandidates {\n areaType?: string;\n assetType?: string;\n deviceType?: string;\n}\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 entities of one kind (token + optional name) the viewer may see,\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\n// The definition format this release writes. 2 is the square-cell grid with explicit placement\n// modes; 3 is the time span (a time-series widget draws a span, `windowSeconds`, not a sample\n// count, and the board carries a `timeRange`). A stored document below it is converted on load\n// (see ./legacy-grid and ./legacy-window).\nexport const CURRENT_SCHEMA_VERSION = 3;\n\n// The board's time range as authored: the span (seconds) its time-series widgets draw until a\n// viewer picks another. Absent = one hour (DEFAULT_TIME_RANGE_SECONDS).\nexport interface BoardTimeRange {\n defaultSeconds: number;\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 // The board's time range (D-1). Optional: a board that names none draws the default hour.\n timeRange?: BoardTimeRange;\n // Set by the parser, NEVER stored: the schemaVersion of the document this definition was\n // converted from, when it was stored in an older format and its layout was mapped onto the\n // current grid approximately. An editor shows a \"review the layout\" notice while it is set;\n // the serializer drops it, so the next save writes the current format and the notice goes.\n convertedFrom?: number;\n // Set by the parser, NEVER stored: the ids of the time-series widgets that carried a\n // sample-count window when the board was stored. A sample count has no span without a sampling\n // rate nobody recorded, so each of these now follows the board's time range instead; an editor\n // names them so the author can pin a `windowSeconds` where the old window mattered.\n convertedWindows?: string[];\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 // Set only on a point that is a bucket AVERAGE rather than a reading: the bucket's width in seconds.\n // Stored history is drawn (and exported) per bucket, and a point read live is the reading itself;\n // this is how a consumer tells them apart. Absent on a reading.\n bucketSeconds?: number;\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 // The originating device's type, present only on a row read for a subscription that asked for it\n // (AlarmSubscription.enrichDeviceType / deviceType). `undefined` means \"not asked for or not\n // resolvable\"; `null` means the originator is not a device. Additive: every other source omits it.\n deviceType?: AlarmDeviceType | null;\n}\n\nexport interface AlarmDeviceType {\n token: string;\n name: string | null;\n}\n\n// One device's connectivity, as device-state holds it: whether it is considered connected, and when\n// it last did anything. `lastActivityTime` is null for a device that has not produced an event.\n// A device with no state row at all has no DevicePresence (the service omits it).\nexport interface DevicePresence {\n deviceToken: string;\n active: boolean;\n lastActivityTime: string | null;\n // Seconds without activity after which an INFERRED device is marked inactive.\n inactivityTimeout: number;\n}\n\n// How many matching alarms are at one severity (a severity-count read).\nexport interface AlarmSeverityCount {\n severity: string;\n count: number;\n}\n\n// How many matching alarms are on devices of one type at one severity (a type-count read).\nexport interface AlarmTypeCount {\n deviceType: AlarmDeviceType;\n severity: string;\n count: number;\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// The square-cell grid's geometry, as pure functions: lengths, cell size, where a placement\n// lands, collisions between grid footprints, and the effective frame of a widget.\n//\n// One copy of the arithmetic, shared by everything that has to agree about where a widget is:\n// the parser (overlap), the legacy converter (mapping old boxes, then pushing collisions\n// apart), the editor (pixels <-> cells), the layout lint (a relative widget off the canvas) and\n// the renderers. Nothing here knows about React or the DOM.\n\nimport {\n SPACE_TOKEN_PX,\n type Breakpoints,\n type BreakpointSpec,\n type Canvas,\n type CanvasGrid,\n type GridLength,\n type GridPlacement,\n type Placement,\n type PlacementAnchor,\n type RelativePlacement,\n type ResolvedFrame,\n type SpaceTokenName,\n type WidgetFrameSpec,\n type WidgetInstance,\n type WidgetLayout,\n} from './types';\n\nexport const BASE_BREAKPOINT = 'base';\n\n// The grid a definition gets when it states none, and the values a partial grid is filled\n// from. 12 columns: it divides by 2, 3, 4 and 6, and at the 960 px a half-screen board gets, a\n// 12-column cell is ~73 px, comfortably above minCellSize, where 16 columns would already be\n// below it (52 px) and reflow. 56 px is the smallest cell a single-value widget reads in.\nexport const DEFAULT_GRID: Readonly<CanvasGrid> = Object.freeze({\n columns: 12,\n minCellSize: 56,\n gutter: 8,\n padding: 0,\n});\n\n// The narrowest a board reflows to: below this many live columns a widget would be a sliver.\nexport const PHONE_MIN_COLUMNS = 4;\n\nexport function isSpaceToken(v: unknown): v is SpaceTokenName {\n return typeof v === 'string' && Object.prototype.hasOwnProperty.call(SPACE_TOKEN_PX, v);\n}\n\n// The px a length stands for at a density scale.\nexport function gridLengthPx(len: GridLength, scale = 1): number {\n const base = typeof len === 'number' ? len : SPACE_TOKEN_PX[len];\n return base * scale;\n}\n\nexport interface Gutter {\n x: number;\n y: number;\n}\n\nexport interface Insets {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function gutterPx(grid: CanvasGrid, scale = 1): Gutter {\n const g = grid.gutter;\n if (typeof g === 'object') return { x: gridLengthPx(g.x, scale), y: gridLengthPx(g.y, scale) };\n const v = gridLengthPx(g, scale);\n return { x: v, y: v };\n}\n\nexport function paddingPx(grid: CanvasGrid, scale = 1): Insets {\n const p = grid.padding;\n if (typeof p === 'object') {\n return {\n top: gridLengthPx(p.top, scale),\n right: gridLengthPx(p.right, scale),\n bottom: gridLengthPx(p.bottom, scale),\n left: gridLengthPx(p.left, scale),\n };\n }\n const v = gridLengthPx(p, scale);\n return { top: v, right: v, bottom: v, left: v };\n}\n\n// The live cell size for a container width at a column count: the width left after the padding\n// and the gutters, shared evenly. The gutters come out of the cell. Never below 0.\nexport function cellSizeAt(grid: CanvasGrid, containerWidth: number, columns = grid.columns, scale = 1): number {\n const pad = paddingPx(grid, scale);\n const g = gutterPx(grid, scale);\n return Math.max(0, (containerWidth - pad.left - pad.right - (columns - 1) * g.x) / columns);\n}\n\n// The container width at which a column count's cells are exactly `cell` px: the inverse of\n// cellSizeAt.\nexport function containerWidthFor(grid: CanvasGrid, cell: number, columns = grid.columns, scale = 1): number {\n const pad = paddingPx(grid, scale);\n return columns * cell + (columns - 1) * gutterPx(grid, scale).x + pad.left + pad.right;\n}\n\n// ---- Breakpoints -------------------------------------------------------------------\n\nexport function breakpointMinWidth(spec: BreakpointSpec): number {\n return typeof spec === 'number' ? spec : spec.minWidth;\n}\n\n// The column count a breakpoint's placements are authored in: its declared count, or the grid's.\nexport function breakpointColumns(canvas: Pick<Canvas, 'grid' | 'breakpoints'>, breakpoint: string): number {\n if (breakpoint === BASE_BREAKPOINT) return canvas.grid.columns;\n const spec = Object.prototype.hasOwnProperty.call(canvas.breakpoints, breakpoint)\n ? canvas.breakpoints[breakpoint]\n : undefined;\n return spec !== undefined && typeof spec === 'object' ? spec.columns : canvas.grid.columns;\n}\n\n// activeBreakpoint picks the breakpoint whose min width is the largest one that still fits the\n// width (falling back to 'base'). Deterministic given equal widths by preferring the wider min.\nexport function activeBreakpoint(breakpoints: Breakpoints, width: number): string {\n let best = BASE_BREAKPOINT;\n let bestWidth = -1;\n for (const [name, spec] of Object.entries(breakpoints)) {\n const minWidth = breakpointMinWidth(spec);\n if (width >= minWidth && minWidth > bestWidth) {\n best = name;\n bestWidth = minWidth;\n }\n }\n return best;\n}\n\n// The placement a widget shows at a breakpoint: its own entry, or its base one.\nexport function resolvePlacement(layout: WidgetLayout, breakpoint: string): Placement {\n return Object.prototype.hasOwnProperty.call(layout, breakpoint) ? layout[breakpoint] : layout[BASE_BREAKPOINT];\n}\n\n// Every breakpoint a set of widgets can be shown at: the declared ones, and any a widget carries\n// an entry for (an entry for an undeclared breakpoint is never shown, but it is still checked).\nexport function layoutBreakpoints(breakpoints: Breakpoints, widgets: readonly { layout: WidgetLayout }[]): string[] {\n const names = new Set<string>([BASE_BREAKPOINT, ...Object.keys(breakpoints)]);\n for (const w of widgets) for (const bp of Object.keys(w.layout)) names.add(bp);\n return [...names];\n}\n\n// ---- Grid footprints ---------------------------------------------------------------\n\nexport function gridFootprintsOverlap(a: GridPlacement, b: GridPlacement): boolean {\n return a.col < b.col + b.cols && b.col < a.col + a.cols && a.row < b.row + b.rows && b.row < a.row + a.rows;\n}\n\nexport interface PlacedGrid {\n id: string;\n placement: GridPlacement;\n}\n\n// pushDown resolves every collision among grid footprints by moving widgets DOWN, never sideways,\n// so each keeps its column and its span: the `first` widgets are placed where they are (in the\n// order given), then the rest in reading order (row, then column, then their order in the input),\n// each at its own row or, if that collides, at the first row below where it no longer does. This\n// is react-grid-layout's collision rule without the upward compaction, so nothing that was not in\n// the way moves. Returns the same placement object for every widget that did not move.\nexport function pushDown(items: readonly PlacedGrid[], first: readonly string[] = []): Map<string, GridPlacement> {\n const order = items.map((item, index) => ({ ...item, index }));\n const priority = new Map(first.map((id, i) => [id, i]));\n order.sort((a, b) => {\n const pa = priority.get(a.id);\n const pb = priority.get(b.id);\n if (pa !== undefined || pb !== undefined) return (pa ?? Infinity) - (pb ?? Infinity);\n return a.placement.row - b.placement.row || a.placement.col - b.placement.col || a.index - b.index;\n });\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n for (const item of order) {\n let p = item.placement;\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.row + q.rows)) };\n }\n placed.push(p);\n out.set(item.id, p);\n }\n return out;\n}\n\n// pushAside is how an EDIT settles the widgets around the one it changed, the Android launcher's\n// rule: the `first` widgets land exactly where they were put (resolved among themselves as\n// pushDown would), a widget their footprint now covers is DISPLACED, and every other widget stays\n// exactly where it is, so a gap the author left on purpose survives an unrelated edit. A displaced\n// widget keeps its column and its span and settles at the free row NEAREST the one it had,\n// searching up as well as down, so a widget dropped onto a neighbour swaps with it (the neighbour\n// takes the room the dragged one left) instead of shoving it further down the board; on a tie it\n// goes up. Moving up, it may not cover any widget, settled or not yet visited; moving down, it\n// displaces in turn whatever it lands on, which settles by the same rule. Nothing undisplaced is\n// ever pulled up into a gap. Deterministic, and the identity on a board with no collision; returns\n// the same placement object for every widget that did not move.\nexport function pushAside(items: readonly PlacedGrid[], first: readonly string[] = []): Map<string, GridPlacement> {\n const order = items.map((item, index) => ({ ...item, index }));\n const priority = new Map(first.map((id, i) => [id, i]));\n order.sort((a, b) => {\n const pa = priority.get(a.id);\n const pb = priority.get(b.id);\n if (pa !== undefined || pb !== undefined) return (pa ?? Infinity) - (pb ?? Infinity);\n return a.placement.row - b.placement.row || a.placement.col - b.placement.col || a.index - b.index;\n });\n const pending = new Map(order.map((item) => [item.id, item.placement]));\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n const clear = (p: GridPlacement, others: Iterable<GridPlacement>) => {\n for (const q of others) if (gridFootprintsOverlap(p, q)) return false;\n return true;\n };\n for (const item of order) {\n pending.delete(item.id);\n let p = item.placement;\n if (priority.has(item.id)) {\n // A widget the edit put somewhere goes exactly there, unless an earlier one of them is.\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.row + q.rows)) };\n }\n } else if (!clear(p, placed)) {\n const from = p.row;\n for (let k = 1; ; k++) {\n const up: GridPlacement = { ...p, row: from - k };\n if (up.row >= 0 && clear(up, placed) && clear(up, pending.values())) {\n p = up;\n break;\n }\n const down: GridPlacement = { ...p, row: from + k };\n if (clear(down, placed)) {\n p = down;\n break;\n }\n }\n }\n placed.push(p);\n out.set(item.id, p);\n }\n return out;\n}\n\n// firstFreeSlot is the first position, scanning rows top to bottom and columns left to right, at\n// which a cols x rows footprint fits inside `columns` without touching any of `occupied`. A\n// footprint wider than the grid is placed at column 0 (it overruns; the lint reports it).\nexport function firstFreeSlot(\n occupied: readonly GridPlacement[],\n cols: number,\n rows: number,\n columns: number,\n): { col: number; row: number } {\n const lastCol = Math.max(0, columns - cols);\n const bottom = occupied.reduce((m, p) => Math.max(m, p.row + p.rows), 0);\n for (let row = 0; row <= bottom; row++) {\n for (let col = 0; col <= lastCol; col++) {\n const probe: GridPlacement = { mode: 'grid', col, row, cols, rows };\n if (!occupied.some((q) => gridFootprintsOverlap(probe, q))) return { col, row };\n }\n }\n return { col: 0, row: bottom };\n}\n\n// ---- Rectangles --------------------------------------------------------------------\n\nexport interface Rect {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n// The grid's rule in px: n cells cover n*cell + (n-1)*gutter.\nexport function spanPx(n: number, cell: number, gutter: number): number {\n return n * cell + (n - 1) * gutter;\n}\n\n// Where a grid footprint lands, relative to the grid's top-left corner (inside the padding).\nexport function gridRect(p: GridPlacement, cell: number, gutter: Gutter): Rect {\n return {\n x: p.col * (cell + gutter.x),\n y: p.row * (cell + gutter.y),\n w: spanPx(p.cols, cell, gutter.x),\n h: spanPx(p.rows, cell, gutter.y),\n };\n}\n\n// The fraction across (fx) and down (fy) a box an anchor point sits at.\nexport function anchorFraction(anchor: PlacementAnchor): { fx: number; fy: number } {\n const fx = anchor.endsWith('left') ? 0 : anchor.endsWith('right') ? 1 : 0.5;\n const fy = anchor.startsWith('top') ? 0 : anchor.startsWith('bottom') ? 1 : 0.5;\n return { fx, fy };\n}\n\n// Where a relative widget lands, given its host's box: the host's `anchor` point, minus this\n// widget's `self` point, plus the offset in cell pitches.\nexport function relativeRect(p: RelativePlacement, host: Rect, cell: number, gutter: Gutter): Rect {\n const w = 'fill' in p.size ? host.w : spanPx(p.size.cols, cell, gutter.x);\n const h = 'fill' in p.size ? host.h : spanPx(p.size.rows, cell, gutter.y);\n const a = anchorFraction(p.anchor);\n const s = anchorFraction(p.self ?? p.anchor);\n return {\n x: host.x + a.fx * host.w - s.fx * w + (p.offset?.x ?? 0) * (cell + gutter.x),\n y: host.y + a.fy * host.h - s.fy * h + (p.offset?.y ?? 0) * (cell + gutter.y),\n w,\n h,\n };\n}\n\n// The box of every widget at a breakpoint, for a cell size: grid footprints first, then each\n// relative widget against its host (a host that is itself relative is resolved first; the parser\n// guarantees the chain is at most one level deep and that every host exists). A widget whose\n// host is missing (an unparsed definition) is left out rather than guessed.\nexport function layoutRects(\n widgets: readonly WidgetInstance[],\n breakpoint: string,\n cell: number,\n gutter: Gutter,\n): Map<string, Rect> {\n const rects = new Map<string, Rect>();\n const relatives: { id: string; p: RelativePlacement }[] = [];\n for (const w of widgets) {\n const p = resolvePlacement(w.layout, breakpoint);\n if (p.mode === 'grid') rects.set(w.id, gridRect(p, cell, gutter));\n else relatives.push({ id: w.id, p });\n }\n // Two passes reach one level of relative-on-relative.\n for (let pass = 0; pass < 2; pass++) {\n for (const { id, p } of relatives) {\n const host = rects.get(p.host);\n if (host && !rects.has(id)) rects.set(id, relativeRect(p, host, cell, gutter));\n }\n }\n return rects;\n}\n\n// ---- Frames ------------------------------------------------------------------------\n\n// What each variant looks like before any field is set.\nconst FRAME_PRESETS: Readonly<Record<ResolvedFrame['variant'], ResolvedFrame>> = {\n card: {\n variant: 'card',\n border: { width: 1, radius: 'md', color: 'border' },\n padding: 'space-3',\n background: 'card',\n header: 'auto',\n },\n flat: {\n variant: 'flat',\n border: { width: 0, radius: 'md', color: 'border' },\n padding: 'space-3',\n background: 'card',\n header: 'auto',\n },\n none: {\n variant: 'none',\n border: { width: 0, radius: 'none', color: 'border' },\n padding: 'space-0',\n background: 'transparent',\n header: 'hide',\n },\n};\n\n// resolveFrame decides every frame field for one widget. The canvas default and the widget's own\n// frame merge field by field (a widget that sets only `border.color` keeps the canvas's border\n// width); the merged variant picks the preset the remaining fields fall back to.\nexport function resolveFrame(canvasFrame: WidgetFrameSpec | undefined, widgetFrame: WidgetFrameSpec | undefined): ResolvedFrame {\n const c = canvasFrame ?? {};\n const w = widgetFrame ?? {};\n const variant = w.variant ?? c.variant ?? 'card';\n const preset = FRAME_PRESETS[variant];\n return {\n variant,\n border: { ...preset.border, ...c.border, ...w.border },\n padding: w.padding ?? c.padding ?? preset.padding,\n background: w.background ?? c.background ?? preset.background,\n header: w.header ?? c.header ?? preset.header,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// isRecord is the one \"plain JSON object\" test the parsers share: an object that is neither null\n// nor an array. One copy, because every module that reads a raw document needs it and each private\n// copy is shipped again in every bundle that parses a board.\nexport function isRecord(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The one-way conversion of a definition stored before schemaVersion 2 onto the square-cell grid.\n//\n// Before 2, a widget was a box on a grid of fluid columns (24 by default) and FIXED-PX rows (40\n// by default), layered by `z` and nudged by a pixel `offset`. Now cells are square, every\n// distance is in cells, and two grid widgets cannot overlap. There is no exact mapping between\n// the two (a fixed-px row is a different fraction of a square cell at every width), so this is\n// an APPROXIMATION, and it says so: the parser marks the result `convertedFrom`, and an editor\n// shows \"converted from the previous grid -- review the layout\" until the next save writes the\n// current format.\n//\n// The mapping:\n// columns start and end lines scale by newColumns / oldColumns and round, so two boxes that\n// met at a line still meet at one;\n// rows start and end lines go to px on the old grid (row * (rowHeight + gap)) and back to\n// cells at the reference width (1366 px, the common laptop), where a new cell is\n// (1366 - (columns-1) * gutter) / columns;\n// gutter the old gap, unchanged;\n// z, offset dropped: there is no layering on the grid, and a pixel nudge has no meaning in\n// cells. Overlap was the only thing either was for;\n// overlap whatever rounding (or the old z-stacking) leaves overlapping is pushed DOWN until it\n// fits, keeping every widget below the ones it was below (restack), so the converted\n// definition parses: the overlap refusal is not relaxed for converted documents.\n//\n// It works on the RAW document, before the parser sees it, so every widget is converted the same\n// way, a widget of a type this build does not know included: its stored object is kept verbatim\n// and written back on save, so its layout must already be in the current format.\n\nimport {\n BASE_BREAKPOINT,\n DEFAULT_GRID,\n cellSizeAt,\n gridFootprintsOverlap,\n gutterPx,\n} from './grid';\nimport { CURRENT_SCHEMA_VERSION, type CanvasGrid, type GridPlacement } from './types';\nimport { isRecord } from './is-record';\n\n// The format this conversion produces: the square-cell grid. Later formats are layered on by\n// their own conversions (./legacy-window), never folded into this one.\nexport const GRID_SCHEMA_VERSION = 2;\n\n// The width the old fixed-px rows are read at.\nexport const LEGACY_REFERENCE_WIDTH = 1366;\n\nconst LEGACY_DEFAULTS = { columns: 24, gap: 8, rowHeight: 40 };\n\nfunction num(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// The format a stored document is in: its schemaVersion when it states one. A document that\n// states none (no producer has written one without it, so it is hand-written or very old) is\n// read by its SHAPE: an old grid (`rowHeight`, `gap`), an old box (`colSpan`, `rowSpan`, `z`), or\n// a box with neither a `mode` nor `cols` anywhere makes it 1, and anything else is the current\n// format. Guessing 1 for every unversioned document would put a hand-written current one\n// through the converter and mangle it.\nexport function storedSchemaVersion(raw: Record<string, unknown>): number {\n if (typeof raw.schemaVersion === 'number' && Number.isFinite(raw.schemaVersion)) return raw.schemaVersion;\n const grid = isRecord(raw.canvas) && isRecord(raw.canvas.grid) ? raw.canvas.grid : {};\n if ('rowHeight' in grid || 'gap' in grid) return 1;\n const widgets = Array.isArray(raw.widgets) ? raw.widgets : [];\n for (const w of widgets) {\n if (!isRecord(w) || !isRecord(w.layout)) continue;\n for (const box of Object.values(w.layout)) {\n if (!isRecord(box)) continue;\n if ('colSpan' in box || 'rowSpan' in box || 'z' in box) return 1;\n // A box with neither a mode nor a span in cells is not a current placement (one with no\n // mode is a grid placement, and a grid placement needs `cols`): the old parser read it,\n // filling every field it left out, so the old format is the reading that opens.\n if (!('mode' in box) && !('cols' in box)) return 1;\n }\n }\n return CURRENT_SCHEMA_VERSION;\n}\n\ninterface LegacyGrid {\n columns: number;\n rowPitch: number;\n gutter: CanvasGrid['gutter'];\n}\n\nfunction legacyGrid(raw: unknown): LegacyGrid {\n const rec = isRecord(raw) ? raw : {};\n const columns = Math.max(1, Math.round(num(rec, 'columns', LEGACY_DEFAULTS.columns)));\n const rowHeight = Math.max(1, num(rec, 'rowHeight', LEGACY_DEFAULTS.rowHeight));\n let gapRow = LEGACY_DEFAULTS.gap;\n let gapCol = LEGACY_DEFAULTS.gap;\n if (typeof rec.gap === 'number' && Number.isFinite(rec.gap)) {\n gapRow = gapCol = Math.max(0, rec.gap);\n } else if (isRecord(rec.gap)) {\n gapRow = Math.max(0, num(rec.gap, 'row', LEGACY_DEFAULTS.gap));\n gapCol = Math.max(0, num(rec.gap, 'col', LEGACY_DEFAULTS.gap));\n }\n return {\n columns,\n rowPitch: rowHeight + gapRow,\n gutter: gapRow === gapCol ? gapCol : { x: gapCol, y: gapRow },\n };\n}\n\n// A widget's rows on the OLD grid (start line, end line) and its old start column.\ninterface OldSpan {\n row: number;\n end: number;\n col: number;\n}\n\ninterface StackItem {\n index: number;\n placement: GridPlacement;\n old: OldSpan;\n}\n\n// restack places the converted boxes of one breakpoint so that none overlaps and the old stacking\n// survives, returning only the ones that moved. In the old grid's reading order (row, column,\n// then document order), each widget goes at its converted row, but never above the bottom of a\n// widget it was BELOW on the old grid and still shares a column with, and then further down past\n// anything it would overlap. The first rule is what a plain push-down lacks: rounding collapses\n// short old rows onto one new row, the push then moves a widget down a long way, and a widget\n// that was under it -- placed later, colliding with nothing -- would stay where it was and end\n// up ABOVE it. Old boxes that overlapped (layered with z) have no above/below, so only the\n// collision rule applies to them.\nfunction restack(items: StackItem[]): Map<number, GridPlacement> {\n const order = [...items].sort((a, b) => a.old.row - b.old.row || a.old.col - b.old.col || a.index - b.index);\n const placed: StackItem[] = [];\n const moved = new Map<number, GridPlacement>();\n for (const item of order) {\n let p = item.placement;\n const sharesColumn = (q: GridPlacement) => p.col < q.col + q.cols && q.col < p.col + p.cols;\n const floor = placed\n .filter((q) => q.old.end <= item.old.row && sharesColumn(q.placement))\n .reduce((m, q) => Math.max(m, q.placement.row + q.placement.rows), 0);\n if (floor > p.row) p = { ...p, row: floor };\n for (;;) {\n const hits = placed.filter((q) => gridFootprintsOverlap(p, q.placement));\n if (hits.length === 0) break;\n p = { ...p, row: Math.max(...hits.map((q) => q.placement.row + q.placement.rows)) };\n }\n placed.push({ ...item, placement: p });\n if (p !== item.placement) moved.set(item.index, p);\n }\n return moved;\n}\n\n// convertLegacyDefinition returns the document in the current format. The input is not modified.\nexport function convertLegacyDefinition(raw: Record<string, unknown>): Record<string, unknown> {\n const canvasIn = isRecord(raw.canvas) ? raw.canvas : {};\n const old = legacyGrid(canvasIn.grid);\n const grid: CanvasGrid = { ...DEFAULT_GRID, gutter: old.gutter };\n const columns = grid.columns;\n const pitch = cellSizeAt(grid, LEGACY_REFERENCE_WIDTH) + gutterPx(grid).y;\n\n const toCols = (line: number) => Math.round((line * columns) / old.columns);\n const toRows = (line: number) => Math.round((line * old.rowPitch) / pitch);\n const convertBox = (box: Record<string, unknown>): GridPlacement => {\n const col = Math.max(0, Math.round(num(box, 'col', 0)));\n const colSpan = Math.max(1, Math.round(num(box, 'colSpan', 1)));\n const row = Math.max(0, Math.round(num(box, 'row', 0)));\n const rowSpan = Math.max(1, Math.round(num(box, 'rowSpan', 1)));\n const c0 = Math.min(toCols(col), columns - 1);\n const c1 = Math.min(Math.max(toCols(col + colSpan), c0 + 1), columns);\n const r0 = toRows(row);\n const r1 = Math.max(toRows(row + rowSpan), r0 + 1);\n return { mode: 'grid', col: c0, row: r0, cols: c1 - c0, rows: r1 - r0 };\n };\n\n // Each widget's old box per breakpoint is kept beside its conversion, because the push below\n // works from where a widget was on the OLD grid, not where rounding put it.\n const oldBoxes = new Map<number, Record<string, OldSpan>>();\n const widgetsIn = Array.isArray(raw.widgets) ? raw.widgets : [];\n const widgets = widgetsIn.map((w, i) => {\n if (!isRecord(w) || !isRecord(w.layout)) return w;\n const layout: Record<string, unknown> = {};\n const olds: Record<string, OldSpan> = {};\n for (const [bp, box] of Object.entries(w.layout)) {\n // An entry that is not a box (null, a number) was skipped by the old parser and is skipped\n // by the current one, which shows the base placement there instead. It is dropped here so\n // the push below sees the placement the parser checks.\n if (!isRecord(box) || bp === '__proto__') continue;\n layout[bp] = convertBox(box);\n const row = Math.max(0, Math.round(num(box, 'row', 0)));\n olds[bp] = { row, end: row + Math.max(1, Math.round(num(box, 'rowSpan', 1))), col: Math.max(0, Math.round(num(box, 'col', 0))) };\n }\n oldBoxes.set(i, olds);\n return { ...w, layout };\n });\n\n // Push apart whatever the rounding, or the old layering, left overlapping: base first, since\n // every other breakpoint inherits it, then each other breakpoint over what it shows. A widget\n // shows its own entry at a breakpoint, or its base one (resolvePlacement's rule, which is the\n // one the parser checks overlap by).\n const byBreakpoint = new Set<string>([BASE_BREAKPOINT]);\n for (const w of widgets) if (isRecord(w) && isRecord(w.layout)) for (const bp of Object.keys(w.layout)) byBreakpoint.add(bp);\n for (const bp of byBreakpoint) {\n const items: StackItem[] = [];\n widgets.forEach((w, i) => {\n if (!isRecord(w) || !isRecord(w.layout)) return;\n const layout = w.layout as Record<string, unknown>;\n const own = Object.prototype.hasOwnProperty.call(layout, bp);\n const p = (own ? layout[bp] : layout[BASE_BREAKPOINT]) as GridPlacement | undefined;\n const old = oldBoxes.get(i)?.[own ? bp : BASE_BREAKPOINT];\n if (p && isRecord(p) && p.mode === 'grid' && old) items.push({ index: i, placement: p, old });\n });\n for (const [index, moved] of restack(items)) {\n const w = widgets[index] as Record<string, unknown>;\n (w.layout as Record<string, unknown>)[bp] = moved;\n }\n }\n\n const breakpoints: Record<string, number> = {};\n if (isRecord(canvasIn.breakpoints)) {\n for (const [name, width] of Object.entries(canvasIn.breakpoints)) {\n if (typeof width === 'number' && Number.isFinite(width)) breakpoints[name] = width;\n }\n }\n return {\n ...raw,\n schemaVersion: GRID_SCHEMA_VERSION,\n canvas: { ...canvasIn, grid, breakpoints },\n widgets,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The one-way conversion of a definition stored before schemaVersion 3 onto the time span.\n//\n// Before 3, a `timeseries-chart` kept a rolling SAMPLE COUNT (`options.window`, default 300) and\n// every chart seeded a fixed hour of history whatever that count was. Now it draws a SPAN\n// (`options.windowSeconds`, or the board's time range when it names none).\n//\n// \uD83D\uDD34 There is no honest mapping from a count to a span. N samples cover N x (the sampling\n// interval) seconds, and the interval is a property of the device, not of the dashboard: 300\n// samples is five minutes from a device reporting every second and a day from one reporting every\n// five minutes. Any factor chosen here would be a guess painted as a conversion. So the rule is:\n//\n// - `options.window` is DROPPED from a time-series chart, and the chart follows the board's\n// time range (one hour unless the board names another -- which is exactly the window every\n// chart already seeded, so what the chart showed on load does not change);\n// - each such widget is FLAGGED (by position in the widget list), and the parser names them so an\n// editor can tell the author which charts to look at. A chart that was deliberately short or\n// long gets its span pinned by hand.\n//\n// The widget's stored options generation (`widget.optionsVersion`) is moved from 1 to 2 on a\n// time-series chart, because the options changed meaning; without it the chart would load as a\n// \"settings version differs\" placeholder. Other widgets keep their `window`: it is a memory bound\n// on their rolling buffer that only the chart ever drew, and is unchanged.\n//\n// Like the grid conversion it works on the RAW document and does not modify its input. It is\n// idempotent: its own output carries no `window` and no version-1 reference, so converting it\n// again changes nothing and flags nothing.\n\nimport { CURRENT_SCHEMA_VERSION } from './types';\nimport { isRecord } from './is-record';\n\nconst TIME_SERIES = 'timeseries-chart';\n\nexport interface WindowConversion {\n doc: Record<string, unknown>;\n // Indices into `doc.widgets` of the charts whose sample-count window was dropped.\n flagged: number[];\n}\n\nexport function convertSampleWindows(raw: Record<string, unknown>): WindowConversion {\n const widgetsIn = Array.isArray(raw.widgets) ? raw.widgets : [];\n const flagged: number[] = [];\n const widgets = widgetsIn.map((w, i) => {\n if (!isRecord(w) || w.type !== TIME_SERIES) return w;\n let out: Record<string, unknown> = w;\n if (isRecord(w.options) && Object.prototype.hasOwnProperty.call(w.options, 'window')) {\n const { window: _count, ...options } = w.options;\n out = { ...out, options };\n flagged.push(i);\n }\n if (isRecord(w.widget) && w.widget.optionsVersion === 1) {\n out = { ...out, widget: { ...w.widget, optionsVersion: 2 } };\n }\n return out;\n });\n return { doc: { ...raw, schemaVersion: CURRENT_SCHEMA_VERSION, widgets }, flagged };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { DashboardDefinition } from './types';\n\n// The board's time range: a SPAN (seconds back from \"now\"), never a sample count. A time-series\n// widget draws the last `spanSeconds`; the viewer may pick another span from the presets, and may\n// PAUSE, which freezes what the board shows at that instant. Everything here is pure -- the\n// clock is an argument -- so the window arithmetic, the bucket choice and the deep-link parsing\n// are testable without a DOM.\n\n// The presets a viewer picks from, shortest first. The id is also the deep-link token\n// (`?range=1h`), so it is a contract: renaming one breaks every saved link.\nexport const TIME_RANGE_PRESETS = [\n { id: '15m', seconds: 15 * 60 },\n { id: '1h', seconds: 60 * 60 },\n { id: '6h', seconds: 6 * 60 * 60 },\n { id: '24h', seconds: 24 * 60 * 60 },\n { id: '7d', seconds: 7 * 24 * 60 * 60 },\n] as const;\n\nexport type TimeRangePresetId = (typeof TIME_RANGE_PRESETS)[number]['id'];\n\n// The span a board uses when its definition names none. One hour is what every chart seeded\n// before the span was configurable, so a board that never set one reads as it always did.\nexport const DEFAULT_TIME_RANGE_SECONDS = 60 * 60;\n\n// The bounds of a span a definition may author: a minute to the longest preset. Outside them\n// the value is not a usable span (a zero-length window draws nothing; a year would ask the\n// backend for an unbounded read), so it is ignored, not clamped -- clamping would silently\n// turn a typo into a different window than anyone wrote.\nexport const MIN_TIME_RANGE_SECONDS = 60;\nexport const MAX_TIME_RANGE_SECONDS = 7 * 24 * 60 * 60;\n\nexport function isValidSpanSeconds(v: unknown): v is number {\n return (\n typeof v === 'number' &&\n Number.isInteger(v) &&\n v >= MIN_TIME_RANGE_SECONDS &&\n v <= MAX_TIME_RANGE_SECONDS\n );\n}\n\n// The board's range state, owned by the host (the viewer header) and read by the renderer.\n// `pausedAt` is the instant (epoch ms) the board froze at; it is set exactly while `paused`.\n//\n// ABSOLUTE ranges (a from-to window, for incident review and a shareable \"02:00 to 04:00\" link) are\n// an ADDITIVE variant of this same shape, not a new one: `absolute` is present only for them, and\n// when present the rolling fields are a faithful projection of the window as a FROZEN rolling\n// range -- `paused` is true, `pausedAt` is the window's end and `spanSeconds` its length. A widget\n// that only understands rolling ranges (every third-party widget written before this variant)\n// therefore computes exactly the absolute window, with its live tail already off, and needs no\n// change. A widget that wants to say \"this is a fixed window\" reads `absolute`.\nexport interface AbsoluteRange {\n // Epoch ms, whole seconds. fromMs < toMs.\n fromMs: number;\n toMs: number;\n}\n\nexport interface TimeRangeState {\n spanSeconds: number;\n paused: boolean;\n pausedAt?: number;\n absolute?: AbsoluteRange;\n}\n\n// Bounds of an ABSOLUTE window. A minute to 31 days: shorter draws nothing, longer is a bulk read\n// the bucketed history does not need to serve on a board. `from` may not predate 2000 (a typo in\n// the year, an epoch in seconds read as ms) and `to` may not lie in the future beyond a minute of\n// clock skew -- a window that has not happened yet has no history to show.\nexport const MIN_ABSOLUTE_SPAN_SECONDS = 60;\nexport const MAX_ABSOLUTE_SPAN_SECONDS = 31 * 24 * 60 * 60;\nexport const EARLIEST_ABSOLUTE_MS = Date.UTC(2000, 0, 1);\nexport const ABSOLUTE_FUTURE_SKEW_MS = 60 * 1000;\n\nexport type AbsoluteRangeProblem = 'unparseable' | 'order' | 'tooShort' | 'tooLong' | 'future' | 'tooEarly';\n\n// absoluteRangeProblem says why a from-to pair is not a usable window, or undefined when it is.\nexport function absoluteRangeProblem(fromMs: number, toMs: number, now: number): AbsoluteRangeProblem | undefined {\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs)) return 'unparseable';\n if (fromMs >= toMs) return 'order';\n if (fromMs < EARLIEST_ABSOLUTE_MS) return 'tooEarly';\n if (toMs > now + ABSOLUTE_FUTURE_SKEW_MS) return 'future';\n const seconds = (Math.floor(toMs / 1000) * 1000 - Math.floor(fromMs / 1000) * 1000) / 1000;\n if (seconds < MIN_ABSOLUTE_SPAN_SECONDS) return 'tooShort';\n if (seconds > MAX_ABSOLUTE_SPAN_SECONDS) return 'tooLong';\n return undefined;\n}\n\nexport function isAbsolute(state: TimeRangeState): boolean {\n return state.absolute !== undefined;\n}\n\n// absoluteRange builds the state for a window, or the problem that makes it unusable. Ends are cut\n// to whole seconds so the projected span is exact.\nexport function absoluteRange(\n fromMs: number,\n toMs: number,\n now: number = Date.now(),\n): { state: TimeRangeState } | { problem: AbsoluteRangeProblem } {\n const problem = absoluteRangeProblem(fromMs, toMs, now);\n if (problem) return { problem };\n const from = Math.floor(fromMs / 1000) * 1000;\n const to = Math.floor(toMs / 1000) * 1000;\n return {\n state: { spanSeconds: (to - from) / 1000, paused: true, pausedAt: to, absolute: { fromMs: from, toMs: to } },\n };\n}\n\n// leaveAbsolute returns to a LIVE rolling range of the same length (capped at the longest preset).\nexport function leaveAbsolute(state: TimeRangeState): TimeRangeState {\n if (!state.absolute) return state;\n return { spanSeconds: Math.min(state.spanSeconds, MAX_TIME_RANGE_SECONDS), paused: false };\n}\n\nexport function initialTimeRange(\n defaultSeconds: number | undefined,\n link: { spanSeconds?: number; paused?: boolean; absolute?: AbsoluteRange } = {},\n now: number = Date.now(),\n): TimeRangeState {\n if (link.absolute) {\n const made = absoluteRange(link.absolute.fromMs, link.absolute.toMs, now);\n if ('state' in made) return made.state;\n }\n const spanSeconds = link.spanSeconds ?? (isValidSpanSeconds(defaultSeconds) ? defaultSeconds : DEFAULT_TIME_RANGE_SECONDS);\n return link.paused ? { spanSeconds, paused: true, pausedAt: now } : { spanSeconds, paused: false };\n}\n\n// Freezing keeps the span and stamps the instant; unfreezing drops the stamp. Both are no-ops\n// when already in the asked state, so a double click cannot move `pausedAt` forward.\nexport function pauseRange(state: TimeRangeState, now: number): TimeRangeState {\n return state.paused ? state : { spanSeconds: state.spanSeconds, paused: true, pausedAt: now };\n}\n\nexport function resumeRange(state: TimeRangeState): TimeRangeState {\n if (state.absolute) return leaveAbsolute(state);\n return state.paused ? { spanSeconds: state.spanSeconds, paused: false } : state;\n}\n\n// A fixed window keeps its length: picking a span is done through leaveAbsolute (a preset click),\n// never by quietly rewriting a window someone chose.\nexport function withSpan(state: TimeRangeState, spanSeconds: number): TimeRangeState {\n if (state.absolute) return state;\n return isValidSpanSeconds(spanSeconds) ? { ...state, spanSeconds } : state;\n}\n\n// The instant the board's window ENDS at: now while live, the pause instant while frozen.\nexport function rangeEnd(state: TimeRangeState, now: number): number {\n return state.paused && state.pausedAt !== undefined ? state.pausedAt : now;\n}\n\nexport interface RangeWindow {\n startTime: string;\n endTime: string;\n}\n\n// from = end - span, to = end, as RFC 3339 instants.\nexport function rangeWindow(state: TimeRangeState, now: number): RangeWindow {\n const end = rangeEnd(state, now);\n return {\n startTime: new Date(end - state.spanSeconds * 1000).toISOString(),\n endTime: new Date(end).toISOString(),\n };\n}\n\n// The most buckets one history seed asks for per measurement. The backend refuses a read past\n// 10 000 buckets; 240 is far inside that, and it is what a chart can show: past a few hundred\n// points a line is smoothed by the pixels anyway, so asking for more only costs the read.\nexport const MAX_SEED_BUCKETS = 240;\n\n// The finest bucket a seed uses, and the base bucket of the pre-aggregated one-minute summaries.\nconst BASE_BUCKET_SECONDS = 60;\n\n// bucketSecondsFor picks the bucket width for a span: the smallest whole number of minutes that\n// keeps the read within MAX_SEED_BUCKETS. A multiple of 60 matters beyond tidiness -- the backend\n// serves it from the one-minute summaries instead of scanning raw readings, which is the\n// difference between a 7-day read being cheap and being a table scan. The result: 15m and 1h\n// stay at one minute; 6h is 2 min; 24h is 6 min; 7d is 42 min.\nexport function bucketSecondsFor(spanSeconds: number): number {\n const wanted = Math.ceil(spanSeconds / MAX_SEED_BUCKETS);\n return Math.max(BASE_BUCKET_SECONDS, Math.ceil(wanted / BASE_BUCKET_SECONDS) * BASE_BUCKET_SECONDS);\n}\n\n// The number of buckets a seed over `spanSeconds` reads, at the width bucketSecondsFor picks.\nexport function seedBucketCount(spanSeconds: number): number {\n return Math.ceil(spanSeconds / bucketSecondsFor(spanSeconds));\n}\n\n// ---- Deep link --------------------------------------------------------------\n\n// parseRangeParam reads `?range=`: a preset id, exactly. Anything else (an unknown id, an empty\n// value, `1H`, `3600`, a span in another unit) is undefined, so the caller keeps the board's\n// default. A link must never be able to ask for a window the control cannot show.\nexport function parseRangeParam(value: string | null | undefined): number | undefined {\n if (!value) return undefined;\n return TIME_RANGE_PRESETS.find((p) => p.id === value)?.seconds;\n}\n\n// parsePausedParam reads `?paused=`: only the literal `1` pauses. `true`, `yes`, `0`, an empty\n// value and garbage all mean \"not paused\", so a malformed link opens live rather than frozen\n// on an unannounced instant.\nexport function parsePausedParam(value: string | null | undefined): boolean {\n return value === '1';\n}\n\n// RFC 3339 with an EXPLICIT zone (`Z` or `+02:00`). A date-time with no zone is ambiguous -- whose\n// 02:00? -- and a shared link must mean the same window to everyone who opens it, so it is refused\n// rather than read in the opener's zone.\nconst RFC3339_WITH_ZONE = /^\\d{4}-\\d{2}-\\d{2}[Tt]\\d{2}:\\d{2}(:\\d{2}(\\.\\d{1,9})?)?([Zz]|[+-]\\d{2}:\\d{2})$/;\n\nexport function parseInstantParam(value: string | null | undefined): number | undefined {\n if (!value || !RFC3339_WITH_ZONE.test(value)) return undefined;\n const ms = Date.parse(value);\n return Number.isNaN(ms) ? undefined : ms;\n}\n\nexport interface AbsoluteLinkRejection {\n reason: AbsoluteRangeProblem | 'incomplete';\n from: string;\n to: string;\n}\n\n// What a deep link's `from`/`to` came to: a usable window, or the reason it was ignored (with the\n// raw values, so the host can show them). Neither parameter present is neither.\nexport function parseAbsoluteParams(\n from: string | null | undefined,\n to: string | null | undefined,\n now: number = Date.now(),\n): { absolute?: AbsoluteRange; rejected?: AbsoluteLinkRejection } {\n if (from == null && to == null) return {};\n const raw = { from: from ?? '', to: to ?? '' };\n if (!from || !to) return { rejected: { reason: 'incomplete', ...raw } };\n const fromMs = parseInstantParam(from);\n const toMs = parseInstantParam(to);\n if (fromMs === undefined || toMs === undefined) return { rejected: { reason: 'unparseable', ...raw } };\n const made = absoluteRange(fromMs, toMs, now);\n if ('problem' in made) return { rejected: { reason: made.problem, ...raw } };\n return { absolute: made.state.absolute };\n}\n\n// The query values that reproduce a window: UTC instants, so the link means one thing everywhere.\nexport function absoluteToParams(a: AbsoluteRange): { from: string; to: string } {\n return { from: new Date(a.fromMs).toISOString(), to: new Date(a.toMs).toISOString() };\n}\n\nexport interface TimeRangeLink {\n spanSeconds?: number;\n paused?: boolean;\n absolute?: AbsoluteRange;\n // Present when `from`/`to` were given but unusable; the host shows a note and opens rolling.\n rejected?: AbsoluteLinkRejection;\n}\n\n// timeRangeFromSearch reads the rolling parameters, `range` and `paused`, from a query string.\n// Never throws. It ignores `from`/`to`, so what it returns for any address is what it returned\n// before fixed windows existed; a host that opens fixed windows reads timeRangeLinkFromSearch.\nexport function timeRangeFromSearch(search: string): { spanSeconds?: number; paused?: boolean } {\n let params: URLSearchParams;\n try {\n params = new URLSearchParams(search);\n } catch {\n return {};\n }\n const link: { spanSeconds?: number; paused?: boolean } = {};\n const spanSeconds = parseRangeParam(params.get('range'));\n if (spanSeconds !== undefined) link.spanSeconds = spanSeconds;\n if (parsePausedParam(params.get('paused'))) link.paused = true;\n return link;\n}\n\n// timeRangeLinkFromSearch reads every time-range parameter from a query string. Never throws. An\n// absolute window (`from`+`to`) wins over `range` and `paused`: it is the more specific ask.\nexport function timeRangeLinkFromSearch(search: string, now: number = Date.now()): TimeRangeLink {\n let params: URLSearchParams;\n try {\n params = new URLSearchParams(search);\n } catch {\n return {};\n }\n const abs = parseAbsoluteParams(params.get('from'), params.get('to'), now);\n if (abs.absolute) return { absolute: abs.absolute };\n const link: TimeRangeLink = timeRangeFromSearch(search);\n if (abs.rejected) link.rejected = abs.rejected;\n return link;\n}\n\n// The preset id for a span, or undefined when the span is none of them (a board default of\n// two hours, say): the control then shows the span's own label with no preset highlighted.\nexport function presetIdOf(spanSeconds: number): TimeRangePresetId | undefined {\n return TIME_RANGE_PRESETS.find((p) => p.seconds === spanSeconds)?.id;\n}\n\n// A compact label for any span: `15m`, `2h`, `3d`. A span that is not a whole number of the\n// largest unit that divides it falls to the next smaller one, so the label is always exact.\nexport function spanLabel(spanSeconds: number): string {\n if (spanSeconds % 86400 === 0) return `${spanSeconds / 86400}d`;\n if (spanSeconds % 3600 === 0) return `${spanSeconds / 3600}h`;\n if (spanSeconds % 60 === 0) return `${spanSeconds / 60}m`;\n return `${spanSeconds}s`;\n}\n\n// ---- Authoring ---------------------------------------------------------------\n\n// setBoardTimeRange writes the board's default span. The default (one hour) and an unusable value\n// are stored as NO range at all, so a board that never chose one stays byte-identical on save and\n// reads as it always did.\nexport function setBoardTimeRange(def: DashboardDefinition, defaultSeconds: number | undefined): DashboardDefinition {\n const { timeRange: _previous, ...rest } = def;\n if (!isValidSpanSeconds(defaultSeconds) || defaultSeconds === DEFAULT_TIME_RANGE_SECONDS) return rest;\n return { ...rest, timeRange: { defaultSeconds } };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Whether a stored widget can be rendered by an installed implementation.\n//\n// The compatibility key is (id, optionsVersion, sdkApiVersion). `version` is PROVENANCE: a\n// differing release still renders, and the editor shows a non-blocking \"authored with X,\n// rendering with Y\" notice (widgetVersionNotice). Only a settings-shape or SDK-generation\n// difference turns a widget into a placeholder, because only those change what the stored\n// options MEAN. \"Exact replay\" is claimed only when the versions are equal.\n//\n// React-free, and it reads a WidgetCatalog (descriptors), never an implementation, so the\n// parser and the headless validators can use it.\n\nimport type { WidgetCatalog } from './widget-catalog';\nimport type { WidgetDescriptor } from './widget-descriptor';\nimport type {\n DashboardDefinition,\n ResolvedWidgetInstance,\n UnresolvedWidgetReason,\n WidgetImplementationRef,\n WidgetInstance,\n} from './types';\nimport { UNKNOWN_WIDGET_TYPE, WIDGET_TYPES, type KnownWidgetInstance } from './types';\nimport { isRecord } from './is-record';\n\nconst BUILTIN_TYPES: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\n// isBuiltinWidget narrows a parsed widget to a built-in instance: not a placeholder and not\n// an extension. For code (the built-in editor panels) that is typed against the closed\n// built-in union and must not be handed an extension widget.\nexport function isBuiltinWidget(widget: WidgetInstance): widget is KnownWidgetInstance {\n return BUILTIN_TYPES.has(widget.type);\n}\n\n// isResolvedWidget narrows a parsed widget to one an installed implementation resolved (a\n// built-in or a registered extension): everything that is not a placeholder.\nexport function isResolvedWidget(widget: WidgetInstance): widget is ResolvedWidgetInstance {\n return widget.type !== UNKNOWN_WIDGET_TYPE;\n}\n\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\n\n// parseWidgetRef reads the stored `widget` metadata. `undefined` = absent (legacy / incomplete);\n// `null` = present but malformed, which must not be mistaken for absent.\nexport function parseWidgetRef(raw: unknown): WidgetImplementationRef | undefined | null {\n if (raw === undefined) return undefined;\n if (!isRecord(raw)) return null;\n const { version, optionsVersion, sdkApiVersion } = raw;\n if (typeof version !== 'string' || !SEMVER.test(version)) return null;\n if (typeof optionsVersion !== 'number' || !Number.isInteger(optionsVersion) || optionsVersion < 1) return null;\n const ref: WidgetImplementationRef = { version, optionsVersion };\n if (sdkApiVersion !== undefined) {\n if (typeof sdkApiVersion !== 'number' || !Number.isInteger(sdkApiVersion) || sdkApiVersion < 1) return null;\n ref.sdkApiVersion = sdkApiVersion;\n }\n return ref;\n}\n\n// checkWidgetCompatibility returns why the stored reference cannot be rendered by this\n// descriptor, or undefined when it can (a differing `version` alone is compatible).\nexport function checkWidgetCompatibility(\n id: string,\n ref: WidgetImplementationRef,\n descriptor: WidgetDescriptor,\n): UnresolvedWidgetReason | undefined {\n if (ref.optionsVersion !== descriptor.optionsVersion) {\n return {\n code: 'options-version',\n message: `\"${id}\" was saved with settings version ${ref.optionsVersion}; the installed widget uses settings version ${descriptor.optionsVersion}`,\n };\n }\n if (ref.sdkApiVersion !== undefined && ref.sdkApiVersion !== descriptor.sdkApiVersion) {\n return {\n code: 'sdk-incompatible',\n message: `\"${id}\" was authored for widget SDK API ${ref.sdkApiVersion}; the installed widget uses API ${descriptor.sdkApiVersion}`,\n };\n }\n return undefined;\n}\n\n// widgetVersionNotice is the non-blocking provenance message for a widget rendered by a\n// different release than the one it was authored with, or undefined when they match (or the\n// widget carries no version).\nexport function widgetVersionNotice(\n id: string,\n ref: WidgetImplementationRef | undefined,\n descriptor: WidgetDescriptor,\n): string | undefined {\n if (!ref || ref.version === descriptor.version) return undefined;\n return `\"${id}\" was authored with version ${ref.version}; rendering with version ${descriptor.version}`;\n}\n\n// classifyStoredWidget decides, for a stored widget object of a NON-reserved or versioned\n// type, whether the catalog resolves it. Returns the reason it does not, or undefined.\n// `builtin` selects the legacy rule: a built-in with no metadata is fine.\nexport function unresolvedReason(\n type: string,\n rawRef: unknown,\n catalog: WidgetCatalog | undefined,\n builtin: boolean,\n): UnresolvedWidgetReason | undefined {\n const ref = parseWidgetRef(rawRef);\n if (ref === null) {\n return { code: 'invalid-metadata', message: `\"${type}\" carries widget version metadata that is not valid` };\n }\n if (!catalog) {\n return builtin\n ? undefined\n : { code: 'unregistered', message: `\"${type}\" is not available: no widget catalog was supplied` };\n }\n const descriptor = catalog.get(type);\n if (!descriptor) {\n return {\n code: 'unregistered',\n message: builtin\n ? `\"${type}\" is not registered in this host`\n : `\"${type}\" is not installed in this host; add its package to the host's widget registry`,\n };\n }\n if (ref === undefined) {\n return builtin\n ? undefined\n : { code: 'unversioned', message: `\"${type}\" is stored without version metadata, so the installed widget cannot be matched to it` };\n }\n return checkWidgetCompatibility(type, ref, descriptor);\n}\n\nexport interface UnresolvedWidgetReport {\n widgetId: string;\n type: string;\n reason: UnresolvedWidgetReason;\n}\n\n// unresolvedWidgets lists every widget on the board that is a placeholder, with its reason.\n// A publish gate uses it: loading, layout edits and draft saves stay possible, but a new\n// version should not be published while a widget cannot be resolved. A placeholder built\n// by an older parser (no reason) is reported as unregistered.\nexport function unresolvedWidgets(definition: Pick<DashboardDefinition, 'widgets'>): UnresolvedWidgetReport[] {\n const out: UnresolvedWidgetReport[] = [];\n for (const w of definition.widgets as readonly WidgetInstance[]) {\n if (w.type !== UNKNOWN_WIDGET_TYPE) continue;\n const storedType = typeof w.raw.type === 'string' ? w.raw.type : '';\n out.push({\n widgetId: w.id,\n type: storedType,\n reason: w.reason ?? { code: 'unregistered', message: `\"${storedType}\" is not available in this host` },\n });\n }\n return out;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Load-time helpers for a stored dashboard definition.\n//\n// dashboard-management stores the definition as opaque JSON (well-formed object,\n// size-bounded \u2014 nothing more). This package owns the *shape*, so validation and\n// default-filling live here rather than on the server: parseDashboardDefinition\n// turns an untrusted parsed value into a DashboardDefinition or throws, so the\n// renderer never has to guess at a missing canvas/breakpoint/box. Kept permissive\n// where a sensible default exists (a bare `{ widgets: [] }` is valid) and strict\n// where a wrong value would silently mis-render (a widget with no type, no base box).\n//\n// A widget whose type this build does not know is NOT an error: it is a widget a newer\n// release added. It parses to a placeholder that keeps the stored object verbatim, and\n// serializeDefinition writes that object back (see UnknownWidgetInstance), so an older\n// host that loads and saves a newer board leaves the newer widget exactly as it was.\n\nimport {\n CURRENT_SCHEMA_VERSION,\n FRAME_BORDER_WIDTHS,\n FRAME_HEADERS,\n FRAME_RADII,\n FRAME_VARIANTS,\n PLACEMENT_ANCHORS,\n RESERVED_PLACEMENT_MODES,\n THEME_COLOR_TOKENS,\n WIDGET_TYPES,\n isCanvasDensity,\n type AnchorTarget,\n type BoardTimeRange,\n type Breakpoints,\n type Canvas,\n type CanvasGrid,\n type CanvasSizing,\n type DashboardDefinition,\n type ExtensionWidgetInstance,\n type ExtensionWidgetType,\n type FrameBorder,\n type GridLength,\n type KnownWidgetInstance,\n type LocationSelection,\n type Placement,\n type PlacementAnchor,\n type SlotBinding,\n type SlotDefinition,\n type SlotCandidates,\n type SlotScope,\n type WidgetFrameSpec,\n type WidgetImplementationRef,\n type WidgetInstance,\n type WidgetLayout,\n type UnknownWidgetInstance,\n type UnresolvedWidgetReason,\n type WidgetType,\n UNKNOWN_WIDGET_TYPE,\n} from './types';\nimport {\n BASE_BREAKPOINT,\n DEFAULT_GRID,\n gridFootprintsOverlap,\n isSpaceToken,\n layoutBreakpoints,\n resolvePlacement,\n} from './grid';\nimport { convertLegacyDefinition, GRID_SCHEMA_VERSION, storedSchemaVersion } from './legacy-grid';\nimport { convertSampleWindows } from './legacy-window';\nimport { isValidSpanSeconds } from './time-range';\nimport { parseWidgetRef, unresolvedReason } from './widget-compat';\nimport type { WidgetCatalog } from './widget-catalog';\nimport { isRecord } from './is-record';\n\n// The breakpoint every layout must define; a widget/viewport with no more specific placement\n// falls back to it.\nexport { BASE_BREAKPOINT };\n\nconst WIDGET_TYPE_SET: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\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\".\n//\n// `code` is set only when the document is one this build must not read at all:\n// 'schemaTooNew' means it was saved by a newer release (its schemaVersion, `found`, is above\n// CURRENT_SCHEMA_VERSION), and reading it as the current format would draw it wrong without\n// saying so (every widget collapsed to a single cell). It is the same class, so a caller that\n// knows nothing of the code still refuses the document; a caller that wants to say WHY tests\n// `code`, which -- unlike `instanceof` -- also holds across two bundled copies of this package.\nexport class DashboardDefinitionError extends Error {\n constructor(\n message: string,\n readonly code?: 'schemaTooNew',\n readonly found?: number,\n ) {\n super(`invalid dashboard definition: ${message}`);\n this.name = 'DashboardDefinitionError';\n }\n}\n\nfunction numberAt(rec: Record<string, unknown>, key: string, fallback: number): number {\n const v = rec[key];\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback;\n}\n\n// parseDashboardDefinition validates a parsed JSON value and returns a definition\n// with defaults filled, or throws DashboardDefinitionError. `raw` is the value\n// after JSON.parse (the caller owns the parse so a syntax error is theirs to\n// handle); an already-typed object round-trips unchanged, an unknown-type\n// placeholder included.\n//\n// `options.catalog` is the React-free view of the host's widget registry. With it, a\n// namespaced extension id the catalog holds parses to an ExtensionWidgetInstance, and a\n// stored `widget` version reference is checked against the installed descriptor; a widget\n// the catalog cannot resolve (absent, no/invalid metadata, settings or SDK generation\n// differing) becomes a placeholder carrying its reason and its stored JSON. Without a\n// catalog the parser knows the built-ins only (legacy behavior), and any other id is a\n// placeholder.\nexport interface ParseDefinitionOptions {\n catalog?: WidgetCatalog;\n}\n\nexport function parseDashboardDefinition(input: unknown, options: ParseDefinitionOptions = {}): DashboardDefinition {\n if (!isRecord(input)) throw new DashboardDefinitionError('not a JSON object');\n // Before anything reads the shape: a newer format is refused whole, not read as this one.\n const stored = storedSchemaVersion(input);\n if (stored > CURRENT_SCHEMA_VERSION) {\n throw new DashboardDefinitionError(`schemaVersion ${stored} is too new`, 'schemaTooNew', stored);\n }\n if (!Array.isArray(input.widgets)) throw new DashboardDefinitionError('widgets must be an array');\n\n // A document stored in an older format is converted on load, one step at a time: the square-cell\n // grid (approximately; see ./legacy-grid), then the time span (./legacy-window). The result\n // says so. Everything below reads the current format only.\n const legacy = stored < CURRENT_SCHEMA_VERSION;\n const gridConverted = stored < GRID_SCHEMA_VERSION;\n let raw: Record<string, unknown> = input;\n if (gridConverted) raw = convertLegacyDefinition(raw);\n let flaggedWindows: number[] = [];\n if (legacy) {\n const converted = convertSampleWindows(raw);\n raw = converted.doc;\n flaggedWindows = converted.flagged;\n }\n const widgetsRaw = raw.widgets as unknown[];\n\n const def: DashboardDefinition = {\n // The format this release writes (a newer one was refused above): a save writes that format.\n schemaVersion: CURRENT_SCHEMA_VERSION,\n title: typeof raw.title === 'string' ? raw.title : '',\n canvas: parseCanvas(raw.canvas),\n widgets: widgetsRaw.map((w, i) => parseWidget(w, i, options.catalog)),\n };\n // slots \u2014 the runtime-binding section (ADR-039). Normalized when present so a\n // stored definition round-trips, but omitted entirely otherwise so a slot-free\n // dashboard serializes unchanged (no spurious `\"slots\":{}` diff).\n const slots = parseSlots(raw.slots);\n if (slots) def.slots = slots;\n const timeRange = parseBoardTimeRange(raw.timeRange);\n if (timeRange) def.timeRange = timeRange;\n validatePlacements(def);\n // Only a conversion that changed something worth reviewing leaves a mark: a re-laid-out grid\n // (an empty board has no layout to review), or a chart that lost its sample-count window. A\n // v2 board with nothing to flag converts silently -- it lost nothing. A typed definition handed\n // back to the parser keeps the marks it already carries.\n const flaggedIds = flaggedWindows.map((i) => def.widgets[i]?.id).filter((id): id is string => id !== undefined);\n if ((gridConverted && def.widgets.length > 0) || flaggedIds.length > 0) def.convertedFrom = stored;\n else if (typeof raw.convertedFrom === 'number') def.convertedFrom = raw.convertedFrom;\n if (flaggedIds.length > 0) def.convertedWindows = flaggedIds;\n else if (Array.isArray(raw.convertedWindows) && raw.convertedWindows.every((id) => typeof id === 'string')) {\n def.convertedWindows = raw.convertedWindows as string[];\n }\n return def;\n}\n\n// validatePlacements refuses a layout that cannot be drawn as declared, at every breakpoint a\n// widget can be shown at (its own placement there, or its base one):\n// - two GRID footprints that overlap. Overlap is something a widget declares (the relative\n// mode), never something that happens to a grid widget;\n// - a relative widget whose host does not exist, is itself, or is a relative widget whose own\n// host is relative too (a chain deeper than one level).\n// It throws rather than degrading: a definition that overlaps would draw one widget over another\n// with nothing to say why.\nfunction validatePlacements(def: DashboardDefinition): void {\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets)) {\n const at = bp === BASE_BREAKPOINT ? '' : ` at breakpoint '${bp}'`;\n const placements = new Map<string, Placement>();\n for (const w of def.widgets) placements.set(w.id, resolvePlacement(w.layout, bp));\n const grid = [...placements].filter((e): e is [string, Extract<Placement, { mode: 'grid' }>] => e[1].mode === 'grid');\n for (let i = 0; i < grid.length; i++) {\n for (let j = i + 1; j < grid.length; j++) {\n if (gridFootprintsOverlap(grid[i][1], grid[j][1])) {\n throw new DashboardDefinitionError(\n `widgets '${grid[i][0]}' and '${grid[j][0]}' overlap on the grid${at}; ` +\n `a widget that is meant to sit over another is placed relative to it`,\n );\n }\n }\n }\n for (const [id, p] of placements) {\n if (p.mode !== 'relative') continue;\n if (p.host === id) throw new DashboardDefinitionError(`widget '${id}' is placed relative to itself${at}`);\n const host = placements.get(p.host);\n if (!host) {\n throw new DashboardDefinitionError(`widget '${id}' is placed relative to '${p.host}', which does not exist${at}`);\n }\n if (host.mode === 'relative' && placements.get(host.host)?.mode !== 'grid') {\n throw new DashboardDefinitionError(\n `widget '${id}' is placed relative to '${p.host}', which is relative to another relative widget${at}; ` +\n `a relative widget's host may be relative only when that host sits on a grid widget`,\n );\n }\n }\n }\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 const candidates = parseCandidates(spec.candidates);\n if (candidates) slot.candidates = candidates;\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// parseCandidates reads a root slot's picker type filter: non-empty string tokens only,\n// anything else dropped, and nothing left means no filter.\nfunction parseCandidates(raw: unknown): SlotCandidates | undefined {\n if (!isRecord(raw)) return undefined;\n const token = (v: unknown) => (typeof v === 'string' && v ? v : undefined);\n const areaType = token(raw.areaType);\n const assetType = token(raw.assetType);\n const deviceType = token(raw.deviceType);\n if (!areaType && !assetType && !deviceType) return undefined;\n return {\n ...(areaType ? { areaType } : {}),\n ...(assetType ? { assetType } : {}),\n ...(deviceType ? { deviceType } : {}),\n };\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// parseGridLength reads one length: a finite number (clamped to >= 0) or a spacing token. An\n// absent value takes the fallback; any other string or type is refused, since a misspelled token\n// would otherwise draw a different board from the one authored.\nfunction parseGridLength(raw: unknown, fallback: GridLength, where: string): GridLength {\n if (raw === undefined) return fallback;\n if (typeof raw === 'number' && Number.isFinite(raw)) return Math.max(0, raw);\n if (isSpaceToken(raw)) return raw;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; a length is a number of px or a space-0..space-4 token`);\n}\n\n// parseGrid coerces the canvas grid, filling defaults. `columns` is floored to an integer >= 1\n// and `minCellSize` to >= 1, so neither can yield an unusable grid. `gutter` is one length or an\n// {x,y} pair; `padding` one length or a {top,right,bottom,left} set; a side an object leaves\n// out takes 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 minCellSize = Math.max(1, numberAt(rec, 'minCellSize', DEFAULT_GRID.minCellSize));\n const dg = DEFAULT_GRID.gutter as GridLength;\n const gutter: CanvasGrid['gutter'] = isRecord(rec.gutter)\n ? {\n x: parseGridLength(rec.gutter.x, dg, 'canvas.grid.gutter.x'),\n y: parseGridLength(rec.gutter.y, dg, 'canvas.grid.gutter.y'),\n }\n : parseGridLength(rec.gutter, dg, 'canvas.grid.gutter');\n const dp = DEFAULT_GRID.padding as GridLength;\n const padding: CanvasGrid['padding'] = isRecord(rec.padding)\n ? {\n top: parseGridLength(rec.padding.top, dp, 'canvas.grid.padding.top'),\n right: parseGridLength(rec.padding.right, dp, 'canvas.grid.padding.right'),\n bottom: parseGridLength(rec.padding.bottom, dp, 'canvas.grid.padding.bottom'),\n left: parseGridLength(rec.padding.left, dp, 'canvas.grid.padding.left'),\n }\n : parseGridLength(rec.padding, dp, 'canvas.grid.padding');\n return { columns, minCellSize, gutter, padding };\n}\n\n// parseBreakpoints keeps every well-formed breakpoint (a min width, or {minWidth, columns}) and\n// drops a malformed one, as it always has. `base` is added at 0 when absent; it is always in the\n// grid's own column count, so a `base` that declares another count is refused.\nfunction parseBreakpoints(raw: unknown, grid: CanvasGrid): Breakpoints {\n const rec = isRecord(raw) ? raw : {};\n const breakpoints: Breakpoints = {};\n for (const [name, spec] of Object.entries(rec)) {\n if (name === '__proto__') continue;\n if (typeof spec === 'number' && Number.isFinite(spec)) {\n breakpoints[name] = spec;\n } else if (\n isRecord(spec) &&\n typeof spec.minWidth === 'number' &&\n Number.isFinite(spec.minWidth) &&\n typeof spec.columns === 'number' &&\n Number.isInteger(spec.columns) &&\n spec.columns >= 1\n ) {\n breakpoints[name] = { minWidth: spec.minWidth, columns: spec.columns };\n }\n }\n const base = breakpoints[BASE_BREAKPOINT];\n if (base === undefined) breakpoints[BASE_BREAKPOINT] = 0;\n else if (typeof base === 'object' && base.columns !== grid.columns) {\n throw new DashboardDefinitionError(\n `breakpoint '${BASE_BREAKPOINT}' declares ${base.columns} columns; the base layout is in canvas.grid.columns (${grid.columns})`,\n );\n }\n return breakpoints;\n}\n\nfunction oneOf<T extends string | number>(values: readonly T[], raw: unknown, where: string): T | undefined {\n if (raw === undefined) return undefined;\n if ((values as readonly unknown[]).includes(raw)) return raw as T;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; expected one of ${values.map((v) => JSON.stringify(v)).join(', ')}`);\n}\n\n// parseFrame reads a frame (canvas default or widget override). Every value is a token from a\n// closed list and anything else is REFUSED, a free colour above all: a frame that quietly dropped\n// `#f00` would render a different card from the one authored, and one that kept it would break\n// dark mode and the ISA-101 theme. Keys a frame does not have are ignored.\nfunction parseFrame(raw: unknown, where: string): WidgetFrameSpec | undefined {\n if (raw === undefined) return undefined;\n if (!isRecord(raw)) throw new DashboardDefinitionError(`${where} is not an object`);\n const frame: WidgetFrameSpec = {};\n const variant = oneOf(FRAME_VARIANTS, raw.variant, `${where}.variant`);\n if (variant) frame.variant = variant;\n if (raw.border !== undefined) {\n if (!isRecord(raw.border)) throw new DashboardDefinitionError(`${where}.border is not an object`);\n const border: FrameBorder = {};\n const width = oneOf(FRAME_BORDER_WIDTHS, raw.border.width, `${where}.border.width`);\n if (width !== undefined) border.width = width;\n const radius = oneOf(FRAME_RADII, raw.border.radius, `${where}.border.radius`);\n if (radius) border.radius = radius;\n const color = oneOf(THEME_COLOR_TOKENS, raw.border.color, `${where}.border.color`);\n if (color) border.color = color;\n frame.border = border;\n }\n if (raw.padding !== undefined) {\n if (!isSpaceToken(raw.padding)) {\n throw new DashboardDefinitionError(`${where}.padding is ${JSON.stringify(raw.padding)}; expected a space-0..space-4 token`);\n }\n frame.padding = raw.padding;\n }\n const background = oneOf([...THEME_COLOR_TOKENS, 'transparent' as const], raw.background, `${where}.background`);\n if (background) frame.background = background;\n const header = oneOf(FRAME_HEADERS, raw.header, `${where}.header`);\n if (header) frame.header = header;\n return frame;\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\n// parseBoardTimeRange reads the board's authored default span. A value that is not a usable span\n// (not an integer, under a minute, over a week) is IGNORED, so the board draws the default hour:\n// failing the whole board for a bad range would hide every widget on it, and clamping would turn\n// a typo into a window nobody wrote.\nfunction parseBoardTimeRange(raw: unknown): BoardTimeRange | undefined {\n if (!isRecord(raw) || !isValidSpanSeconds(raw.defaultSeconds)) return undefined;\n return { defaultSeconds: raw.defaultSeconds };\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 // Breakpoints must define 'base'; a definition that omits responsive layouts gets base:0.\n const breakpoints = parseBreakpoints(rec.breakpoints, grid);\n\n const canvas: Canvas = { grid, sizing, breakpoints };\n const frame = parseFrame(rec.frame, 'canvas.frame');\n if (frame) canvas.frame = frame;\n // An unknown value falls back to the default (absent) rather than failing the parse: a\n // definition written by a newer release still renders.\n if (isCanvasDensity(rec.density)) canvas.density = rec.density;\n if (isRecord(rec.background)) {\n const { color, imageUrl } = rec.background;\n canvas.background = {\n color: typeof color === 'string' ? color : null,\n imageUrl: typeof imageUrl === 'string' ? imageUrl : null,\n };\n }\n return canvas;\n}\n\nfunction parseWidget(raw: unknown, index: number, catalog: WidgetCatalog | undefined): WidgetInstance {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}] is not an object`);\n\n const type = raw.type;\n // A missing, empty or non-string type names nothing a newer viewer could render: the\n // document is broken, not newer.\n if (typeof type !== 'string' || type.length === 0) {\n throw new DashboardDefinitionError(`widgets[${index}] has unknown type ${JSON.stringify(type)}`);\n }\n if (type === UNKNOWN_WIDGET_TYPE && isRecord(raw.raw)) return reparseUnknownWidget(raw, index, catalog);\n\n const builtin = WIDGET_TYPE_SET.has(type);\n const reason = unresolvedReason(type, raw.widget, catalog, builtin) ?? (builtin ? undefined : reservedFieldReason(type, raw));\n if (reason) return parseUnknownWidget(raw, index, reason);\n\n const ref = parseWidgetRef(raw.widget) ?? undefined;\n const widget: KnownWidgetInstance | ExtensionWidgetInstance = builtin\n ? {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as WidgetType,\n ...(ref ? { widget: ref } : {}),\n layout: parseLayout(raw.layout, index),\n }\n : {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: type as ExtensionWidgetType,\n widget: ref as WidgetImplementationRef, // unresolvedReason guarantees a valid ref here\n layout: parseLayout(raw.layout, index),\n };\n // datasource is owned by the hub, but the two supported kinds are NORMALIZED here\n // so downstream (hub/widgets) never sees a partial shape (a device selector with\n // no measurements array, an anchor with a non-object target, \u2026). Reserved/other\n // kinds are carried through opaquely \u2014 the hub rejects them.\n const ds = parseDatasource(raw.datasource, index);\n if (ds) widget.datasource = ds;\n if (isRecord(raw.options)) widget.options = raw.options as Record<string, unknown>;\n const frame = parseFrame(raw.frame, `widgets[${index}].frame`);\n if (frame) widget.frame = frame;\n if (!builtin) return withStoredFields(raw, widget as ExtensionWidgetInstance);\n return widget;\n}\n\n// The envelope fields the parser owns on a resolved widget. Everything else a stored widget\n// carries is outside the owned schema.\nconst OWNED_WIDGET_KEYS: ReadonlySet<string> = new Set(['id', 'type', 'widget', 'layout', 'datasource', 'options', 'frame']);\n\n// withStoredFields keeps, on a resolved EXTENSION widget, every stored field the envelope does\n// not own (a package's own top-level additions, extra keys inside `widget`), in the stored key\n// order, so a load/save round trip writes them back byte-for-byte. Owned fields take their\n// parsed (normalized) values. Built-ins are deliberately left as they always were (only the\n// owned envelope survives), so an existing board's saved bytes do not change.\nfunction withStoredFields(raw: Record<string, unknown>, parsed: ExtensionWidgetInstance): ExtensionWidgetInstance {\n const owned = parsed as unknown as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n // defineProperty, not assignment: a JSON.parse'd `__proto__` key is an own field to keep,\n // and assigning it would set the prototype instead.\n const put = (key: string, value: unknown) =>\n Object.defineProperty(out, key, { value, enumerable: true, writable: true, configurable: true });\n for (const key of Object.keys(raw)) {\n if (!OWNED_WIDGET_KEYS.has(key)) put(key, JSON.parse(JSON.stringify(raw[key])) as unknown);\n else if (key === 'widget') put(key, { ...(raw.widget as Record<string, unknown>), ...parsed.widget });\n else if (owned[key] !== undefined) put(key, owned[key]);\n }\n for (const key of OWNED_WIDGET_KEYS) {\n if (!Object.prototype.hasOwnProperty.call(out, key) && owned[key] !== undefined) put(key, owned[key]);\n }\n return out as unknown as ExtensionWidgetInstance;\n}\n\n// A stored extension widget that uses a field name the parsed model reserves for placeholders\n// cannot be carried on a resolved instance without being mistaken for one, so it is kept\n// verbatim as a placeholder instead of having the field dropped.\nfunction reservedFieldReason(type: string, raw: Record<string, unknown>): UnresolvedWidgetReason | undefined {\n const clash = ['raw', 'reason'].find((k) => Object.prototype.hasOwnProperty.call(raw, k));\n return clash === undefined\n ? undefined\n : { code: 'invalid-metadata', message: `\"${type}\" carries a top-level \"${clash}\" field, which the dashboard format reserves` };\n}\n\n// The id a stored widget carries, or undefined when it has none worth keeping.\nfunction storedWidgetId(raw: Record<string, unknown>): string | undefined {\n return typeof raw.id === 'string' && raw.id.length > 0 ? raw.id : undefined;\n}\n\n// parseUnknownWidget builds the placeholder for a widget of a type this build does not\n// know. Only `id` and `layout` are read, through the same rules as every other widget,\n// so the canvas can place it; a layout with no base box still fails the parse, because\n// a widget that cannot be placed cannot be shown even as a placeholder.\n//\n// `raw` is a JSON copy of the stored object: detached from the caller's value (which a\n// host may go on to mutate), and exactly what JSON.stringify will write back.\nfunction parseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n reason: UnresolvedWidgetReason,\n): UnknownWidgetInstance {\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: JSON.parse(JSON.stringify(raw)) as Record<string, unknown>,\n reason,\n };\n}\n\n// reparseUnknownWidget takes back a placeholder this parser already built \u2014 a typed\n// definition handed to the parser again \u2014 as the same placeholder: its stored object is\n// kept as its raw, and its id and layout (which may carry an edit) come from the\n// placeholder. Wrapping it again would make the save write the wrapper and lose the\n// widget. The serializer never writes the placeholder type, so no stored document takes\n// this path.\nfunction reparseUnknownWidget(\n raw: Record<string, unknown>,\n index: number,\n catalog: WidgetCatalog | undefined,\n): UnknownWidgetInstance {\n const stored = JSON.parse(JSON.stringify(raw.raw)) as Record<string, unknown>;\n const storedType = typeof stored.type === 'string' ? stored.type : '';\n // The reason is recomputed against THIS catalog (it is never stored); a stored object the\n // catalog now resolves stays a placeholder here, because only a parse of the stored\n // document \u2014 not a re-parse of a typed definition \u2014 promotes a widget.\n const reason = unresolvedReason(storedType, stored.widget, catalog, WIDGET_TYPE_SET.has(storedType)) ?? {\n code: 'unregistered' as const,\n message: `\"${storedType}\" is not available in this host`,\n };\n return {\n id: storedWidgetId(raw) ?? generateWidgetId(),\n type: UNKNOWN_WIDGET_TYPE,\n layout: parseLayout(raw.layout, index),\n raw: stored,\n reason,\n };\n}\n\nfunction stringAt(rec: Record<string, unknown>, key: string): string {\n const v = rec[key];\n return typeof v === 'string' ? v : '';\n}\n\nfunction stringArrayAt(rec: Record<string, unknown>, key: string): string[] {\n const v = rec[key];\n return Array.isArray(v) ? v.filter((m): m is string => typeof m === 'string') : [];\n}\n\n// parseLocationSelection reads a selector's OPTIONAL location-series field.\n//\n// \uD83D\uDD34 Additive, and it has to stay that way: every definition stored before the location\n// channel existed carries no `location`, and undefined here is exactly right for all of\n// them \u2014 the selector names no location series, which is true. Nothing about the\n// measurement path is touched, so the regression risk is confined to what an author\n// deliberately adds.\n//\n// The vocabulary is closed, so an unrecognized series is DROPPED rather than carried\n// through: the hub resolves a location scope only for a selector naming a series it\n// understands, and carrying an unknown one would produce a widget that looks configured\n// and reads nothing. Dropping it makes the widget's empty state honest.\nfunction parseLocationSelection(raw: unknown): LocationSelection | undefined {\n if (!isRecord(raw)) return undefined;\n return raw.series === 'latest' ? { series: 'latest' } : undefined;\n}\n\n// withLocation attaches a parsed location selection to a selector, and OMITS the key\n// entirely when there is none \u2014 so a measurement-only definition serializes byte-for-\n// byte as it did before this field existed (no spurious `\"location\":undefined` in the\n// diff, and isDirty stays quiet on a load/save round trip).\nfunction withLocation<S extends object>(selector: S, raw: Record<string, unknown>): S {\n const location = parseLocationSelection(raw.location);\n return location ? { ...selector, location } : selector;\n}\n\n// parseDatasource coerces a raw datasource into a normalized selector, or drops it\n// (returns undefined) when it is absent or its `kind` is not a non-empty string.\nfunction parseDatasource(raw: unknown, index: number): WidgetInstance['datasource'] | undefined {\n if (!isRecord(raw)) return undefined;\n const kind = raw.kind;\n if (typeof kind !== 'string' || kind.length === 0) return undefined;\n\n // \uD83D\uDD34 `unbound` is what the RENDERER produces for a slot with no binding; it is never\n // stored. Carried through, a stored one would render as an empty pane on every channel\n // with nothing to say why \u2014 the shape of a correctly unbound slot, not of a broken\n // definition. A widget with no entity is stored as a `slot` with no binding.\n if (kind === 'unbound') {\n throw new DashboardDefinitionError(\n `widgets[${index}].datasource has kind 'unbound', which is never stored; bind the widget through a slot`,\n );\n }\n\n if (kind === 'device') {\n return withLocation(\n { kind: 'device' as const, deviceToken: stringAt(raw, 'deviceToken'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n if (kind === 'anchor') {\n const anchorRec = isRecord(raw.anchor) ? raw.anchor : {};\n const selector: WidgetInstance['datasource'] = withLocation(\n {\n kind: 'anchor' as const,\n anchor: {\n relationship: stringAt(anchorRec, 'relationship'),\n // targetType defaults to '' (the config panel constrains it to the union;\n // a hand-edited/empty value round-trips rather than being silently coerced).\n targetType: stringAt(anchorRec, 'targetType') as AnchorTarget['targetType'],\n targetToken: stringAt(anchorRec, 'targetToken'),\n },\n measurements: stringArrayAt(raw, 'measurements'),\n },\n raw,\n );\n if (isRecord(raw.aggregation)) {\n (selector as { aggregation?: unknown }).aggregation = raw.aggregation;\n }\n return selector;\n }\n\n if (kind === 'slot') {\n // Runtime-binding kind: the entity is resolved at mount via the Hub's binding\n // manifest; the widget carries only the slot name + the series it wants (the\n // measurement names, and/or the location series).\n return withLocation(\n { kind: 'slot' as const, slot: stringAt(raw, 'slot'), measurements: stringArrayAt(raw, 'measurements') },\n raw,\n );\n }\n\n // Reserved/other kinds: carry through opaquely (the hub rejects them).\n return raw as unknown as WidgetInstance['datasource'];\n}\n\nfunction parseLayout(raw: unknown, index: number): WidgetLayout {\n if (!isRecord(raw)) throw new DashboardDefinitionError(`widgets[${index}].layout is missing`);\n\n const layout: WidgetLayout = {};\n for (const [bp, placement] of Object.entries(raw)) {\n if (bp === '__proto__') continue;\n if (isRecord(placement)) layout[bp] = parsePlacement(placement, `widgets[${index}].layout.${bp}`);\n }\n if (!Object.prototype.hasOwnProperty.call(layout, BASE_BREAKPOINT)) {\n throw new DashboardDefinitionError(`widgets[${index}].layout has no '${BASE_BREAKPOINT}' placement`);\n }\n return layout;\n}\n\n// A whole number of cells >= min, or a refusal. Required: a span the author left out has no\n// value this parser could honestly pick.\nfunction cellCount(rec: Record<string, unknown>, key: string, min: number, where: string): number {\n const v = rec[key];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new DashboardDefinitionError(`${where}.${key} is ${JSON.stringify(v)}; expected a number of cells`);\n }\n return Math.max(min, Math.round(v));\n}\n\n// parsePlacement reads one breakpoint's placement. A missing `mode` is a grid placement. Every\n// value the mode needs is required and checked: a placement is where a widget is drawn, and a\n// guessed one draws it somewhere nobody chose.\nfunction parsePlacement(rec: Record<string, unknown>, where: string): Placement {\n const mode = rec.mode ?? 'grid';\n if (mode === 'grid') {\n return {\n mode: 'grid',\n col: cellCount(rec, 'col', 0, where),\n row: cellCount(rec, 'row', 0, where),\n cols: cellCount(rec, 'cols', 1, where),\n rows: cellCount(rec, 'rows', 1, where),\n };\n }\n if (mode === 'relative') return parseRelative(rec, where);\n if ((RESERVED_PLACEMENT_MODES as readonly unknown[]).includes(mode)) {\n throw new DashboardDefinitionError(`${where}.mode '${String(mode)}' is reserved and not supported by this release`);\n }\n throw new DashboardDefinitionError(`${where}.mode is ${JSON.stringify(mode)}; expected 'grid' or 'relative'`);\n}\n\nfunction parseAnchor(raw: unknown, where: string): PlacementAnchor {\n if ((PLACEMENT_ANCHORS as readonly unknown[]).includes(raw)) return raw as PlacementAnchor;\n throw new DashboardDefinitionError(`${where} is ${JSON.stringify(raw)}; expected one of ${PLACEMENT_ANCHORS.join(', ')}`);\n}\n\nfunction parseRelative(rec: Record<string, unknown>, where: string): Placement {\n if (typeof rec.host !== 'string' || rec.host.length === 0) {\n throw new DashboardDefinitionError(`${where}.host is ${JSON.stringify(rec.host)}; a relative widget names the widget it is placed against`);\n }\n const placement: Extract<Placement, { mode: 'relative' }> = {\n mode: 'relative',\n host: rec.host,\n anchor: parseAnchor(rec.anchor, `${where}.anchor`),\n size: { fill: 'host' },\n };\n if (rec.self !== undefined) placement.self = parseAnchor(rec.self, `${where}.self`);\n if (rec.offset !== undefined) {\n const o = rec.offset;\n if (!isRecord(o) || !isFiniteNumber(o.x) || !isFiniteNumber(o.y)) {\n throw new DashboardDefinitionError(`${where}.offset is ${JSON.stringify(o)}; expected {x, y} in cells`);\n }\n placement.offset = { x: o.x, y: o.y };\n }\n const size = rec.size;\n if (isRecord(size) && size.fill === 'host') {\n placement.size = { fill: 'host' };\n } else if (isRecord(size)) {\n placement.size = { cols: cellCount(size, 'cols', 1, `${where}.size`), rows: cellCount(size, 'rows', 1, `${where}.size`) };\n } else {\n throw new DashboardDefinitionError(`${where}.size is ${JSON.stringify(size)}; expected {cols, rows} or {fill: 'host'}`);\n }\n if (rec.layer !== undefined) {\n if (!isFiniteNumber(rec.layer) || !Number.isInteger(rec.layer)) {\n throw new DashboardDefinitionError(`${where}.layer is ${JSON.stringify(rec.layer)}; expected a whole number`);\n }\n placement.layer = rec.layer;\n }\n return placement;\n}\n\nfunction isFiniteNumber(v: unknown): v is number {\n return typeof v === 'number' && Number.isFinite(v);\n}\n\n// The stored shape of a definition: what serializeDefinition writes. A placeholder for\n// an unknown widget type is written as the stored object it was parsed from.\nexport type StoredDashboardDefinition = Omit<DashboardDefinition, 'widgets'> & {\n widgets: Array<KnownWidgetInstance | ExtensionWidgetInstance | Record<string, unknown>>;\n};\n\n// storedDefinition is the definition as it is stored: every unknown-type placeholder\n// replaced by its raw object (carrying any move or id change the editor made to it).\n// Anything that writes a definition out \u2014 a save, a copy-as-JSON export \u2014 goes through\n// this, because JSON.stringify of the parsed model would write the placeholder wrapper\n// and lose the widget. Returns the same reference when there is no placeholder.\n//\n// The parser's `convertedFrom` mark is never written: it describes how this copy was read, not\n// the board, and a save is exactly what retires it.\nexport function storedDefinition(def: DashboardDefinition): StoredDashboardDefinition {\n const unmarked = withoutConversionMark(def);\n if (!unmarked.widgets.some((w) => w.type === UNKNOWN_WIDGET_TYPE)) return unmarked as StoredDashboardDefinition;\n return {\n ...unmarked,\n widgets: unmarked.widgets.map((w) => (w.type === UNKNOWN_WIDGET_TYPE ? storedUnknownWidget(w) : w)),\n };\n}\n\n// storedUnknownWidget writes a placeholder back as its raw object. The raw object is\n// returned untouched unless the editor changed one of the two fields a placeholder\n// exposes: a changed id is written over the stored one, and a moved breakpoint box is\n// written over that breakpoint (every untouched breakpoint keeps its stored bytes). An\n// id the parser GENERATED for an id-less widget is not written, so the stored widget\n// stays id-less exactly as it was.\nfunction storedUnknownWidget(w: UnknownWidgetInstance): Record<string, unknown> {\n let out = w.raw;\n const rawId = storedWidgetId(w.raw);\n if (rawId !== undefined && rawId !== w.id) out = { ...out, id: w.id };\n const layout = storedUnknownLayout(w.layout, w.raw.layout);\n if (layout !== w.raw.layout) out = { ...out, layout };\n return out;\n}\n\n// storedUnknownLayout merges the placeholder's (parsed, possibly edited) layout into\n// the stored one, breakpoint by breakpoint. A stored box that parses to the same box is\n// kept as stored; a changed one is replaced; a breakpoint the stored layout did not\n// have is appended; a stored entry that is not a box at all (the parser skipped it) is\n// carried. Returns the stored reference when nothing changed.\nfunction storedUnknownLayout(layout: WidgetLayout, rawLayout: unknown): unknown {\n const stored = isRecord(rawLayout) ? rawLayout : {};\n const out: Record<string, unknown> = {};\n let changed = !isRecord(rawLayout);\n for (const [bp, box] of Object.entries(stored)) {\n if (!isRecord(box)) {\n out[bp] = box;\n continue;\n }\n const current = Object.prototype.hasOwnProperty.call(layout, bp) ? layout[bp] : undefined;\n if (!current) {\n changed = true; // the editor removed this breakpoint\n continue;\n }\n if (samePlacement(parsePlacement(box, bp), current)) {\n out[bp] = box;\n } else {\n out[bp] = movedPlacement(box, current);\n changed = true;\n }\n }\n for (const [bp, box] of Object.entries(layout)) {\n if (Object.prototype.hasOwnProperty.call(stored, bp) && isRecord(stored[bp])) continue;\n out[bp] = box;\n changed = true;\n }\n return changed ? out : rawLayout;\n}\n\n// Every field a placement has in this build, in either mode.\nconst PLACEMENT_KEYS: ReadonlySet<string> = new Set([\n 'mode', 'col', 'row', 'cols', 'rows', 'host', 'anchor', 'self', 'offset', 'size', 'layer',\n]);\n\n// movedPlacement writes an edited placement over a stored one. The edited placement replaces\n// every field this build knows (so a field the edit dropped, or one of the other mode, is gone);\n// any other field the stored entry carries (one a newer release added) is kept.\nfunction movedPlacement(stored: Record<string, unknown>, current: Placement): Record<string, unknown> {\n const out: Record<string, unknown> = { ...current };\n for (const [k, v] of Object.entries(stored)) if (!PLACEMENT_KEYS.has(k)) out[k] = v;\n return out;\n}\n\nfunction samePlacement(a: Placement, b: Placement): boolean {\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n// serializeDefinition is the canonical on-the-wire JSON \u2014 the inverse of\n// parseDashboardDefinition \u2014 that dashboard-management stores. Named (not an inline\n// JSON.stringify) so every consumer that persists a definition shares one format.\nexport function serializeDefinition(def: DashboardDefinition): string {\n return JSON.stringify(storedDefinition(def));\n}\n\n// withoutConversionMark is the definition minus the parser's `convertedFrom` mark (the same\n// reference when it carries none): what an editor keeps once a save has written the current\n// format.\nexport function withoutConversionMark(def: DashboardDefinition): DashboardDefinition {\n if (def.convertedFrom === undefined && def.convertedWindows === undefined) return def;\n const { convertedFrom: _mark, convertedWindows: _windows, ...rest } = def;\n return rest;\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// generateWidgetId mints a unique widget-instance id. Uses crypto.randomUUID where\n// available (browsers, modern Node); the counter fallback keeps ids unique within a\n// session for the rare environment without it.\nlet idCounter = 0;\nexport function generateWidgetId(): string {\n const c = globalThis.crypto;\n if (c && typeof c.randomUUID === 'function') return `w-${c.randomUUID()}`;\n idCounter += 1;\n return `w-${idCounter.toString(36)}`;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Runtime slot binding (ADR-039). A dashboard definition is a reusable TEMPLATE:\n// widgets reference named slots, and each slot declares a `defaultBinding` (the\n// authoring tenant's entity). At MOUNT the host may supply a manifest that overrides\n// any slot's binding \u2014 so one definition + two manifests renders as two live\n// dashboards on different entities. effectiveBindings computes the base manifest: the\n// slot defaults, overlaid by the host's overrides. The renderer resolves each widget's\n// slot through the settled manifest (resolveWidgetDatasource) before the hub sees it.\n\nimport { parseSlotBinding } from './definition';\nimport type { DashboardDefinition, DatasourceSelector, SlotBinding, SlotDefinition } from './types';\n\n// effectiveBindings merges a definition's slot default bindings with an optional\n// host manifest (manifest wins). Slots without a default and not in the manifest are\n// omitted \u2192 the renderer hands those widgets an `unbound` selector, an empty placeholder.\n//\n// This is the BASE layer (defaults + manifest), computed synchronously. For a dashboard\n// with SCOPED slots it is NOT the final manifest: a scoped slot's default here is a\n// fallback that resolveContextBindings (the cascade) supersedes with a value derived\n// from the parent. A host with scoped slots feeds this map as `base` into the cascade\n// and hands the renderer the cascade's output, not this map directly.\nexport function effectiveBindings(\n definition: DashboardDefinition,\n manifest?: Record<string, SlotBinding>,\n): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [name, slot] of Object.entries(definition.slots ?? {})) {\n if (slot.defaultBinding) out[name] = slot.defaultBinding;\n }\n if (manifest) {\n for (const [name, binding] of Object.entries(manifest)) out[name] = binding;\n }\n return out;\n}\n\n// What parseBindingManifest made of a host manifest: the bindings it accepted, and the\n// slot names it did NOT \u2014 in manifest order.\nexport interface ParsedBindingManifest {\n bindings: Record<string, SlotBinding>;\n dropped: string[];\n}\n\n// parseBindingManifest validates an untrusted host manifest (slot name \u2192 binding)\n// into a clean Record<slot, SlotBinding>, dropping malformed entries. The host of an\n// exported dashboard passes this to effectiveBindings to bind the definition's slots\n// to ITS entities: one definition + two manifests \u2192 two live dashboards.\n//\n// \uD83D\uDD34 IT REPORTS WHAT IT DROPPED, because a silent drop is this API's sharpest edge and\n// every host has to handle it. A typo'd binding does not fail \u2014 it vanishes, the slot\n// stays unbound, and the widgets on it render as empty frames with nothing anywhere\n// saying why. Returning the names makes that a thing a host can show a user. The\n// alternative every host would otherwise reach for is comparing key counts against its\n// own input, which infers the same fact less well: it cannot name the offender, and it\n// counts the `__proto__` refusal below as if it were malformed.\nexport function parseBindingManifest(raw: unknown): ParsedBindingManifest {\n if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {\n return { bindings: {}, dropped: [] };\n }\n const bindings: Record<string, SlotBinding> = {};\n const dropped: string[] = [];\n for (const [slot, spec] of Object.entries(raw as Record<string, unknown>)) {\n // Skip a `__proto__` key: `out['__proto__'] = \u2026` would hit the prototype setter\n // (lose the binding + swap out's prototype) rather than set an own property.\n // Reported as dropped: it IS a binding the host asked for and did not get.\n if (slot === '__proto__') {\n dropped.push(slot);\n continue;\n }\n const binding = parseSlotBinding(spec);\n if (binding) bindings[slot] = binding;\n else dropped.push(slot);\n }\n return { bindings, dropped };\n}\n\n// stripDefaultBindings removes every slot's default binding, turning a concrete\n// dashboard into a TEMPLATE \u2014 the exported form a host must supply a manifest for\n// (each slot renders as a placeholder until bound). Slot names/types/labels are kept\n// so the importer knows what to bind. (Caveats: a slot's `label` is the author's\n// entity token \u2014 a naming hint, not stripped; and an anchor selector carrying a\n// Phase-2 `aggregation` stays concrete/un-slotted through migration, so it isn't\n// rebindable \u2014 neither is reachable through the console's authoring UI today.)\nexport function stripDefaultBindings(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) {\n const { defaultBinding: _drop, ...rest } = slot;\n slots[name] = rest;\n }\n return { ...def, slots };\n}\n\n// resolveWidgetDatasource turns a widget's datasource into the CONCRETE selector a data\n// source subscribes: a `slot` is looked up in the settled bindings and becomes the device\n// or anchor it is bound to, carrying the widget's own series (measurement names, and the\n// location series when it names one). Every other selector passes through unchanged.\n//\n// This is the renderer's job, not the hub's. The hub keeps no bindings, so a selection\n// that re-points one slot changes the concrete selector of exactly the widgets bound to\n// it \u2014 and only those re-subscribe, because every channel keys its subscription on the\n// selector's VALUE. A hub that resolved slots itself had to be rebuilt on every selection,\n// which tore down and re-fetched the whole board.\n//\n// \uD83D\uDD34 A slot with no binding becomes `{kind:'unbound'}`, NEVER `undefined`. An absent\n// datasource is tenant-wide on the alarm channel; an unbound slot is zero devices. The\n// two must not be confused, and this function is the one place they could be.\n//\n// The lookup is own-property only, so a slot named `constructor` or `__proto__` is\n// unbound rather than resolving to something on Object.prototype.\nexport function resolveWidgetDatasource(\n datasource: DatasourceSelector | undefined,\n bindings: Record<string, SlotBinding> | undefined,\n): DatasourceSelector | undefined {\n if (!datasource || datasource.kind !== 'slot') return datasource;\n const binding =\n bindings && Object.prototype.hasOwnProperty.call(bindings, datasource.slot)\n ? bindings[datasource.slot]\n : undefined;\n const series = datasource.location\n ? { measurements: datasource.measurements, location: datasource.location }\n : { measurements: datasource.measurements };\n if (binding?.kind === 'device') return { kind: 'device', deviceToken: binding.deviceToken, ...series };\n if (binding?.kind === 'anchor') return { kind: 'anchor', anchor: binding.anchor, ...series };\n return { kind: 'unbound', ...series };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// 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), narrowed to the slot's\n// candidates.deviceType when it declares one.\n// root anchor slot \u2192 the entities of the slot's target type THE VIEWER MAY SEE (the lister\n// answers a customer user with the areas its devices are in), narrowed to\n// the slot's candidates.areaType/assetType when it declares one; 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 EntityListKind,\n SelectionCandidate,\n SlotBinding,\n SlotDefinition,\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\n// slotTypeFilter is the picker type filter a root anchor slot declares for its target kind: the\n// area type for an area slot, the asset type for an asset slot, none for anything else.\n// A lister MAY take the type token as a second argument (createEntityLister's does, and narrows the\n// areas or assets it reads); one that does not simply lists every entity of the kind. The public\n// EntityCandidateLister type stays one-argument, so a host's own lister keeps compiling.\nexport const listEntities = (\n lister: EntityCandidateLister,\n kind: EntityListKind,\n typeToken: string | undefined,\n): ReturnType<EntityCandidateLister> => (lister as (k: EntityListKind, t?: string) => ReturnType<EntityCandidateLister>)(kind, typeToken);\n\nexport function slotTypeFilter(def: SlotDefinition, targetType: string): string | undefined {\n return targetType === 'customer' ? undefined : def.candidates?.[targetType === 'area' ? 'areaType' : 'assetType'];\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 listEntities(lister, 'device', def.candidates?.deviceType);\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 listEntities(lister, template.targetType, slotTypeFilter(def, 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// 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// The cascade as a VIEWER sees it. resolveContextBindings settles every slot from the board's own\n// defaults and the viewer's picks; this adds the one thing only the viewer's side can answer: is\n// the area or asset a root anchor slot is bound to one the viewer may pick? A board saved with\n// floor tp-tower-f4 and opened by a member who is not placed there would otherwise show an empty\n// floor under a name they cannot see. It falls back to the first entity the picker offers, for\n// every viewer (an operator's board pointing at a deleted area benefits too), by correcting the\n// root binding BEFORE the cascade derives the slots that follow it.\n//\n// It is a separate function, not a parameter of resolveContextBindings, so that function keeps the\n// signature published hosts call.\n\nimport { listEntities, slotTypeFilter } from './candidates';\nimport { resolveContextBindings, type MemberResolver } from './context';\nimport type { DashboardDefinition, EntityCandidateLister, SlotBinding } from './types';\n\nconst ownGet = <T>(map: Record<string, T>, key: string): T | undefined =>\n Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n\n// hasRootAnchorSlots reports whether any ROOT slot is an anchor slot: the slots whose binding the\n// viewer-side check applies to. A board without one has nothing to correct.\nexport function hasRootAnchorSlots(definition: DashboardDefinition): boolean {\n return Object.values(definition.slots ?? {}).some((s) => s.type === 'anchor' && !s.scope);\n}\n\n// resolveViewerBindings is resolveContextBindings plus the fallback. Fail-safe like the cascade: a\n// lister that is absent, fails or lists nothing keeps the binding, so an outage degrades to\n// today's behaviour and never to a blank board.\nexport async function resolveViewerBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n overlay: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister?: EntityCandidateLister,\n): Promise<Record<string, SlotBinding>> {\n const slots = definition.slots ?? {};\n let corrected: Record<string, SlotBinding> = { ...overlay };\n if (lister) {\n for (const name of Object.keys(slots)) {\n const slot = slots[name];\n if (slot.type !== 'anchor' || slot.scope) continue;\n // The binding the cascade would settle on: a type-compatible selection over the default.\n const picked = ownGet(overlay, name);\n const binding = picked?.kind === 'anchor' ? picked : ownGet(base, name);\n if (binding?.kind !== 'anchor') continue;\n const { targetType, targetToken } = binding.anchor;\n let rows: Array<{ token: string }>;\n try {\n rows = await listEntities(lister, targetType, slotTypeFilter(slot, targetType));\n } catch {\n continue;\n }\n if (rows.length > 0 && !rows.some((r) => r.token === targetToken)) {\n // A computed key in a literal makes an own property even for '__proto__'.\n corrected = { ...corrected, [name]: { kind: 'anchor', anchor: { ...binding.anchor, targetToken: rows[0].token } } };\n }\n }\n }\n return resolveContextBindings(definition, base, corrected, resolver);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Context deep links: a URL that opens a board with a device already bound.\n//\n// \u2026/dashboards/<board>?bind.<slot>=<deviceToken> (console)\n// /dash/?dashboard=<board>&bind.<slot>=<deviceToken> (the viewer app)\n//\n// One query parameter per slot, named for the slot, valued with the device token. The slot name\n// travels in the KEY (not in a `bind=slot:token` pair) so it needs no escaping rule of its own\n// and a second slot is simply a second parameter.\n//\n// This file is the pure half: reading the parameters against a definition, writing them back,\n// and asking the resolver whether the caller can see each device. The React half, which keeps\n// the address and the selection in step, is useBoundDevices in @devicechain/widgets.\n//\n// \uD83D\uDD34 A LINK IS A CLAIM, NOT A GRANT. A token in a URL is whatever its sender typed, so every\n// device a link names is checked with the CALLER'S OWN credentials before it is bound. The\n// server answers a device outside the caller's tenant or customer scope exactly as it answers a\n// device that does not exist (it must not confirm that a row the caller may not see exists), so\n// \"no such device\" and \"not yours\" are ONE outcome here, never two that could be told apart.\n\nimport { isValidToken } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport type { DashboardDefinition, SlotBinding } from './types';\n\n// The query-parameter prefix a bound slot travels under.\nexport const BIND_PARAM_PREFIX = 'bind.';\n\n// Why a parameter was set aside without being asked of the server.\nexport type BindParamIssue =\n // `bind.x` where the board has no slot `x` (a link for another board, or a renamed slot).\n | { kind: 'unknown-slot'; slot: string; token: string }\n // `bind.x` where slot `x` is an anchor slot: a link binds a single device.\n | { kind: 'not-a-device-slot'; slot: string; token: string }\n // The value is not a token at all.\n | { kind: 'invalid-token'; slot: string; token: string }\n // `bind.x` given more than once. Which one the link \"means\" depends on which parser reads it\n // (first wins in one, last in another), so the link is ambiguous and neither is asked of the\n // server. `token` is the first value.\n | { kind: 'duplicate-slot'; slot: string; token: string }\n // `bind.x` where slot `x` follows its parent (`scope.strategy: 'first'`): the board derives\n // that slot's device and ignores any pick, so honouring the link would mean showing a device\n // other than the one in the address.\n | { kind: 'derived-slot'; slot: string; token: string };\n\nexport interface ParsedBinds {\n // slot \u2192 device token, for the parameters that name a real device slot with a well-formed token.\n binds: Record<string, string>;\n // Everything else a `bind.` parameter could have been, so the viewer is told rather than\n // shown a board that silently ignored half the link.\n issues: BindParamIssue[];\n}\n\nfunction ownSlot(definition: DashboardDefinition, slot: string) {\n const slots = definition.slots;\n return slots && Object.prototype.hasOwnProperty.call(slots, slot) ? slots[slot] : undefined;\n}\n\n// parseBindParams reads the `bind.<slot>` parameters of a query string against a board. Pure and\n// synchronous: it checks SHAPE only. Whether the device exists, and is the caller's to see, is\n// validateBinds.\nexport function parseBindParams(search: string, definition: DashboardDefinition): ParsedBinds {\n const binds: Record<string, string> = {};\n const issues: BindParamIssue[] = [];\n const params = new URLSearchParams(search);\n const seen = new Set<string>();\n for (const [key, token] of params) {\n if (!key.startsWith(BIND_PARAM_PREFIX)) continue;\n const slot = key.slice(BIND_PARAM_PREFIX.length);\n // Every occurrence after the first is the same ambiguity, reported once.\n if (seen.has(slot)) continue;\n seen.add(slot);\n const def = ownSlot(definition, slot);\n if (params.getAll(key).length > 1) issues.push({ kind: 'duplicate-slot', slot, token });\n else if (!def) issues.push({ kind: 'unknown-slot', slot, token });\n else if (def.type !== 'device') issues.push({ kind: 'not-a-device-slot', slot, token });\n else if (def.scope?.strategy === 'first') issues.push({ kind: 'derived-slot', slot, token });\n else if (!isValidToken(token)) issues.push({ kind: 'invalid-token', slot, token });\n else Object.defineProperty(binds, slot, { value: token, enumerable: true, writable: true, configurable: true });\n }\n return { binds, issues };\n}\n\n// The outcome of asking the server about each named device.\nexport interface BindCheck {\n // Devices the caller can see: safe to bind.\n ok: Record<string, string>;\n // Absent for this caller: deleted, never existed, or outside their tenant or customer scope.\n // Deliberately one bucket.\n missing: Array<{ slot: string; token: string }>;\n // The check itself failed (network, server error). Inconclusive, NOT \"missing\": retryable.\n failed: Array<{ slot: string; token: string }>;\n}\n\n// validateBinds asks the resolver (device-management, under the caller's own token) whether\n// each named device is visible. It never throws: a failure is reported per device as `failed`.\nexport async function validateBinds(binds: Record<string, string>, resolver: Pick<DeviceResolver, 'deviceExists'>): Promise<BindCheck> {\n const out: BindCheck = { ok: {}, missing: [], failed: [] };\n await Promise.all(\n Object.entries(binds).map(async ([slot, token]) => {\n try {\n if (await resolver.deviceExists(token)) Object.defineProperty(out.ok, slot, { value: token, enumerable: true, writable: true, configurable: true });\n else out.missing.push({ slot, token });\n } catch {\n out.failed.push({ slot, token });\n }\n }),\n );\n return out;\n}\n\n// bindsToSelection turns validated binds into the selection overlay the hosts hold.\nexport function bindsToSelection(binds: Record<string, string>): Record<string, SlotBinding> {\n const out: Record<string, SlotBinding> = {};\n for (const [slot, deviceToken] of Object.entries(binds)) {\n Object.defineProperty(out, slot, {\n value: { kind: 'device', deviceToken } satisfies SlotBinding,\n enumerable: true,\n writable: true,\n configurable: true,\n });\n }\n return out;\n}\n\n// selectionToBinds is the other direction: the device picks in a selection overlay, for slots\n// the board declares as device slots. An anchor pick is not carried (a link binds a device).\nexport function selectionToBinds(\n selection: Record<string, SlotBinding>,\n definition: DashboardDefinition,\n): Record<string, string> {\n const out: Record<string, string> = {};\n for (const slot of Object.keys(selection)) {\n const b = selection[slot];\n if (b.kind !== 'device' || ownSlot(definition, slot)?.type !== 'device') continue;\n Object.defineProperty(out, slot, { value: b.deviceToken, enumerable: true, writable: true, configurable: true });\n }\n return out;\n}\n\n// withBindParams returns `search` with its `bind.*` parameters replaced by `binds`, every other\n// parameter (kiosk, theme, dashboard, \u2026) untouched and in place. Returns the query WITHOUT a\n// leading `?`, so a host puts it wherever its router wants it; empty when nothing is left.\nexport function withBindParams(search: string, binds: Record<string, string>): string {\n const next = new URLSearchParams();\n for (const [k, v] of new URLSearchParams(search)) if (!k.startsWith(BIND_PARAM_PREFIX)) next.append(k, v);\n for (const slot of Object.keys(binds).sort()) next.append(BIND_PARAM_PREFIX + slot, binds[slot]);\n return next.toString();\n}\n\n// bindParamsOf is the `bind.*` slice of a query string, normalised (sorted, no `?`), so two\n// addresses can be compared for \"same link\" regardless of parameter order.\nexport function bindParamsOf(search: string): string {\n const only = new URLSearchParams();\n for (const [k, v] of new URLSearchParams(search)) if (k.startsWith(BIND_PARAM_PREFIX)) only.append(k, v);\n only.sort();\n return only.toString();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Slot authoring transforms (ADR-039). A dashboard is a reusable template: every\n// widget references a named slot, and each slot carries a default entity binding.\n// These pure (React/DOM-free, unit-tested) helpers convert a dashboard's concrete\n// device/anchor selectors into that model and drive the editor's rebind/prune. The\n// authoring UX stays \"pick a device/anchor\"; the storage becomes slot-based, and\n// identical bindings collapse into ONE shared slot (dedup) \u2014 so a host manifest\n// binds each real entity once, and CompositeWidget-style sharing falls out for free.\n\nimport { canScopeSlot } from './definition';\nimport {\n UNKNOWN_WIDGET_TYPE,\n type AnchorTarget,\n type DashboardDefinition,\n type DatasourceSelector,\n type LocationSelection,\n type SlotBinding,\n type SlotDefinition,\n type SlotCandidates,\n type SlotScope,\n type WidgetInstance,\n} from './types';\n\n// slotSelector builds the slot selector these transforms write, carrying the SERIES the\n// widget reads (measurement names, and the location series when one is named) while the\n// ENTITY moves onto the slot.\n//\n// The location key is omitted when absent rather than written as undefined, so a\n// measurement-only widget serializes exactly as it did before the location channel\n// existed \u2014 a migration must not make every stored dashboard dirty.\nfunction slotSelector(\n slot: string,\n measurements: string[],\n location: LocationSelection | undefined,\n): DatasourceSelector {\n return location\n ? { kind: 'slot', slot, measurements, location }\n : { kind: 'slot', slot, measurements };\n}\n\n// sameBinding \u2014 value equality of two entity bindings (drives dedup).\nexport function sameBinding(a: SlotBinding | undefined, b: SlotBinding | undefined): boolean {\n if (!a || !b) return a === b;\n if (a.kind === 'device' && b.kind === 'device') return a.deviceToken === b.deviceToken;\n if (a.kind === 'anchor' && b.kind === 'anchor') {\n return (\n a.anchor.relationship === b.anchor.relationship &&\n a.anchor.targetType === b.anchor.targetType &&\n a.anchor.targetToken === b.anchor.targetToken\n );\n }\n return false;\n}\n\n// bindingLabel \u2014 a readable slot label (the bound entity's token).\nfunction bindingLabel(b: SlotBinding): string {\n return b.kind === 'device' ? b.deviceToken : b.anchor.targetToken;\n}\n\n// nextSlotName \u2014 the lowest unused `slot-N` name in a slots map.\nexport function nextSlotName(slots: Record<string, SlotDefinition>): string {\n let n = 1;\n while (slots[`slot-${n}`]) n += 1;\n return `slot-${n}`;\n}\n\n// bindingOfSelector \u2014 the entity binding a concrete device/anchor selector names\n// (measurements are dropped \u2014 a binding is the entity only). undefined for a device\n// selector with no token, or a non-concrete selector.\nfunction bindingOfSelector(ds: DatasourceSelector | undefined): SlotBinding | undefined {\n if (ds?.kind === 'device') return ds.deviceToken ? { kind: 'device', deviceToken: ds.deviceToken } : undefined;\n // Guard an empty target token (symmetric with device): a token-less anchor names no\n // entity, so it can't be a binding \u2014 and parseSlotBinding would drop it, so slotting it\n // would silently lose the relationship/targetType on reload.\n if (ds?.kind === 'anchor') {\n return ds.anchor.targetToken ? { kind: 'anchor', anchor: ds.anchor } : undefined;\n }\n return undefined;\n}\n\n// findOrAddSlot returns the name of the slot bound to `binding`, creating one (in\n// the passed, freshly-copied map) when none exists. Dedup lives here. It only reuses a\n// PLAIN (unscoped) slot: a scoped slot follows a parent, so folding a plain widget onto\n// one that merely shares the same default binding would silently give the widget cascade\n// behavior it never asked for. The migration/authoring path only ever mints plain slots.\nfunction findOrAddSlot(slots: Record<string, SlotDefinition>, binding: SlotBinding): string {\n const existing = Object.keys(slots).find((k) => !slots[k].scope && sameBinding(slots[k].defaultBinding, binding));\n if (existing) return existing;\n const name = nextSlotName(slots);\n slots[name] = { type: binding.kind, label: bindingLabel(binding), defaultBinding: binding };\n return name;\n}\n\n// migrateToSlots rewrites every widget's concrete device/anchor selector into a slot\n// selector (default-bound to that entity), deduping identical bindings into one\n// shared slot. Idempotent: slot selectors (and slot-free widgets) pass through\n// untouched, so re-running is a no-op. This is the decisive pre-GA cutover applied\n// when the console loads a dashboard.\nexport function migrateToSlots(def: DashboardDefinition): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const ds = w.datasource;\n // Leave an anchor that carries a Phase-2 `aggregation` as a concrete selector: the\n // slot model has no aggregation field, so slotting it would drop that (reserved)\n // config. It coexists fine \u2014 the Hub resolves concrete anchors too. (Editing such a\n // widget in the panel still drops aggregation, but that's an explicit action.)\n if (ds?.kind === 'anchor' && ds.aggregation) return w;\n const binding = bindingOfSelector(ds);\n if (!binding || !ds) return w;\n changed = true;\n const slot = findOrAddSlot(slots, binding);\n // The location series moves across with the measurement names: it says WHICH SERIES\n // the widget reads, and slotting changes only WHICH ENTITY it reads them from.\n // Dropping it here would silently blank every map on the board at load time.\n return { ...w, datasource: slotSelector(slot, ds.measurements, ds.location) };\n });\n // Return the SAME reference (no spurious `slots:{}`) when nothing migrated, so the\n // early-out is a true no-op and the result round-trips byte-identically.\n if (!changed) return def;\n return { ...def, widgets, slots };\n}\n\n// bindWidgetSlot points a widget at the slot for `binding` (creating/reusing it,\n// deduped) with the given measurement names \u2014 the editor's rebind + measurements\n// operation. Picking a NEW entity forks a fresh slot, leaving other widgets' slots\n// alone. A measurements-only edit (binding unchanged) KEEPS the widget's own slot even\n// when another slot shares the binding \u2014 so a distinct slot the host may override\n// separately (I-3 templates) isn't silently collapsed away.\n// `location` names the location series the widget reads, when it reads one. It is a\n// parameter rather than something preserved from the widget's current selector for the\n// same reason `measurements` is: the caller (the config panel) is the one that knows\n// which series the widget wants, and a map widget's FIRST datasource has no previous\n// selector to preserve it from.\nexport function bindWidgetSlot(\n def: DashboardDefinition,\n widgetId: string,\n binding: SlotBinding,\n measurements: string[],\n location?: LocationSelection,\n): DashboardDefinition {\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n const current = def.widgets.find((w) => w.id === widgetId)?.datasource;\n const currentSlot = current?.kind === 'slot' ? current.slot : undefined;\n const currentDef = currentSlot ? slots[currentSlot] : undefined;\n // Keep the widget's own slot when it is SCOPED (a context slot the cascade drives \u2014\n // never silently rehome it onto a plain slot) or when the binding is unchanged;\n // otherwise fork/reuse a plain slot for the new entity.\n const slot =\n currentSlot && currentDef && (currentDef.scope || sameBinding(currentDef.defaultBinding, binding))\n ? currentSlot\n : findOrAddSlot(slots, binding);\n const widgets = def.widgets.map((w) =>\n // A placeholder for an unknown widget type binds nothing this build can see, so it is\n // never given a datasource here: its bindings live in its raw object.\n w.id === widgetId && w.type !== UNKNOWN_WIDGET_TYPE ? { ...w, datasource: slotSelector(slot, measurements, location) } : w,\n );\n return { ...def, widgets, slots };\n}\n\n// clearWidgetDatasource drops a widget's datasource (the \"None\" data source). Prune\n// afterwards to reclaim a now-orphaned slot.\nexport function clearWidgetDatasource(def: DashboardDefinition, widgetId: string): DashboardDefinition {\n const widgets = def.widgets.map((w) => {\n if (w.id !== widgetId) return w;\n const { datasource: _drop, ...rest } = w;\n return rest;\n });\n return { ...def, widgets };\n}\n\n// collectRawSlotReferences adds every slot name a raw (unparsed) widget object names,\n// in the two shapes this release knows a slot reference by \u2014 a slot selector\n// `{kind:'slot', slot}` and a `selectionTarget` \u2014 at any depth. Over-counting keeps an\n// unused slot, which is harmless; under-counting deletes a newer widget's binding. The\n// depth bound only guards against a hand-built cyclic object (parsed JSON has none).\nconst RAW_SLOT_SCAN_DEPTH = 32;\nfunction collectRawSlotReferences(value: unknown, used: Set<string>, depth: number): void {\n if (depth > RAW_SLOT_SCAN_DEPTH || typeof value !== 'object' || value === null) return;\n if (Array.isArray(value)) {\n for (const item of value) collectRawSlotReferences(item, used, depth + 1);\n return;\n }\n const rec = value as Record<string, unknown>;\n if (rec.kind === 'slot' && typeof rec.slot === 'string' && rec.slot.length > 0) used.add(rec.slot);\n if (typeof rec.selectionTarget === 'string' && rec.selectionTarget.length > 0) used.add(rec.selectionTarget);\n for (const child of Object.values(rec)) collectRawSlotReferences(child, used, depth + 1);\n}\n\n// pruneSlots removes slots no widget references, and omits the `slots` key entirely\n// when none remain (so a dashboard that loses its last slot serializes clean).\nexport function pruneSlots(def: DashboardDefinition): DashboardDefinition {\n if (!def.slots) return def;\n const defSlots = def.slots;\n const used = new Set<string>();\n for (const w of def.widgets) {\n if (w.datasource?.kind === 'slot') used.add(w.datasource.slot);\n // A widget can also REFERENCE a slot without binding it: a context/entity-selector's\n // (and an alarm-table drill's) `options.selectionTarget` names the slot it re-points.\n // Miss it and deleting the data widget that binds that slot would prune it out from\n // under the selector \u2014 a silent \"No options\". Count it as a use.\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target.length > 0) used.add(target);\n // A widget of a type this build does not know uses slots this build cannot see in\n // its parsed fields: they are inside its raw object, possibly nested (a container's\n // children). Pruning them would make the next save delete the newer widget's slots\n // out from under it, so every slot the raw object names counts as a use.\n if (w.raw) collectRawSlotReferences(w.raw, used, 0);\n }\n // Close `used` over scope.parent: a context-only parent slot (e.g. a root 'building'\n // that no widget binds directly but a scoped child depends on) must be kept, or the\n // child dangles and the cascade loses its top-level context. Walk each used slot's\n // ancestor chain (guarded against a hand-edited cycle) adding every parent.\n for (const start of [...used]) {\n let cur: string | undefined = start;\n const guard = new Set<string>();\n while (cur && Object.prototype.hasOwnProperty.call(defSlots, cur) && !guard.has(cur)) {\n guard.add(cur);\n const parent: string | undefined = defSlots[cur].scope?.parent;\n if (parent) used.add(parent);\n cur = parent;\n }\n }\n const slots: Record<string, SlotDefinition> = {};\n for (const [name, slot] of Object.entries(def.slots)) if (used.has(name)) slots[name] = slot;\n if (Object.keys(slots).length === 0) {\n const { slots: _drop, ...rest } = def;\n return rest;\n }\n return { ...def, slots };\n}\n\n// anchorSlotNames lists the dashboard's anchor-typed slots \u2014 the candidate PARENTS a\n// scoped slot may attach to (an editor's parent picker), and the slots a root context-\n// selector can target. Empty when the dashboard has no slots.\nexport function anchorSlotNames(def: DashboardDefinition): string[] {\n const slots = def.slots ?? {};\n return Object.keys(slots).filter((name) => slots[name].type === 'anchor');\n}\n\n// setSlotScope makes `slotName` scoped to a parent (the context hierarchy) or, with\n// `scope` undefined, clears its scope back to a root context \u2014 the editor's scope-authoring\n// transform. It is a no-op when the slot doesn't exist, or when the proposed scope is\n// invalid (canScopeSlot: parent missing/non-anchor/self/cycle) \u2014 degrade, never write a\n// scope the loader would drop on reload. Prototype-free (operates on the parsed slots map).\nexport function setSlotScope(\n def: DashboardDefinition,\n slotName: string,\n scope: SlotScope | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const slots: Record<string, SlotDefinition> = { ...def.slots };\n const current = slots[slotName];\n if (!scope) {\n if (!current.scope) return def; // already root \u2014 no-op (keeps the reference stable)\n const { scope: _drop, ...rest } = current;\n slots[slotName] = rest;\n return { ...def, slots };\n }\n if (!canScopeSlot(slots, slotName, scope.parent)) return def;\n slots[slotName] = { ...current, scope: { parent: scope.parent, strategy: scope.strategy } };\n return { ...def, slots };\n}\n\n// setSlotCandidates sets (or, with undefined/empty, clears) the picker type filter of a ROOT\n// slot - the area type or asset type an anchor slot's picker lists, or the device type a device\n// slot's does. Only the field that fits the slot's kind is kept. A no-op when the slot does not\n// exist or is scoped (a scoped slot's options are its parent's members, which no type\n// filter applies to), so the editor can never write a field the loader would ignore.\nexport function setSlotCandidates(\n def: DashboardDefinition,\n slotName: string,\n candidates: SlotCandidates | undefined,\n): DashboardDefinition {\n if (!def.slots || !Object.prototype.hasOwnProperty.call(def.slots, slotName)) return def;\n const current = def.slots[slotName];\n if (current.scope) return def;\n const next: SlotCandidates = {};\n if (current.type === 'anchor') {\n if (candidates?.areaType) next.areaType = candidates.areaType;\n if (candidates?.assetType) next.assetType = candidates.assetType;\n } else if (candidates?.deviceType) {\n next.deviceType = candidates.deviceType;\n }\n // A filter that names only a field the slot's kind cannot use is a no-op, not a silent clear.\n const asked = !!(candidates?.areaType || candidates?.assetType || candidates?.deviceType);\n if (asked && !next.areaType && !next.assetType && !next.deviceType) return def;\n const { candidates: _drop, ...rest } = current;\n const slot: SlotDefinition = next.areaType || next.assetType || next.deviceType ? { ...rest, candidates: next } : rest;\n return { ...def, slots: { ...def.slots, [slotName]: slot } };\n}\n\n// widgetBinding resolves a widget's effective entity binding for the editor: a slot\n// widget's default binding, or a (pre-migration) concrete selector's entity.\nexport function widgetBinding(def: DashboardDefinition, widget: WidgetInstance): SlotBinding | undefined {\n const ds = widget.datasource;\n if (ds?.kind === 'slot') return def.slots?.[ds.slot]?.defaultBinding;\n return bindingOfSelector(ds);\n}\n\n// widgetSlotName is the slot a widget references, if any (for an editor hint).\nexport function widgetSlotName(widget: WidgetInstance): string | undefined {\n return widget.datasource?.kind === 'slot' ? widget.datasource.slot : undefined;\n}\n\n// A concrete device/anchor selector \u2014 what the config panel edits (slot-agnostic).\n// Rebuilt into slot storage by the workspace via bindWidgetSlot.\nexport type ConcreteSelector =\n | { kind: 'device'; deviceToken: string; measurements: string[]; location?: LocationSelection }\n | { kind: 'anchor'; anchor: AnchorTarget; measurements: string[]; location?: LocationSelection };\n\n// resolveConcrete gives the config panel a slot-free view of a widget's data source:\n// the bound entity + the series the widget reads (measurement names, and the location\n// series when it names one), or undefined when unbound.\n//\n// \uD83D\uDD34 A SELECTOR THE AUTHOR IS STILL FILLING IN IS RETURNED AS IT STANDS. Choosing the Device\n// kind stores `{kind:'device', deviceToken:''}` on the widget (the workspace keeps a partial\n// selector concrete rather than slotting it -- see applyDatasource). It names no entity, so\n// it has no binding; but the panel renders from THIS value, and answering undefined for it\n// snapped the Kind control straight back to \"None\" and never showed the device picker, so a\n// device could not be chosen at all.\nexport function resolveConcrete(\n def: DashboardDefinition,\n widget: WidgetInstance,\n): ConcreteSelector | undefined {\n const binding = widgetBinding(def, widget);\n const measurements = widget.datasource?.measurements ?? [];\n const location = widget.datasource?.location;\n const base = location ? { measurements, location } : { measurements };\n if (!binding) {\n const ds = widget.datasource;\n if (ds?.kind === 'device') return { kind: 'device', deviceToken: ds.deviceToken, ...base };\n if (ds?.kind === 'anchor') return { kind: 'anchor', anchor: ds.anchor, ...base };\n return undefined;\n }\n return binding.kind === 'device'\n ? { kind: 'device', deviceToken: binding.deviceToken, ...base }\n : { kind: 'anchor', anchor: binding.anchor, ...base };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// 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<string, Promise<Array<{ token: string; name: string | null }>>>();\n const criteria = { pageNumber: 1, pageSize: LIST_PAGE_SIZE };\n\n return (kind: EntityListKind, typeToken?: string) => {\n const key = `${kind}|${typeToken}`;\n let pending = cache.get(key);\n if (!pending) {\n pending = fetchKind(kind, criteria, typeToken).catch((err) => {\n cache.delete(key);\n throw err;\n });\n cache.set(key, pending);\n }\n return pending;\n };\n}\n\nfunction fetchKind(\n kind: EntityListKind,\n criteria: { pageNumber: number; pageSize: number },\n typeToken?: string,\n): Promise<Array<{ token: string; name: string | null }>> {\n switch (kind) {\n case 'device':\n return gql('device-management', LIST_DEVICES, { criteria: { ...criteria, deviceType: typeToken } }).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: { ...criteria, areaTypeToken: typeToken } }).then((r) => r.areas.results);\n case 'asset':\n return gql('device-management', LIST_ASSETS, { criteria: { ...criteria, assetTypeToken: typeToken } }).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 of a customer anchor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A customer anchor reads customerDevices rather than the relationship graph: the graph is\n// closed to a member limited to some customers (a customer user), and customerDevices\n// answers such a member about its own customers. For every other viewer it answers what\n// the relationship search did for (device -> customer, type), so ONE read serves\n// every viewer. It carries each device's name, so the roster needs no second read.\n\nexport interface CustomerDevicesResult {\n customerDevices: {\n results: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n pagination?: { totalRecords: number | null } | null;\n };\n}\nexport interface CustomerDevicesVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n customer: string;\n relationshipType: string;\n };\n}\n\nexport const CUSTOMER_DEVICES = `\n query DashboardCustomerDevices($criteria: CustomerDeviceSearchCriteria!) {\n customerDevices(criteria: $criteria) {\n results {\n token\n name\n deviceType {\n token\n }\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<CustomerDevicesResult, CustomerDevicesVariables>;\n\n// \u2500\u2500 device-management: the devices on an area or an asset anchor \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// anchorDevices answers every viewer for an area or an asset: an unscoped caller gets the devices\n// with a live edge to the anchor, and a member limited to some customers gets the same list\n// narrowed to the devices it can see (the anchor itself needs no permission of its own, since an\n// anchor none of its devices is on answers an empty page). The relationship graph that read this\n// before is closed to such a member. relationshipType is optional on the wire and REFUSED for a\n// scoped caller (SCOPE_UNSUPPORTED): the resolver sends it first and retries without it.\n\nexport interface AnchorDevicesResult {\n anchorDevices: {\n results: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n pagination?: { totalRecords: number | null } | null;\n };\n}\nexport interface AnchorDevicesVariables {\n criteria: {\n pageNumber: number;\n pageSize: number;\n targetType: string;\n target: string;\n relationshipType?: string | null;\n };\n}\n\nexport const ANCHOR_DEVICES =\n 'query DashboardAnchorDevices($criteria: AnchorDeviceSearchCriteria!){anchorDevices(criteria:$criteria){results{token name deviceType{token}}pagination{totalRecords}}}' as unknown as TypedDocument<AnchorDevicesResult, AnchorDevicesVariables>;\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// -- device-management: the names of a set of devices (one batched read) ------\n// A fleet widget labels its rows with device names, not tokens. devicesByToken takes up to 1000\n// tokens, and an anchor resolves to at most one page of 500, so the whole roster's names are ONE\n// round trip however large the fleet.\n\nexport interface DeviceNamesResult {\n devicesByToken: Array<{ token: string; name: string | null; deviceType?: { token: string } | null }>;\n}\n\nexport const DEVICE_NAMES = `\n query DashboardDeviceNames($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n name\n deviceType {\n token\n }\n }\n }\n` as unknown as TypedDocument<DeviceNamesResult, DevicesByTokenVariables>;\n\n// -- device-management: what a device type's ACTIVE profile version declares ---\n// The names a fleet widget reads to tell \"does not report this\" (the device's profile never\n// declares the measurement) from \"not reporting\" (it declares it and the device is silent). A\n// catalogue read: a member limited to some customers may make it. Calculated metrics are part of\n// the same list. activeProfileVersion is null until the profile is first published, and profile is\n// null on a type with none: neither declares anything, so neither can say a measurement is absent.\n\nexport interface DeclaredMetricsResult {\n deviceTypesByToken: Array<{\n token: string;\n profile: { activeProfileVersion: { metrics: Array<{ metricKey: string }> } | null } | null;\n }>;\n}\nexport interface DeclaredMetricsVariables {\n tokens: string[];\n}\n\nexport const DECLARED_METRICS = `\n query DashboardDeclaredMetrics($tokens: [String!]!) {\n deviceTypesByToken(tokens: $tokens) {\n token\n profile {\n activeProfileVersion {\n metrics {\n metricKey\n }\n }\n }\n }\n }\n` as unknown as TypedDocument<DeclaredMetricsResult, DeclaredMetricsVariables>;\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: {\n pageNumber: number;\n pageSize: number;\n areaTypeToken?: string | null;\n assetTypeToken?: string | null;\n deviceType?: string | null;\n };\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 // The bucket's other aggregates, exact over the readings it holds (null when it holds none).\n // Optional on the type because the chart seed reads only `avg`; the aggregate widgets read\n // these and refuse a bucket that has readings but lacks them.\n min?: number | null;\n max?: number | null;\n sum?: number | null;\n count?: number;\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 min\n max\n sum\n count\n }\n }\n` as unknown as TypedDocument<BucketedMeasurementsResult, BucketedMeasurementsVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// sameLabel -- whether two display strings say the same thing: equal after trimming and\n// ignoring case. The ONE predicate behind \"this subtitle only repeats the title\": the widget\n// frame uses it to drop a duplicate subject line, and the layout lint uses it to flag a board\n// that would show one, so the two can never disagree about what counts as a repeat. Empty or\n// missing strings never match anything.\nexport function sameLabel(a: string | null | undefined, b: string | null | undefined): boolean {\n if (!a || !b) return false;\n const x = a.trim().toLowerCase();\n return x !== '' && x === b.trim().toLowerCase();\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The hub's live-connection state, as a viewer should read it: is the data on this board\n// arriving (live), is the socket down and being retried (reconnecting), or has it been down\n// long enough, or the browser itself gone offline, that the board should be treated as frozen\n// (offline).\n//\n// \uD83D\uDD34 It is derived from the SOCKET events the client already surfaces (connected / closed),\n// per open subscription, not from the age of any sample. A quiet sensor on a healthy socket\n// is `live`; sample age is the freshness indicator's job, and the two answer different\n// questions.\n\nexport type HubConnectionState = 'live' | 'reconnecting' | 'offline';\n\n// How long a socket may stay down before \"reconnecting\" is no longer an honest word for it.\n// graphql-ws retries with a growing back-off, so a drop that outlasts this is not a blip.\nexport const OFFLINE_AFTER_MS = 20_000;\n\n// How long the sockets must stay up before a drop is forgotten. A server that accepts and then\n// drops the socket (a crash-looping pod, a proxy timing out the upgrade) reconnects every few\n// seconds; if each brief reconnect restarted the OFFLINE_AFTER_MS clock, that board would read\n// \"reconnecting\" forever and never \"offline\". Within this window a new drop resumes the\n// original clock. The badge still shows `live` the moment the socket is up, because it is.\nexport const STABLE_AFTER_MS = 10_000;\n\n// One open subscription's view of its socket. A subscription starts UP: the shared per-area\n// socket may already be connected, and the client then emits no `connected` for a late\n// joiner, so waiting for one would leave a healthy board stuck \"reconnecting\".\nexport interface ConnectionHandle {\n connected(): void;\n closed(): void;\n // The subscription is gone; its socket state no longer counts.\n release(): void;\n}\n\nexport class ConnectionTracker {\n private readonly down = new Set<ConnectionHandle>();\n private readonly listeners = new Set<() => void>();\n private escalated = false;\n private escalate: ReturnType<typeof setTimeout> | undefined;\n private recover: ReturnType<typeof setTimeout> | undefined;\n private browserOffline = false;\n private current: HubConnectionState = 'live';\n private detach: (() => void) | undefined;\n\n constructor(\n private readonly offlineAfterMs: number = OFFLINE_AFTER_MS,\n private readonly stableAfterMs: number = STABLE_AFTER_MS,\n ) {}\n\n get state(): HubConnectionState {\n return this.current;\n }\n\n register(): ConnectionHandle {\n const handle: ConnectionHandle = {\n connected: () => {\n this.down.delete(handle);\n this.settle();\n },\n closed: () => {\n this.down.add(handle);\n this.settle();\n },\n release: () => {\n this.down.delete(handle);\n this.settle();\n },\n };\n return handle;\n }\n\n // Listeners are what keep the browser online/offline hooks attached, so a hub nobody is\n // watching holds no window listeners. Returns the unsubscribe.\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n if (this.listeners.size === 1) this.attach();\n return () => {\n this.listeners.delete(listener);\n if (this.listeners.size === 0) this.release();\n };\n }\n\n private attach(): void {\n if (typeof window === 'undefined') return;\n const sync = (): void => {\n this.browserOffline = typeof navigator !== 'undefined' && navigator.onLine === false;\n this.settle();\n };\n window.addEventListener('offline', sync);\n window.addEventListener('online', sync);\n this.detach = () => {\n window.removeEventListener('offline', sync);\n window.removeEventListener('online', sync);\n };\n sync();\n }\n\n private release(): void {\n this.detach?.();\n this.detach = undefined;\n this.browserOffline = false;\n this.settle();\n }\n\n private settle(): void {\n if (this.down.size === 0) {\n // Up again: forget the drop only once the sockets have stayed up (STABLE_AFTER_MS).\n if ((this.escalate || this.escalated) && !this.recover) {\n this.recover = setTimeout(() => {\n this.recover = undefined;\n if (this.escalate) clearTimeout(this.escalate);\n this.escalate = undefined;\n this.escalated = false;\n }, this.stableAfterMs);\n }\n } else {\n // Down before the recovery window closed: keep the original clock running.\n if (this.recover) clearTimeout(this.recover);\n this.recover = undefined;\n }\n if (this.down.size > 0 && !this.escalate && !this.escalated) {\n this.escalate = setTimeout(() => {\n this.escalate = undefined;\n this.escalated = true;\n this.settle();\n }, this.offlineAfterMs);\n }\n const next: HubConnectionState = this.browserOffline\n ? 'offline'\n : this.down.size === 0\n ? 'live'\n : this.escalated\n ? 'offline'\n : 'reconnecting';\n if (next === this.current) return;\n this.current = next;\n for (const listener of [...this.listeners]) listener();\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type { AlarmRow, CommandRow } from './types';\n\n// The time range an event read covers, for a widget that draws events on a time axis: the\n// `spanSeconds` that end at `endAt` (epoch ms), or, without `endAt`, at the source's now() when\n// each read is made, so a live range keeps sliding. Every source applies it BEFORE paging, so a\n// snapshot's `total` counts the range and nothing outside it.\nexport interface EventWindow {\n spanSeconds: number;\n endAt?: number;\n}\n\n// The instants a window covers at `now`. `to` is undefined for a live window: the read is open\n// at the top, since nothing has happened after now.\nexport function eventWindowBounds(window: EventWindow, now: number): { from: number; to: number | undefined } {\n const end = window.endAt ?? now;\n return { from: end - window.spanSeconds * 1000, to: window.endAt };\n}\n\n// The range criteria of the alarms and commands queries (RFC 3339; null = unbounded), at `now`.\nexport function windowCriteria(window: EventWindow | undefined, now: number): { startTime: string | null; endTime: string | null } {\n if (!window) return { startTime: null, endTime: null };\n const { from, to } = eventWindowBounds(window, now);\n return { startTime: new Date(from).toISOString(), endTime: to === undefined ? null : new Date(to).toISOString() };\n}\n\nconst instant = (iso: string | null | undefined): number | null => {\n if (!iso) return null;\n const t = Date.parse(iso);\n return Number.isNaN(t) ? null : t;\n};\n\n// The platform's alarm range, for a source that answers in memory: the alarm's span (raise to\n// clear, or still active) overlaps the window. A row with no raise time cannot be placed.\nexport function alarmOverlaps(row: Pick<AlarmRow, 'raisedTime' | 'clearedTime'>, window: EventWindow, now: number): boolean {\n const { from, to } = eventWindowBounds(window, now);\n const raised = instant(row.raisedTime);\n if (raised === null || (to !== undefined && raised > to)) return false;\n const cleared = instant(row.clearedTime);\n return cleared === null || cleared >= from;\n}\n\n// The platform's command range, for a source that answers in memory: sent (or queued, when\n// never sent) inside the window.\nexport function commandInRange(row: Pick<CommandRow, 'sentTime' | 'queuedTime'>, window: EventWindow, now: number): boolean {\n const { from, to } = eventWindowBounds(window, now);\n const at = instant(row.sentTime) ?? instant(row.queuedTime);\n return at !== null && at >= from && (to === undefined || at <= to);\n}\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 { windowCriteria, type EventWindow } from './event-window';\nimport { gql, isForbiddenError, isScopeUnsupportedError, subscribe, type Area, type SubscriptionSink } from '@devicechain/client';\n\nimport { ConnectionTracker, type HubConnectionState } from './connection';\nimport type { HistoryWindow } from './history';\nimport {\n ACKNOWLEDGE_ALARM,\n ALARM_STREAM,\n ALARM_DEVICE_TYPES,\n ALARM_SEVERITY_COUNTS,\n ALARM_TYPE_COUNTS,\n ALARMS_QUERY,\n CLEAR_ALARM,\n type AlarmAnchorInput,\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 { DEVICE_PRESENCE_QUERY } from './internal/presence-doc';\nimport { createLimiter, MEMBER_SEED_CONCURRENCY, SEED_CANCELLED } from './members';\nimport type { MemberBucketCriteria, MemberRef, MemberRoster, MemberSeed, MemberSeedPlan, MemberSink, PresenceSink, SeedFailure } from './members';\nimport { BUCKETED_MEASUREMENTS, type MeasurementBucket } from './queries';\nimport {\n MEASUREMENT_STREAM,\n type MeasurementStreamResult,\n type MeasurementStreamVariables,\n} from './internal/measurement-doc';\nimport type {\n AlarmDeviceType,\n AlarmRow,\n AlarmSeverityCount,\n AlarmTypeCount,\n AnchorTarget,\n CommandRow,\n DatasourceSelector,\n DevicePresence,\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// devicesByToken takes at most 1000 tokens.\nconst ALARM_DEVICE_TYPES_CHUNK = 1000;\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// Presence channel cadence. device-state exposes NO presence subscription, so like the location\n// channel this is poll-only and the poll IS the channel; the same batch-per-tick reasoning applies\n// (one `deviceStatesByDeviceToken` round trip per tick covers every device on the widget). Presence\n// changes on the scale of an inactivity timeout (minutes), so it polls at the location cadence rather\n// than faster: a tighter poll would multiply a board's queries to learn nothing sooner.\nconst PRESENCE_POLL_MS = 15_000;\n// deviceStatesByDeviceToken refuses more than 1000 tokens with LIMIT_EXCEEDED.\nconst PRESENCE_CHUNK = 1000;\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 // The anchor's members with their names, and the anchor's true size (which can exceed the page\n // the resolver reads). Optional: a resolver without it yields tokens only, and a fleet widget then\n // labels rows by token and cannot report a truncated anchor.\n anchorMembers?(anchor: AnchorTarget): Promise<{ members: MemberRef[]; total: number }>;\n // The measurement names the ACTIVE profile version of a device type declares (calculated metrics\n // included), or null when it declares nothing to consult. Rejects when the read fails. Optional:\n // a resolver without it leaves every member's declaration unknown.\n declaredMetrics?(deviceType: string): Promise<string[] | null>;\n // The token of the device type a device belongs to, or null when it has none (or does not exist).\n // Rejects when the read fails. Optional: a resolver without it cannot say what a single device's\n // profile declares.\n deviceType?(deviceToken: string): Promise<string | null>;\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. reconnected() fires when one of its device streams resumes\n// after an interruption -- the socket dropped and came back, or the server ended the stream and it\n// was opened again. The samples sent meanwhile were never delivered and never will be: a widget\n// that draws history re-reads it rather than joining the readings either side of the outage.\n// Optional (a source that never drops a feed does not call it).\nexport interface WidgetStreamSink {\n next: (sample: MeasurementSample) => void;\n error?: (err: unknown) => void;\n reconnected?: () => 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// What an alarm subscription reads: tenant-wide, one anchored read, or one read per device token.\ninterface AlarmScope {\n tenantWide: boolean;\n tokens: string[];\n anchor?: AlarmAnchorInput;\n}\nconst STAMPED = new Set(['customer', 'area', 'asset']);\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 // Resolve each row's originating device type (AlarmRow.deviceType) with one batched read per\n // reconcile. Off by default: an alarm table that does not group by type pays nothing.\n enrichDeviceType?: boolean;\n // Keep only alarms whose originating device has this device-type TOKEN.\n // Filtered by device-management, so `total` is the exact match count.\n deviceType?: string;\n // Read the per-type counts of the matching alarms instead of the alarms: the snapshot then has no\n // rows, `typeCounts` is set, and `total` is their sum. A source that cannot count by type leaves\n // `typeCounts` unset (or fails the stream); it never answers with counts it did not make.\n countByDeviceType?: boolean;\n // Read the per-severity counts of the matching alarms instead of the alarms: the snapshot then has\n // no rows, `severityCounts` is set, and `total` is their sum. Tenant-wide, EVERY alarm the viewer\n // can see counts, whatever its originator (a device, an asset, an area, a customer, a group), so\n // the total is the alarm count's. With `deviceType`, only alarms on devices of that type count. A\n // source that cannot count by severity leaves `severityCounts` unset (or fails the stream).\n countBySeverity?: boolean;\n // Keep only alarms whose span (raise to clear, or still active) overlaps this range, filtered\n // by device-management, so `total` is the exact count in range. An alarm keeps only its latest\n // raise, so an earlier raise of the same alarm is in no range.\n window?: EventWindow;\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 // Only for a subscription with countByDeviceType: one entry per (device type, severity) present.\n typeCounts?: AlarmTypeCount[];\n // Only for a subscription with countBySeverity: one entry per severity present.\n severityCounts?: AlarmSeverityCount[];\n}\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 // Keep only commands sent inside this range (queued, for one never sent), filtered by\n // command-delivery, so `total` is the exact count in range.\n window?: EventWindow;\n}\n\n// A command-history snapshot: the resolved target device (null when unbound \u2014 the\n// widget then can't issue), the recent commands (newest first, capped to pageSize) and\n// the total matching count. One replaces the last (poll-then-emit).\nexport interface CommandSnapshot {\n deviceToken: string | null;\n commands: CommandRow[];\n total: number;\n}\n\nexport interface CommandStreamSink {\n next: (snapshot: CommandSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// The result of issuing a command. It is a DISCRIMINATED UNION rather than a token,\n// because the enqueue has two possible answers and a widget must not show them the same\n// way:\n//\n// - 'sent' \u2014 the command was created; `token` is its freshly-minted dispatch\n// token, so the widget can highlight the command it just issued as it\n// moves through the lifecycle.\n// - 'rejected' \u2014 the server REFUSED the request and said why: `code` is the stable\n// classification to branch on (DEVICE_NOT_FOUND,\n// COMMAND_NOT_IN_VOCABULARY, PAYLOAD_SCHEMA_VIOLATION,\n// PAYLOAD_NOT_JSON, METADATA_NOT_JSON, EXPIRES_AT_INVALID,\n// HELD_CEILING_EXCEEDED, \u2026 \u2014 an OPEN set), `reason` is client-safe\n// prose to show a person.\n//\n// A THROWN error is neither: it means the platform could not decide the enqueue at all\n// (the service unreachable, a database error), which says nothing about the command and\n// so deserves a generic failure message rather than a reason.\n//\n// The union is deliberately not \"token, plus an optional rejection\". A caller reading\n// `.token` off a refusal would compile, highlight nothing, and report no failure \u2014 the\n// send would look successful. The discriminant makes the refusal impossible to ignore\n// at the type level.\nexport type CommandDispatch =\n | { status: 'sent'; token: string }\n | { status: 'rejected'; code: string; reason: string };\n\n// \u2500\u2500 Location channel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// The map widget consumes a fifth surface: where the bound devices ARE. Like the\n// control channel there is no live subscription to ride (device-state exposes none for\n// position), so it is query-then-poll \u2014 the alarm and command channels are the\n// precedent here, not the measurement channel.\n//\n// What is different from all three is the REFUSAL. Position is gated on its own\n// `location:read` authority, which is deliberately absent from the read-only viewer\n// baseline, so an ordinary member with full telemetry access is routinely refused. The\n// channel therefore turns a refusal into a VALUE rather than an error: it is not a\n// fault, and it is not emptiness either.\n\n// LocationSubscription is one map widget's interest: the selector naming both the\n// devices and the location series to read. There is no page size \u2014 a map shows every\n// device it is bound to, and the bound set is the selector's own resolution.\nexport interface LocationSubscription {\n datasource?: DatasourceSelector;\n}\n\n// A location snapshot, discriminated so the refusal cannot be mistaken for emptiness.\n//\n// \uD83D\uDD34 `positions` with an empty `locations` and `forbidden` are OPPOSITE facts, and only\n// one of them is actionable. \"No device here has ever reported a position\" is a claim\n// about the DEVICES; \"you may not view location\" is a claim about the CALLER. Folding\n// them together (a bare `locations: []`) would tell an operator their fleet is\n// unlocated when the truth is that they need a role \u2014 which is the exact mistake the\n// device-detail position panel was built to avoid, held here in the type.\n//\n// `deviceTokens` is the set the selector resolved to, carried alongside the positions\n// because a never-located device is ABSENT from the query result: without it, \"bound to\n// nothing\" and \"bound to devices that have never moved\" are indistinguishable.\nexport type LocationSnapshot =\n | { kind: 'positions'; deviceTokens: string[]; locations: LocationSample[] }\n | { kind: 'forbidden' };\n\nexport interface LocationStreamSink {\n next: (snapshot: LocationSnapshot) => void;\n error?: (err: unknown) => void;\n}\n\n// \u2500\u2500 Action seam (writes) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Read widgets are pure `(widget, data)`; a widget that ACTS (acknowledge/clear an\n// alarm, send a command) never touches the SDK either \u2014 it calls this seam, which the\n// renderer threads in from the runtime. So preview stays offline (SyntheticDataSource\n// implements a no-op/echo WidgetActions) and the \"widget never reaches the backend\"\n// invariant holds. `can` gates the UI: a widget hides an action the viewer isn't\n// authorized for (the server enforces it regardless).\n//\n// Growing this interface is a breaking change for any external host implementing it.\n// The alarm actions (acknowledgeAlarm/clearAlarm) are the required baseline; capabilities\n// added later \u2014 like sendCommand below \u2014 are declared OPTIONAL so a host predating them\n// still satisfies the type, and consumers feature-detect (typeof actions?.sendCommand).\nexport interface WidgetActions {\n // Acknowledge / clear a raised alarm by token (requires alarm:write). Resolves when\n // the mutation succeeds; the runtime reconciles the affected alarm widgets so the\n // change shows immediately rather than waiting for the next poll.\n acknowledgeAlarm(alarmToken: string): Promise<void>;\n clearAlarm(alarmToken: string): Promise<void>;\n // Issue a command to a device (requires command:write). The runtime mints the dispatch\n // token and returns it; it also reconciles the command widgets so the new command\n // appears at once. It RESOLVES on a refusal too \u2014 with a 'rejected' dispatch carrying\n // the server's code + reason (see CommandDispatch) \u2014 and rejects only when the enqueue\n // could not be decided at all. OPTIONAL: added after this interface shipped, so a host\n // predating it (or a strictly read-only one) may omit it \u2014 a command widget then\n // renders its Send control disabled. `payload` is the request body sent to the device\n // verbatim (the widget serializes its typed parameter form to JSON).\n sendCommand?(deviceToken: string, name: string, payload?: string): Promise<CommandDispatch>;\n // Whether the current viewer holds an authority (e.g. 'alarm:write'). Drives whether\n // an action control renders at all.\n can(authority: string): boolean;\n}\n\n// The hub holds NO slot bindings. A widget's `slot` selector is resolved by the renderer\n// (resolveWidgetDatasource) through the settled bindings before it reaches the hub, so one\n// hub serves a board for its whole life: a selection that re-points a slot changes the\n// concrete selector of just the widgets bound to it, and only those re-subscribe. A host\n// builds one hub per (resolver, authorities).\nexport interface DashboardHubConfig {\n resolver: DeviceResolver;\n // The current viewer's authorities (access-token claims). Drives `can()` \u2014 which\n // gates whether a widget's action controls render. Omitted/empty = no write actions\n // (the read-only default); '*' grants all. The server enforces authority regardless.\n authorities?: string[];\n}\n\n// WidgetDataSource is the minimal contract a widget renderer needs from a data\n// source: bind a datasource selector to a sink, get a disposer back. DashboardHub\n// is the live implementation (multiplexed backend telemetry); SyntheticDataSource\n// is the offline preview implementation. The widget layer depends on THIS interface,\n// not the concrete class, so a host can feed either without widgets knowing which.\nexport interface WidgetDataSource {\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void;\n // Bind an alarm widget's scope+filters to a sink; returns a disposer. Delivers\n // whole snapshots (query-then-reconcile), not incremental events. Implemented by\n // both the live hub and the synthetic preview source so alarm widgets render\n // identically from either.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void;\n // Bind a command widget's scope to a sink; returns a disposer. Delivers whole\n // command-history snapshots on a poll (command-delivery has no subscription).\n // Implemented by both the live hub and the synthetic preview source.\n subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;\n // Bind a map widget's selector to a sink; returns a disposer. Delivers whole\n // position snapshots on a poll (device-state has no location subscription), and\n // reports a `location:read` refusal as a snapshot state rather than an error.\n // Implemented by both the live hub and the synthetic preview source.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;\n // Whether a widget's bound entity still exists. Optimistic + async: a widget renders\n // from its stream immediately, and this resolves separately \u2014 only a device selector\n // (or a slot bound to a device) whose token no longer resolves reports false, so the\n // widget shows \"unavailable\" instead of a blank pane. Anchor / unbound-slot / no-\n // datasource report true (their empty state is legitimate, not \"unavailable\"). Fails\n // OPEN (true) on an inconclusive check, so a device-management blip never falsely marks\n // a live device unavailable.\n isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;\n // Whether the platform offers this binding to the viewer at all. False only when the server\n // refuses it as not available to a member limited to part of the tenant (a customer user):\n // an area or asset anchor, whose members are found through the relationship graph such a\n // member cannot read. The widget then says so rather than rendering empty or \"no access\",\n // since nothing that member could be granted would change it. Optional: a source without it\n // (the synthetic preview, a recorded run) supports every binding. Fails open (true) on any\n // other failure, which the widget's own channel reports.\n isDatasourceSupported?(datasource: DatasourceSelector | undefined): Promise<boolean>;\n // The source's idea of \"now\" (epoch ms), for measuring a time span against its samples. Omitted\n // = the wall clock, which is right for every live source. A source that plays back a recorded\n // run implements it, because the run's samples are dated by the run.\n now?(): number;\n // The time buckets of a selector's measurements over a window, each carrying the exact count,\n // minimum, maximum and sum of its readings: what a stat widget folds into the span's aggregates.\n // Optional: a source that cannot produce them omits it, and the widget then says so instead of\n // showing a number. Rejects when the read fails.\n bucketMeasurements?(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]>;\n // A fleet widget's roster and live feed: the members the selector resolves to (delivered once),\n // then every member's samples, each tagged with its device. ONE subscription per widget, however\n // many members; the hub shares each member's upstream stream with every other widget on it.\n // Optional: a source without it leaves a fleet widget with an explained \"not supported\" state.\n subscribeMembers?(datasource: DatasourceSelector, sink: MemberSink): () => void;\n // Each device's seed, made by the widget's plan from the reads it asks for, delivered per device as\n // it settles (`null` when that device's plan failed; `failure` says why). Every read is bounded\n // hub-wide (MEMBER_SEED_CONCURRENCY) and shared with any identical one in flight. `cancelled` is\n // checked before each read starts. Optional like the above.\n readMemberSeeds?(\n deviceTokens: readonly string[],\n plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void,\n cancelled?: () => boolean,\n ): Promise<void>;\n // The measurement names a device TYPE's active profile version declares: what separates \"does not\n // report this\" (the profile never declares it) from \"not reporting\" (it does, and the device is\n // silent). `null` when it cannot say -- no published profile, no way to look, or the lookup\n // failed -- and an unknown declaration is read as declared, never as absent. Optional like the above.\n declaredMetrics?(deviceType: string): Promise<readonly string[] | null>;\n // The same, for ONE device: what its device type's active profile declares. It backs the explained\n // empty state of a single-device widget (\"this device does not report that\"). `null` when it cannot\n // say, with the same rule: unknown is read as declared. Optional like the above.\n declaredMetricsForDevice?(deviceToken: string): Promise<readonly string[] | null>;\n // A presence widget's roster and connectivity: the members the selector resolves to (delivered\n // once), then a whole snapshot of their device-state on every poll (online or offline, last active).\n // Optional: a source with no device-state to read (a recorded run) omits it, and the widget then\n // says so rather than draw a status it cannot know.\n subscribePresence?(datasource: DatasourceSelector, sink: PresenceSink): () => void;\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 // Live presence-subscription disposers (same rationale: a poll per subscription).\n private readonly presenceDisposers = new Set<() => void>();\n\n // Which of this hub's sockets are down, folded into one state a header can show.\n private readonly connection = new ConnectionTracker();\n // Streams evicted by an upstream error that still have subscribers: out of `streams`, but\n // still holding their connection handle (see ensureStream), so disposeAll() can reach them.\n private readonly deadStreams = new Set<DeviceStream>();\n\n constructor(config: DashboardHubConfig) {\n this.resolver = config.resolver;\n this.authorities = new Set(config.authorities ?? []);\n }\n\n // The hub's live-connection state: `live`, `reconnecting` (a socket dropped and is being\n // retried) or `offline` (down past the point a retry is a blip, or the browser is offline).\n // A hub with nothing subscribed has nothing to lose and reads `live`. Pair with\n // onConnectionState (a subscribe/getSnapshot pair, so it drops into useSyncExternalStore).\n get connectionState(): HubConnectionState {\n return this.connection.state;\n }\n\n onConnectionState(listener: () => void): () => void {\n return this.connection.subscribe(listener);\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 // subscribeMembers: the roster, then the live samples of every member. Resolution is async, so\n // the disposer is returned at once and cancels a still-pending resolution (a widget torn down\n // before its roster arrives never attaches a leaked subscriber). Each member rides the same\n // ref-counted device stream every other widget on it uses.\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n let disposed = false;\n const detachers: Array<() => void> = [];\n this.resolveRoster(datasource)\n .then(({ roster, names }) => {\n if (disposed) return;\n sink.roster(roster);\n for (const member of roster.members) {\n detachers.push(\n this.attach(member.token, names, {\n next: (sample) => sink.next(sample),\n error: (err) => sink.error?.(err),\n reconnected: () => sink.reconnected?.(),\n }),\n );\n }\n })\n .catch((err) => {\n if (disposed) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n });\n return () => {\n disposed = true;\n for (const detach of detachers.splice(0)) detach();\n };\n }\n\n // One resolution of a selector into a roster. An anchor goes through the resolver's member read\n // when it has one (names and the true size); everything else is the tokens resolveDevices yields.\n private async resolveRoster(datasource: DatasourceSelector): Promise<{ roster: MemberRoster; names: Set<string> }> {\n const names = new Set(datasource.measurements);\n if (datasource.kind === 'anchor' && this.resolver.anchorMembers) {\n const { members, total } = await this.resolver.anchorMembers(datasource.anchor);\n return { roster: { members, total, unbound: false }, names };\n }\n const groups = await this.resolveDevices(datasource);\n const members = groups.map((g) => ({ token: g.deviceToken, name: null }));\n return { roster: { members, total: members.length, unbound: datasource.kind === 'unbound' }, names };\n }\n\n // One read per distinct device type, whoever asks and however many devices have it. A settled\n // answer (names, or null for \"no published profile\") is kept for the session; a failed read is\n // not, so a later ask tries again, and that ask is answered null meanwhile.\n private readonly declared = new Map<string, Promise<readonly string[] | null>>();\n\n declaredMetrics(deviceType: string): Promise<readonly string[] | null> {\n const read = this.resolver.declaredMetrics;\n if (!read) return Promise.resolve(null);\n let pending = this.declared.get(deviceType);\n if (!pending) {\n pending = read.call(this.resolver, deviceType).catch(() => {\n this.declared.delete(deviceType);\n return null;\n });\n this.declared.set(deviceType, pending);\n }\n return pending;\n }\n\n async declaredMetricsForDevice(deviceToken: string): Promise<readonly string[] | null> {\n const typeOf = this.resolver.deviceType;\n if (!typeOf) return null;\n try {\n const type = await typeOf.call(this.resolver, deviceToken);\n return type ? await this.declaredMetrics(type) : null;\n } catch {\n return null;\n }\n }\n\n // The hub-wide cap on in-flight member seed reads, and the reads currently in flight (so a second\n // widget asking the same question of the same device shares one request).\n private readonly seedLimiter = createLimiter(MEMBER_SEED_CONCURRENCY);\n private readonly seedReads = new Map<string, Promise<MeasurementBucket[]>>();\n\n async readMemberSeeds(\n deviceTokens: readonly string[],\n plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void,\n cancelled: () => boolean = () => false,\n ): Promise<void> {\n await Promise.all(\n deviceTokens.map(async (token) => {\n let seed: MemberSeed;\n try {\n seed = await plan.seed(token, (criteria) => this.memberBuckets(token, criteria, cancelled));\n } catch (err) {\n if (cancelled() || err === SEED_CANCELLED) return;\n onSeed(token, null, isForbiddenError(err) ? 'forbidden' : 'failed');\n return;\n }\n if (!cancelled()) onSeed(token, seed);\n }),\n );\n }\n\n private async memberBuckets(token: string, criteria: MemberBucketCriteria, cancelled: () => boolean): Promise<MeasurementBucket[]> {\n const key = JSON.stringify([token, criteria]);\n for (;;) {\n let read = this.seedReads.get(key);\n if (!read) {\n // The first asker's `cancelled` decides whether the read starts.\n read = this.seedLimiter(\n () => gql(EVENT_AREA, BUCKETED_MEASUREMENTS, { criteria: { deviceToken: token, ...criteria } }).then((r) => r.bucketedMeasurements),\n cancelled,\n );\n this.seedReads.set(key, read);\n const forget = () => this.seedReads.delete(key);\n read.then(forget, forget);\n }\n try {\n return await read;\n } catch (err) {\n // A read this asker shared with one that has since gone away was dropped before it started;\n // this asker is still here, so it asks again (now as the first asker).\n if (err === SEED_CANCELLED && !cancelled()) continue;\n throw err;\n }\n }\n }\n\n // bucketMeasurements reads the buckets of every (device, measurement) pair over a window. Widgets\n // that ask the same question while a read is in flight share it: stat cards on one measurement\n // and span read on the same minute boundaries, so a board of them costs one read per question.\n private readonly bucketReads = new Map<string, Promise<MeasurementBucket[]>>();\n\n bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const key = JSON.stringify([datasource, window]);\n let read = this.bucketReads.get(key);\n if (!read) {\n read = this.readBuckets(datasource, window);\n this.bucketReads.set(key, read);\n const forget = () => this.bucketReads.delete(key);\n read.then(forget, forget);\n }\n return read;\n }\n\n private async readBuckets(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const groups = await this.resolveDevices(datasource);\n const reads = groups.flatMap((g) =>\n (g.names.size ? [...g.names] : [undefined]).map((name) =>\n gql(EVENT_AREA, BUCKETED_MEASUREMENTS, { criteria: { deviceToken: g.deviceToken, name, ...window } }),\n ),\n );\n return (await Promise.all(reads)).flatMap((r) => r.bucketedMeasurements);\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 = (scope: AlarmScope): void => {\n const gen = ++generation;\n this.queryAlarms(subscription, scope)\n .then((snapshot) => {\n if (!disposed && gen === generation) sink.next(snapshot);\n })\n .catch((err) => {\n if (!disposed && gen === generation) sink.error?.(err);\n });\n };\n\n this.resolveAlarmScope(subscription.datasource)\n .then((scope) => {\n if (disposed) return;\n\n // A scoped widget that resolves to no device (an unbound slot, an empty anchor)\n // shows an empty state \u2014 NOT tenant-wide. Only a widget with no datasource at\n // all is tenant-wide. Nothing to stream/poll here. Scope is resolved once (like\n // the measurement channel): a slot rebind arrives as a new concrete selector, which\n // re-subscribes this widget, but organic anchor-membership change isn't picked up\n // until the widget re-subscribes \u2014 a deferred enhancement shared with the\n // measurement channel.\n if (!scope.tenantWide && !scope.anchor && 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), 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 handle = this.connection.register();\n const adapter: SubscriptionSink<AlarmStreamResult> = {\n next: () => trigger(),\n connected: (wasRetry) => {\n handle.connected();\n if (wasRetry) reconcile(scope);\n },\n closed: () => handle.closed(),\n };\n const closeSocket = subscribe(DEVICE_AREA, ALARM_STREAM, { anchor: scope.anchor }, adapter);\n unsubscribe = () => {\n handle.release();\n closeSocket();\n };\n poll = setInterval(() => reconcile(scope), 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);\n this.alarmReconcilers.add(reconciler);\n reconcile(scope); // initial load\n })\n .catch((err) => {\n if (!disposed) sink.error?.(err);\n });\n\n return dispose;\n }\n\n // resolveAlarmScope turns an alarm widget's scope selector into the originator device\n // tokens to filter on, or tenant-wide when it carries no datasource. Reuses the same\n // device/anchor/unbound resolution the measurement channel does.\n //\n // \uD83D\uDD34 Only an ABSENT datasource is tenant-wide. `{kind:'unbound'}` is a datasource, and\n // it resolves to zero devices like any other empty scope.\n private async resolveAlarmScope(datasource: DatasourceSelector | undefined): Promise<AlarmScope> {\n if (!datasource) return { tenantWide: true, tokens: [] };\n // A customer, area or asset the alarms are stamped with is ONE anchored read, not a read per\n // member device. Any other anchor (a group) still expands to its devices.\n if (datasource.kind === 'anchor' && datasource.anchor.relationship === 'assigned' && STAMPED.has(datasource.anchor.targetType)) {\n return { tenantWide: false, tokens: [], anchor: { type: datasource.anchor.targetType, token: datasource.anchor.targetToken } };\n }\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 { tenantWide, tokens, anchor }: AlarmScope,\n ): Promise<AlarmSnapshot> {\n const filters = {\n state: sub.state ?? null,\n severity: sub.severity ?? null,\n acknowledged: sub.acknowledged ?? null,\n } satisfies Partial<AlarmSearchCriteriaInput>;\n const originators: Array<{ originatorType: string | null; originatorToken: string | null; anchor?: AlarmAnchorInput }> =\n tenantWide || anchor\n ? [{ originatorType: null, originatorToken: null, anchor }]\n : tokens.map((token) => ({ originatorType: 'device', originatorToken: token }));\n\n if (sub.countBySeverity) {\n // One grouped read per originator query (one tenant-wide), summed by severity. Per-device\n // reads cannot double count: an alarm has one originator.\n const pages = await Promise.all(\n originators.map((o) =>\n gql(DEVICE_AREA, ALARM_SEVERITY_COUNTS, { criteria: { ...filters, ...o }, deviceType: sub.deviceType ?? null }),\n ),\n );\n const merged = new Map<string, number>();\n for (const page of pages) {\n for (const c of page.alarmCountsBySeverity) merged.set(c.severity, (merged.get(c.severity) ?? 0) + c.count);\n }\n const severityCounts = [...merged].map(([severity, count]) => ({ severity, count }));\n return { alarms: [], total: severityCounts.reduce((n, c) => n + c.count, 0), severityCounts };\n }\n\n if (sub.countByDeviceType) {\n // One grouped read per originator query (one tenant-wide), summed by (type, severity).\n const pages = await Promise.all(\n originators.map((o) => gql(DEVICE_AREA, ALARM_TYPE_COUNTS, { criteria: { ...filters, ...o } })),\n );\n const merged = new Map<string, AlarmTypeCount>();\n for (const page of pages) {\n for (const c of page.alarmCountsByDeviceType) {\n const key = `${c.deviceType.token}\\u0000${c.severity}`;\n const prior = merged.get(key);\n merged.set(key, prior ? { ...prior, count: prior.count + c.count } : { ...c, deviceType: { ...c.deviceType } });\n }\n }\n const typeCounts = [...merged.values()];\n return { alarms: [], total: typeCounts.reduce((n, c) => n + c.count, 0), typeCounts };\n }\n\n // The type filter is device-management's, so a page is the subscription's either way.\n const base = {\n pageNumber: 1, // the alarms query paginates 1-based\n pageSize: sub.pageSize,\n ...filters,\n deviceType: sub.deviceType ?? null,\n ...windowCriteria(sub.window, Date.now()),\n } satisfies Partial<AlarmSearchCriteriaInput>;\n const pages = await Promise.all(\n originators.map((o) => gql(DEVICE_AREA, ALARMS_QUERY, { criteria: { ...base, ...o } })),\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 let alarms = [...byToken.values()]\n .sort((a, b) => (b.raisedTime ?? '').localeCompare(a.raisedTime ?? ''))\n .slice(0, sub.pageSize);\n if (sub.enrichDeviceType === true) alarms = await this.withDeviceTypes(alarms);\n return { alarms, total };\n }\n\n // Each device's type, for the lifetime of the hub: a device's type is fixed for a viewing session,\n // and the same originators come back every poll. ONE devicesByToken read per chunk of uncached\n // tokens (the server takes 1000), never one per alarm. A failed read leaves those rows with no\n // `deviceType` (unknown, so a grouping widget lists them under All only) instead of failing the\n // whole alarm table, and is retried on the next reconcile because a failure is not cached.\n private readonly alarmDeviceTypes = new Map<string, AlarmDeviceType>();\n\n private async withDeviceTypes(rows: AlarmRow[]): Promise<AlarmRow[]> {\n const missing = [\n ...new Set(\n rows\n .filter((r) => r.originatorType === 'device' && r.originatorToken && !this.alarmDeviceTypes.has(r.originatorToken))\n .map((r) => r.originatorToken as string),\n ),\n ];\n for (let i = 0; i < missing.length; i += ALARM_DEVICE_TYPES_CHUNK) {\n try {\n const r = await gql(DEVICE_AREA, ALARM_DEVICE_TYPES, { tokens: missing.slice(i, i + ALARM_DEVICE_TYPES_CHUNK) });\n for (const d of r.devicesByToken) this.alarmDeviceTypes.set(d.token, d.deviceType);\n } catch {\n // rows stay unresolved; see above\n }\n }\n return rows.map((r) => {\n if (r.originatorType !== 'device' || !r.originatorToken) return { ...r, deviceType: null };\n const deviceType = this.alarmDeviceTypes.get(r.originatorToken);\n return deviceType ? { ...r, deviceType } : r;\n });\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, ...windowCriteria(sub.window, Date.now()) },\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 // subscribePresence: the roster, then the roster's device-state on an interval. Poll-only (see\n // PRESENCE_POLL_MS). A refusal (`state:read` is outside some members' grants) becomes `forbidden`,\n // and every other failure stays an error, as for the location channel. A slow read that lands after\n // a newer one is dropped, so an old answer never overwrites a fresher one.\n subscribePresence(datasource: DatasourceSelector, sink: PresenceSink): () => void {\n let disposed = false;\n let poll: ReturnType<typeof setInterval> | undefined;\n let shown: (() => void) | undefined;\n let generation = 0;\n const dispose = (): void => {\n disposed = true;\n if (poll) clearInterval(poll);\n if (shown) document.removeEventListener('visibilitychange', shown);\n this.presenceDisposers.delete(dispose);\n };\n this.presenceDisposers.add(dispose);\n\n const reconcile = (tokens: string[]): void => {\n const gen = ++generation;\n this.queryPresence(tokens).then(\n (states) => {\n if (!disposed && gen === generation) sink.next(states);\n },\n (err) => {\n if (disposed || gen !== generation) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n },\n );\n };\n\n this.resolveRoster(datasource).then(\n ({ roster }) => {\n if (disposed) return;\n sink.roster(roster);\n const tokens = roster.members.map((m) => m.token);\n if (tokens.length === 0) {\n sink.next([]);\n return;\n }\n // A hidden tab reads nothing: nobody is looking, and a board left open in a background tab\n // would otherwise poll device-state for as long as the browser lives. Shown again, it reads at\n // once rather than drawing the snapshot it last had until the next tick.\n const hidden = (): boolean => typeof document !== 'undefined' && document.hidden;\n poll = setInterval(() => {\n if (!hidden()) reconcile(tokens);\n }, PRESENCE_POLL_MS);\n if (typeof document !== 'undefined') {\n shown = () => {\n if (!hidden()) reconcile(tokens);\n };\n document.addEventListener('visibilitychange', shown);\n }\n reconcile(tokens);\n },\n (err) => {\n if (disposed) return;\n if (isForbiddenError(err)) sink.forbidden?.();\n else sink.error?.(err);\n },\n );\n return dispose;\n }\n\n private async queryPresence(tokens: string[]): Promise<DevicePresence[]> {\n const chunks: string[][] = [];\n for (let i = 0; i < tokens.length; i += PRESENCE_CHUNK) chunks.push(tokens.slice(i, i + PRESENCE_CHUNK));\n const reads = await Promise.all(chunks.map((deviceTokens) => gql(STATE_AREA, DEVICE_PRESENCE_QUERY, { deviceTokens })));\n return reads.flatMap((r) => r.deviceStatesByDeviceToken);\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 stream of this.deadStreams) stream.unsubscribe();\n this.deadStreams.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 for (const dispose of [...this.presenceDisposers]) dispose();\n this.presenceDisposers.clear();\n }\n\n // The number of distinct upstream device streams currently open (observability\n // + test hook \u2014 proves multiplexing collapses shared devices to one stream).\n get openStreamCount(): number {\n return this.streams.size;\n }\n\n // isDatasourceAvailable reports whether a widget's bound device still exists. Only a\n // device selector is validated (a slot bound to a device arrives as one) \u2014 an anchor, an\n // unbound slot, or no datasource has a legitimate empty state and is always \"available\".\n // Fails open: an existence-check outage returns true (never falsely mark a live device\n // gone).\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n const deviceToken = this.availabilityToken(datasource);\n if (deviceToken === undefined) return true;\n try {\n return await this.resolver.deviceExists(deviceToken);\n } catch {\n return true;\n }\n }\n\n // isDatasourceSupported reports whether the viewer is offered the binding at all: false only\n // when resolving an anchor is refused as SCOPE_UNSUPPORTED (an anchor the platform still\n // declines for a customer user; areas and assets are answered through anchorDevices). The resolution is the same\n // memoized one the widget's channel makes, so a supported anchor costs no second read. Every other answer, including a failure, is true.\n async isDatasourceSupported(datasource: DatasourceSelector | undefined): Promise<boolean> {\n if (datasource?.kind !== 'anchor') return true;\n try {\n await this.resolver.devicesForAnchor(datasource.anchor);\n return true;\n } catch (err) {\n return !isScopeUnsupportedError(err);\n }\n }\n\n // availabilityToken returns the single device token whose existence gates a widget's\n // availability, or undefined when there is nothing device-specific to validate (an\n // anchor's membership is self-validating; an unbound slot is a placeholder; a reserved\n // kind isn't resolved yet; an unresolved slot fails on its data channel instead).\n private availabilityToken(datasource: DatasourceSelector | undefined): string | undefined {\n if (!datasource) return undefined;\n // An empty token (a half-authored or hand-edited definition) has nothing to\n // validate \u2014 treat it like an unbound slot (available/empty), not a device that\n // \"no longer exists\", and skip the guaranteed-empty query.\n if (datasource.kind === 'device') return datasource.deviceToken || undefined;\n return undefined;\n }\n\n // resolveDevices turns a selector into the devices to stream, each with the\n // measurement names the widget wants (empty = all). Reserved selector kinds are\n // rejected here, mirroring the backend (Phase 1 ships device + anchor).\n private async resolveDevices(\n datasource: DatasourceSelector,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n switch (datasource.kind) {\n case 'device':\n return this.resolveBinding(\n { kind: 'device', deviceToken: datasource.deviceToken },\n new Set(datasource.measurements),\n );\n case 'anchor':\n return this.resolveBinding(\n { kind: 'anchor', anchor: datasource.anchor },\n new Set(datasource.measurements),\n );\n case 'unbound':\n // A slot with no binding, as the renderer resolved it: a valid placeholder (a\n // template the host hasn't bound, a selection not yet made), not an error.\n // Zero devices \u2014 a silent empty state, like an anchor with no members.\n return [];\n case 'slot':\n // \uD83D\uDD34 FAIL LOUDLY. The hub keeps no bindings, so a slot here was never resolved:\n // the host handed a raw definition widget to the data source instead of going\n // through the renderer (or resolveWidgetDatasource). Answering \"zero devices\"\n // would make that bug look exactly like a slot that is legitimately unbound.\n throw new Error(\n `dashboard slot '${datasource.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(\n `dashboard selector kind '${datasource.kind}' is not supported yet`,\n );\n }\n }\n\n // resolveBinding turns a concrete entity binding (device or anchor) into the\n // device streams to open, each carrying the given measurement names. Shared by the\n // device/anchor selectors and by slot resolution (whose binding is either kind).\n // A device binding streams its token directly (measurementStream is keyed by token,\n // per ADR-044); an anchor expands to its member device tokens.\n private async resolveBinding(\n binding: SlotBinding,\n names: Set<string>,\n ): Promise<Array<{ deviceToken: string; names: Set<string> }>> {\n if (binding.kind === 'device') {\n return [{ deviceToken: binding.deviceToken, names }];\n }\n const tokens = await this.resolver.devicesForAnchor(binding.anchor);\n return tokens.map((deviceToken) => ({ deviceToken, names }));\n }\n\n // attach registers a subscriber on a device's stream (opening the upstream on\n // the first subscriber) and returns a detacher that drops it and closes the\n // upstream once the last subscriber leaves.\n private attach(deviceToken: string, names: Set<string>, sink: WidgetStreamSink): () => void {\n const stream = this.ensureStream(deviceToken);\n const subscriber: Subscriber = { names, sink };\n stream.subscribers.add(subscriber);\n\n return () => {\n if (!stream.subscribers.delete(subscriber)) return;\n // Only tear down and forget the stream if it is STILL the registered stream\n // for this device \u2014 an upstream error may have evicted and replaced it, and a\n // lingering old subscriber's detach must not delete the replacement.\n if (stream.subscribers.size === 0) {\n // An evicted stream's operation is already closed; this releases its connection\n // handle, which it kept so a dead socket reads as down while anything still shows it.\n stream.unsubscribe();\n this.deadStreams.delete(stream);\n if (this.streams.get(deviceToken) === stream) 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 handle = this.connection.register();\n let closeSocket: (() => void) | undefined;\n let operationOpen = true;\n // Consecutive server-side ends with no reading in between; spaces the re-opens below.\n let ended = 0;\n // This operation's socket is up, as far as its last connected/closed said.\n let up = true;\n let reopen: ReturnType<typeof setTimeout> | undefined;\n const closeOperation = (): void => {\n if (!operationOpen) return;\n operationOpen = false;\n clearTimeout(reopen);\n closeSocket?.();\n };\n const open = (): void => {\n closeSocket = subscribe(EVENT_AREA, MEASUREMENT_STREAM, variables, adapter);\n };\n // The stream resumed after an interruption: whatever was sent meanwhile was never delivered,\n // and each subscriber is told so it can read the missed history back. Sent to the stream's own\n // subscribers. Never called once the operation is closed: closing it drops its connection\n // listener and clears a pending re-open.\n const resumed = (): void => {\n stream.subscribers.forEach((s) => s.sink.reconnected?.());\n };\n const adapter: SubscriptionSink<MeasurementStreamResult> = {\n next: (data) => {\n ended = 0;\n this.fanout(deviceToken, data.measurementStream);\n },\n connected: (wasRetry) => {\n up = true;\n handle.connected();\n // graphql-ws re-sends this operation on the new socket; a first connect is no gap.\n if (wasRetry) resumed();\n },\n closed: () => {\n up = false;\n handle.closed();\n },\n // \uD83D\uDD34 The server ended this operation on a socket that is still up: its live source\n // closed, or could not be opened (a resolver error reaches the client as an error-only\n // `next`, which carries no data, and then this). Left alone, the stream would sit here\n // receiving nothing while the board read `live` -- a widget frozen until someone reloads,\n // one stream at a time. Open it again, after a doubling wait capped at 30 s, for as long\n // as anything shows it. Two other completes are not the server's and are never re-opened:\n // a widget's own unsubscribe (closeOperation has marked it), and the client being disposed\n // on sign-out, which closes the socket first (re-opening would bring back the socket the\n // sign-out exists to drop).\n complete: () => {\n if (!operationOpen || !up) return;\n closeSocket?.();\n reopen = setTimeout(() => {\n open();\n // Opened on a socket that is already up, so no connection_ack follows to say so.\n resumed();\n }, Math.min(1000 * 2 ** ended++, 30_000));\n },\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 // \uD83D\uDD34 Close the operation but KEEP the connection handle until the last subscriber\n // detaches. A dropped socket is retried for as long as the stream is wanted (the\n // client never gives up on one), so what lands here is final: a close that says\n // the request is wrong (a refused token among them) or the server ending this\n // operation. A fatal close errors every operation at once, so releasing here would\n // empty the tracker exactly when the socket is down for good, and a board whose\n // every stream had died would read `live`. A handle\n // whose socket was up when the operation failed (a server-side error) counts as up.\n closeOperation();\n if (stream.subscribers.size > 0) this.deadStreams.add(stream);\n else handle.release();\n for (const subscriber of stream.subscribers) subscriber.sink.error?.(err);\n },\n };\n const variables: MeasurementStreamVariables = { deviceToken, name: null };\n open();\n if (!operationOpen) closeSocket?.(); // it failed while subscribing\n stream.unsubscribe = () => {\n handle.release();\n closeOperation();\n };\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 originatorToken?: string | null;\n state?: string | null;\n severity?: string | null;\n acknowledged?: boolean | null;\n alarmKey?: string | null;\n // Only alarms raised on a device of this type (its token).\n deviceType?: string | null;\n // Only alarms whose span overlaps this range (RFC 3339, each optional).\n startTime?: string | null;\n endTime?: string | null;\n // Only alarms stamped with this customer, area or asset.\n anchor?: AlarmAnchorInput | null;\n}\n\nexport interface AlarmAnchorInput {\n type: string;\n token: string;\n}\n\nexport interface AlarmsQueryResult {\n alarms: {\n results: AlarmRow[];\n pagination: { totalRecords: number };\n };\n}\n\nexport interface AlarmsQueryVariables {\n criteria: AlarmSearchCriteriaInput;\n}\n\nexport const ALARMS_QUERY = `\n query DashboardAlarms($criteria: AlarmSearchCriteria!) {\n alarms(criteria: $criteria) {\n results {\n token\n originatorType\n originatorToken\n alarmKey\n metricKey\n state\n acknowledged\n severity\n raisedTime\n clearedTime\n acknowledgedTime\n acknowledgedBy\n lastValue\n }\n pagination {\n totalRecords\n }\n }\n }\n` as unknown as TypedDocument<AlarmsQueryResult, AlarmsQueryVariables>;\n\n// \u2500\u2500 Device types of a set of originators (one batched read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The alarm row carries only its originator's token. A widget that groups alarms by device type\n// resolves them with ONE devicesByToken read (up to 1000 tokens), never one query per alarm.\n\nexport interface AlarmDeviceTypesResult {\n devicesByToken: Array<{ token: string; deviceType: { token: string; name: string | null } }>;\n}\n\nexport interface AlarmDeviceTypesVariables {\n tokens: string[];\n}\n\nexport const ALARM_DEVICE_TYPES = `\n query DashboardAlarmDeviceTypes($tokens: [String!]!) {\n devicesByToken(tokens: $tokens) {\n token\n deviceType {\n token\n name\n }\n }\n }\n` as unknown as TypedDocument<AlarmDeviceTypesResult, AlarmDeviceTypesVariables>;\n\n// \u2500\u2500 Alarm counts per device type (one grouped read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// The tab strip of an alarm table grouped by type: how many alarms match, per type and severity,\n// counted by device-management, so the badges are exact however many alarms there are.\n\nexport type AlarmCountCriteriaInput = Omit<AlarmSearchCriteriaInput, 'pageNumber' | 'pageSize' | 'deviceType' | 'startTime' | 'endTime'>;\n\nexport interface AlarmTypeCountsResult {\n alarmCountsByDeviceType: Array<{ deviceType: { token: string; name: string | null }; severity: string; count: number }>;\n}\n\nexport interface AlarmTypeCountsVariables {\n criteria: AlarmCountCriteriaInput;\n}\n\nexport const ALARM_TYPE_COUNTS = `\n query DashboardAlarmTypeCounts($criteria: AlarmCountCriteria!) {\n alarmCountsByDeviceType(criteria: $criteria) {\n deviceType {\n token\n name\n }\n severity\n count\n }\n }\n` as unknown as TypedDocument<AlarmTypeCountsResult, AlarmTypeCountsVariables>;\n\n// \u2500\u2500 Alarm counts per severity (one grouped read) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A severity breakdown. With no deviceType it counts every alarm the search would return, whatever\n// its originator, so the counts sum to the alarm search's total; with one, only that type's devices.\n\nexport interface AlarmSeverityCountsResult {\n alarmCountsBySeverity: Array<{ severity: string; count: number }>;\n}\n\nexport interface AlarmSeverityCountsVariables {\n criteria: AlarmCountCriteriaInput;\n deviceType: string | null;\n}\n\nexport const ALARM_SEVERITY_COUNTS = `\n query DashboardAlarmSeverityCounts($criteria: AlarmCountCriteria!, $deviceType: String) {\n alarmCountsBySeverity(criteria: $criteria, deviceType: $deviceType) {\n severity\n count\n }\n }\n` as unknown as TypedDocument<AlarmSeverityCountsResult, AlarmSeverityCountsVariables>;\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 originatorToken?: string | null;\n state?: string | null;\n severity?: string | null;\n alarmKey?: string | null;\n anchor?: AlarmAnchorInput | null;\n}\n\nexport const ALARM_STREAM = `\n subscription DashboardAlarmStream(\n $originatorType: String\n $originatorToken: String\n $state: String\n $severity: String\n $alarmKey: String\n $anchor: AlarmAnchor\n ) {\n alarmStream(\n originatorType: $originatorType\n originatorToken: $originatorToken\n state: $state\n severity: $severity\n alarmKey: $alarmKey\n anchor: $anchor\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 // Only commands sent (queued, when never sent) inside this range (RFC 3339, each optional).\n startTime?: string | null;\n endTime?: 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 device-state document the hub's presence read drives, hand-authored (packages carry no\n// graphql-codegen; see measurement-doc).\n//\n// `deviceStatesByDeviceToken` is the BATCH read (up to 1000 tokens): one round trip covers a whole\n// board's worth of devices. A device with no state yet is ABSENT from the result, not present with\n// nulls -- the service's contract, which the channel keeps. Requires state:read.\n\nimport type { TypedDocument } from '@devicechain/client';\n\nimport type { DevicePresence } from '../types';\n\nexport interface DeviceStatesResult {\n deviceStatesByDeviceToken: DevicePresence[];\n}\n\nexport interface DeviceStatesVariables {\n deviceTokens: string[];\n}\n\nexport const DEVICE_PRESENCE_QUERY = `\n query DashboardDevicePresence($deviceTokens: [String!]!) {\n deviceStatesByDeviceToken(deviceTokens: $deviceTokens) {\n deviceToken\n active\n lastActivityTime\n inactivityTimeout\n }\n }\n` as unknown as TypedDocument<DeviceStatesResult, DeviceStatesVariables>;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What a fleet widget (one row or one bar per member of an anchor) reads from a data source.\n//\n// The measurement channel keys its latest values by measurement NAME alone, so an anchor of ten\n// thermostats collapses into one \"temperature\". A fleet widget needs the device kept: a roster of\n// the anchor's members, each member's own live samples (every sample already carries its device\n// token), and one seed read per member for what it last reported before the board was opened.\n//\n// There is no batched \"latest value of these devices\" read in the platform: the event store\n// aggregates one device at a time (bucketedMeasurements), and device-state holds presence and\n// position but no measurement values. The seed is therefore read per member, and what keeps that\n// from being a query storm is that (a) it is the only per-member read -- the roster is one\n// relationship page plus one batched name lookup, whatever the size of the fleet -- (b) a hub-wide\n// limiter bounds how many are in flight, (c) identical reads share one request, and (d) the widget's\n// plan asks for as little as answers it (the fleet widgets read the last few minutes first; see\n// fleet-seed.ts in the widgets package).\n\nimport type { MeasurementBucket } from './queries';\nimport type { DevicePresence, MeasurementSample } from './types';\n\n// One device of a roster. `name` is null when the source does not know it. `deviceType` is the\n// token of its device type, when the source knows it: it is what the device's declared metrics are\n// looked up by, once per type.\nexport interface MemberRef {\n token: string;\n name: string | null;\n deviceType?: string | null;\n}\n\n// The members a selector resolves to. `total` is the size of the anchor, which can exceed\n// `members.length`: the resolver reads one bounded page, and a fleet beyond it must be reported as\n// truncated rather than shown as if it were whole. `unbound` marks a slot with no binding (zero\n// members, but a placeholder, not an empty fleet).\nexport interface MemberRoster {\n members: MemberRef[];\n total: number;\n unbound: boolean;\n}\n\n// Where a fleet widget's data lands. `roster` arrives once, before any `next`.\nexport interface MemberSink {\n roster(roster: MemberRoster): void;\n next(sample: MeasurementSample): void;\n // The viewer may not read this data (a refusal, not a failure): the widget says so in words\n // rather than showing the generic error pane every ordinary member would see for a working platform.\n forbidden?(): void;\n error?(err: unknown): void;\n // A member's live feed resumed after an interruption (a dropped socket, or the server ending\n // the stream): readings sent meanwhile were never delivered, so a widget that seeds from history\n // reads it again. Called once per member stream that resumed. Optional on both sides: a source\n // that never drops a feed does not call it.\n reconnected?(): void;\n}\n\n// Where a presence widget's data lands: the roster once, then a whole snapshot of the roster's\n// connectivity on every poll. A token the snapshot does not carry has no device-state row (the\n// service omits those), which is a settled answer -- never connected -- not a failure.\nexport interface PresenceSink {\n roster(roster: MemberRoster): void;\n next(states: readonly DevicePresence[]): void;\n // The viewer may not read device state (a refusal, not a failure).\n forbidden?(): void;\n error?(err: unknown): void;\n}\n\n// Why one member's seed read did not come back.\nexport type SeedFailure = 'forbidden' | 'failed';\n\n// One bucketed read of ONE member, as a seed plan asks for it: the hub fills in the device. `name`\n// narrows the read to one measurement (the store takes one name or none).\nexport interface MemberBucketCriteria {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n name?: string;\n}\n\nexport type MemberBucketRead = (criteria: MemberBucketCriteria) => Promise<MeasurementBucket[]>;\n\n// What to read for one member and how to make its seed of the answer. The WIDGET decides that (which\n// span, which measurements, read in what order); the hub supplies `read` -- bounded hub-wide and\n// shared, so two widgets asking one device the same question cost one request -- and settles the\n// result. A plan that rejects settles that member as failed (or forbidden).\nexport interface MemberSeedPlan {\n seed(deviceToken: string, read: MemberBucketRead): Promise<MemberSeed>;\n}\n\n// What one member last reported inside a window: its newest reading per measurement name, and every\n// name it reported at all (which is how \"does not report this\" is told from \"sent nothing\").\nexport interface MemberSeed {\n latest: Record<string, MeasurementSample>;\n observed: string[];\n // Names it reported in the window whose newest reading the plan chose not to read (a bound on its\n // reads): the device HAS data for them, so they must never read as \"no data\".\n unread?: string[];\n}\n\n// How many seed reads may be in flight at once across the whole hub. Six is what a browser opens\n// to one origin over HTTP/1.1, so a larger number only queues in the browser; a fleet of 500 then\n// takes ~84 rounds rather than 500 simultaneous requests starving every other widget on the board.\nexport const MEMBER_SEED_CONCURRENCY = 6;\n\n// The reason a queued read gave up because its widget went away before it started.\nexport const SEED_CANCELLED = Symbol('member seed cancelled');\n\n// A first-in-first-out limiter: at most `max` tasks run at once, the rest wait their turn. A task\n// whose `cancelled()` is true when its turn comes is dropped without running -- a widget that\n// changed its span or unmounted must not leave hundreds of reads queued behind it.\nexport function createLimiter(max: number) {\n let active = 0;\n const queue: Array<() => void> = [];\n const next = (): void => {\n while (active < max && queue.length > 0) queue.shift()!();\n };\n return function run<T>(task: () => Promise<T>, cancelled: () => boolean = () => false): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const start = (): void => {\n if (cancelled()) {\n reject(SEED_CANCELLED);\n return;\n }\n active += 1;\n task().then(resolve, reject).finally(() => {\n active -= 1;\n next();\n });\n };\n queue.push(start);\n next();\n });\n };\n}\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 { alarmOverlaps, commandInRange } from './event-window';\nimport type { HistoryWindow } from './history';\nimport type { MemberRef, MemberSeed, MemberSeedPlan, MemberSink, PresenceSink } from './members';\nimport type { MeasurementBucket } from './queries';\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 AlarmDeviceType,\n AlarmRow,\n AlarmTypeCount,\n CommandRow,\n DatasourceSelector,\n DevicePresence,\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// Every generator id, for a picker. Ids only: the display name is the host's to localize,\n// and the id is what a definition stores, so it never changes with the language.\nexport const SYNTHETIC_GENERATORS: ReadonlyArray<SyntheticGenerator> = ['sine', 'ramp', 'random-walk'];\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// The most buckets one preview read returns per measurement (the store's own ceiling is far higher).\nconst MAX_PREVIEW_BUCKETS = 500;\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.\n// The device types of the originators above, for a table that groups by type. device-99 has none\n// (unresolved), which is the row a grouping widget keeps under All only.\nconst SYNTHETIC_DEVICE_TYPES: Readonly<Record<string, AlarmDeviceType>> = {\n 'thermostat-01': { token: 'thermostat', name: 'Thermostat' },\n 'sensor-14': { token: 'sensor', name: 'Sensor' },\n 'sensor-03': { token: 'sensor', name: 'Sensor' },\n 'gateway-02': { token: 'gateway', name: 'Gateway' },\n};\n\nfunction withType(token: string | null): Pick<AlarmRow, 'deviceType'> {\n const deviceType = token ? SYNTHETIC_DEVICE_TYPES[token] : undefined;\n return deviceType ? { deviceType } : {};\n}\n\nconst SYNTHETIC_ALARMS: ReadonlyArray<\n Pick<\n AlarmRow,\n 'severity' | 'state' | 'acknowledged' | 'alarmKey' | 'metricKey' | 'lastValue' | 'originatorToken'\n >\n> = [\n { severity: 'CRITICAL', state: 'ACTIVE', acknowledged: false, alarmKey: 'over-temperature', metricKey: 'temperature', lastValue: 87.4, originatorToken: 'thermostat-01' },\n { severity: 'MAJOR', state: 'ACTIVE', acknowledged: true, alarmKey: 'low-battery', metricKey: 'battery', lastValue: 12, originatorToken: 'sensor-14' },\n { severity: 'MINOR', state: 'ACTIVE', acknowledged: false, alarmKey: 'humidity-high', metricKey: 'humidity', lastValue: 78, originatorToken: 'sensor-03' },\n { severity: 'WARNING', state: 'CLEARED', acknowledged: true, alarmKey: 'signal-weak', metricKey: 'rssi', lastValue: -89, originatorToken: 'gateway-02' },\n { severity: 'INDETERMINATE', state: 'ACTIVE', acknowledged: false, alarmKey: 'self-test', metricKey: 'status', lastValue: null, originatorToken: 'device-99' },\n];\n\n// A canonical spread of synthetic commands (one per lifecycle stage) so an author\n// previewing a command-button sees a populated, representative history \u2014 an in-flight\n// command, one withheld for an absent device, one parked after finding nobody home, a\n// completed one, a failure, a cancellation \u2014 before any real command has been issued.\n//\n// `dispatched` says whether this command was ever actually put on the wire, which is\n// what decides sentTime below. It is a per-fixture fact, not something derivable from\n// the status: a CANCELLED command may have been called off while still held (never\n// sent) or after dispatch, and the preview picks the former.\nconst SYNTHETIC_COMMANDS: ReadonlyArray<\n Pick<CommandRow, 'name' | 'status' | 'payload' | 'responsePayload' | 'error'> & { dispatched: boolean }\n> = [\n { name: 'reboot', status: 'SENT', payload: '{\"delaySeconds\":5}', responsePayload: null, error: null, dispatched: true },\n // Early in the list on purpose: an author can cap the widget's row count, and a\n // withheld command is the state a preview most needs to show.\n { name: 'self-test', status: 'HELD', payload: null, responsePayload: null, error: null, dispatched: false },\n // The other way a command ends up waiting on an absent device: this one was published\n // and found nobody there, so it is parked until the device wakes. Included next to HELD\n // because the two look alike in a list and an author laying out a command-button needs\n // to see that they are distinguishable. `dispatched` is false, matching the service: it\n // CLEARS sent_time when it parks a command, because a dispatch that reached nobody sent\n // nothing. That is also why a parked command is still cancellable \u2014 nothing has taken\n // delivery of it.\n { name: 'sync-clock', status: 'PARKED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'set-interval', status: 'SUCCESSFUL', payload: '{\"seconds\":30}', responsePayload: '{\"ok\":true}', error: null, dispatched: true },\n { name: 'calibrate', status: 'QUEUED', payload: null, responsePayload: null, error: null, dispatched: false },\n { name: 'firmware-update', status: 'FAILED', payload: '{\"version\":\"2.1.0\"}', responsePayload: null, error: 'device offline', dispatched: true },\n { name: 'open-valve', status: 'CANCELLED', payload: '{\"percent\":100}', responsePayload: null, error: null, dispatched: false },\n];\n\n// The device a synthetic command-button reports as its target, so the Send control\n// renders (a real widget needs a bound device to issue against).\nconst SYNTHETIC_COMMAND_DEVICE = 'synthetic-device';\n\n// A canonical spread of synthetic positions so an author previewing a map sees markers\n// laid out the way a real fleet would be \u2014 several devices a short distance apart, at a\n// zoom a map actually renders at, rather than one dot in the middle.\n//\n// \uD83D\uDD34 THE OPTIONALS ARE DELIBERATELY MIXED, INCLUDING NULLS AND A GENUINE ZERO. Preview\n// is where an author decides what a marker looks like, so it must show the real range:\n// `sp-loader-03` reports no heading and no speed at all (a receiver that does not supply\n// them), while `sp-dozer-01` reports speed 0 \u2014 parked, which is a reading, not an\n// absence. A preview that filled every optional in would teach an author that a marker\n// always has a heading, and the first real fleet would prove otherwise.\nconst SYNTHETIC_LOCATIONS: ReadonlyArray<\n Pick<LocationSample, 'deviceToken' | 'latitude' | 'longitude' | 'elevation' | 'accuracy' | 'speed' | 'heading'>\n> = [\n { deviceToken: 'sp-dozer-01', latitude: 33.749, longitude: -84.388, elevation: 320.5, accuracy: 4.2, speed: 0, heading: 271.5 },\n { deviceToken: 'sp-excavator-02', latitude: 33.7512, longitude: -84.3858, elevation: 318.1, accuracy: 3.1, speed: 1.4, heading: 88 },\n { deviceToken: 'sp-loader-03', latitude: 33.7468, longitude: -84.3903, elevation: null, accuracy: 9.8, speed: null, heading: null },\n { deviceToken: 'sp-truck-04', latitude: 33.7481, longitude: -84.3841, elevation: 315.9, accuracy: 2.5, speed: 8.3, heading: 12.25 },\n];\n\n// Deterministic small hash of a name \u2192 a stable phase offset, so multiple series on\n// one dashboard are visibly out of phase rather than overlapping identically.\nfunction hashName(name: string): number {\n let h = 0;\n for (let i = 0; i < name.length; i++) h = (Math.imul(h, 31) + name.charCodeAt(i)) | 0;\n return h >>> 0;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return v < min ? min : v > max ? max : v;\n}\n\nexport class SyntheticDataSource implements WidgetDataSource, WidgetActions {\n private readonly generator: SyntheticGenerator;\n private readonly intervalMs: number;\n private readonly backfill: number;\n private readonly min: number;\n private readonly max: number;\n private readonly periodMs: number;\n // Live timers, tracked so disposeAll() can stop every widget's stream at once.\n private readonly timers = new Set<ReturnType<typeof setInterval>>();\n\n constructor(config: SyntheticDataSourceConfig = {}) {\n this.generator = config.generator ?? 'sine';\n // Guard the divisors/counts so a misconfigured host can't produce NaN values\n // (periodMs:0 \u2192 tMs/0) or a zero-delay flood: intervalMs/periodMs floor at 1ms,\n // backfill can't go negative.\n this.intervalMs = Math.max(1, config.intervalMs ?? 1000);\n this.backfill = Math.max(0, config.backfill ?? 60);\n this.min = config.min ?? 0;\n this.max = config.max ?? 100;\n this.periodMs = Math.max(1, config.periodMs ?? 60_000);\n }\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n // Per-name random-walk state, private to this subscription so two widgets don't\n // share (and corrupt) each other's walk.\n const walk = new Map<string, number>();\n let seq = 0;\n\n const emit = (name: string, tMs: number): void => {\n const value = this.valueFor(name, tMs, walk);\n const s: MeasurementSample = {\n id: `syn-${seq++}`,\n deviceToken: 'synthetic',\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value,\n classifier: null,\n };\n sink.next(s);\n };\n\n // Backfill oldest \u2192 newest so the widget window is chronological (and the\n // random-walk advances forward through the backfilled points).\n const now = Date.now();\n for (let i = this.backfill - 1; i >= 0; i--) {\n const tMs = now - i * this.intervalMs;\n for (const name of names) emit(name, tMs);\n }\n\n const timer = setInterval(() => {\n const tMs = Date.now();\n for (const name of names) emit(name, tMs);\n }, this.intervalMs);\n this.timers.add(timer);\n\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // subscribeAlarms emits a synthetic alarm snapshot for preview. The canonical set is\n // filtered by the subscription (state/severity/ack) so the preview reflects what the\n // widget is configured to show; scope (datasource) is ignored \u2014 preview never resolves\n // a device. Re-emits on the same cadence with advancing raised times so the table\n // looks live. Returns whole snapshots, matching the live hub's contract.\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n const typed = subscription.enrichDeviceType === true;\n const matches = SYNTHETIC_ALARMS.filter(\n (a) =>\n (subscription.deviceType === undefined ||\n SYNTHETIC_DEVICE_TYPES[a.originatorToken ?? '']?.token === subscription.deviceType) &&\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.severity === subscription.severity) &&\n (subscription.acknowledged == null || a.acknowledged === subscription.acknowledged),\n );\n\n const emit = (): void => {\n const now = Date.now();\n const rows: AlarmRow[] = matches.map((a, i) => {\n const raised = new Date(now - i * 45_000).toISOString();\n return {\n token: `syn-alarm-${i}`,\n originatorType: 'device',\n alarmKey: a.alarmKey,\n metricKey: a.metricKey,\n state: a.state,\n acknowledged: a.acknowledged,\n severity: a.severity,\n originatorToken: a.originatorToken,\n lastValue: a.lastValue,\n raisedTime: raised,\n clearedTime: a.state === 'CLEARED' ? raised : null,\n acknowledgedTime: a.acknowledged ? raised : null,\n acknowledgedBy: a.acknowledged ? 'preview@devicechain' : null,\n ...(typed ? withType(a.originatorToken) : {}),\n };\n });\n if (subscription.countBySeverity) {\n // The live hub's severity count, over the same filtered set (type facet included).\n const bySeverity = new Map<string, number>();\n for (const r of rows) bySeverity.set(r.severity, (bySeverity.get(r.severity) ?? 0) + 1);\n const severityCounts = [...bySeverity].map(([severity, count]) => ({ severity, count }));\n sink.next({ alarms: [], total: rows.length, severityCounts });\n return;\n }\n if (subscription.countByDeviceType) {\n // The live hub's count read, over the same filtered set: per (type, severity).\n const counts = new Map<string, AlarmTypeCount>();\n for (const r of rows) {\n const deviceType = SYNTHETIC_DEVICE_TYPES[r.originatorToken ?? ''];\n if (!deviceType) continue;\n const key = `${deviceType.token}/${r.severity}`;\n const prior = counts.get(key);\n counts.set(key, { deviceType, severity: r.severity, count: (prior?.count ?? 0) + 1 });\n }\n const typeCounts = [...counts.values()];\n sink.next({ alarms: [], total: typeCounts.reduce((n, c) => n + c.count, 0), typeCounts });\n return;\n }\n const inRange = subscription.window ? rows.filter((r) => alarmOverlaps(r, subscription.window!, now)) : rows;\n sink.next({ alarms: inRange.slice(0, subscription.pageSize), total: inRange.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 const inRange = subscription.window ? commands.filter((c) => commandInRange(c, subscription.window!, now)) : commands;\n sink.next({\n deviceToken: SYNTHETIC_COMMAND_DEVICE,\n commands: inRange.slice(0, subscription.pageSize),\n total: inRange.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 // bucketMeasurements answers with ONE bucket per measurement spanning the window, folded from a\n // synthetic reading per interval, so a preview stat reads something the waveform actually did\n // (a minimum and maximum inside [min, max]).\n async bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n const from = Date.parse(window.startTime);\n const to = Date.parse(window.endTime);\n // A window asked in buckets narrower than itself (a history table, a trend) gets one bucket per\n // interval, aligned on the epoch as the store's are; one as wide as the window gets the single\n // bucket below.\n const width = window.intervalSeconds * 1000;\n if (width > 0 && width < to - from) {\n // Past the cap the OLDEST buckets go: a table lists newest first and a trend ends at now, so the\n // preview keeps the end of the window, as the screen does.\n const aligned = Math.floor(from / width) * width;\n const total = Math.ceil((to - aligned) / width);\n const count = Math.min(total, MAX_PREVIEW_BUCKETS);\n const first = aligned + (total - count) * width;\n return names.flatMap((name) => {\n const walk = new Map<string, number>();\n return Array.from({ length: count }, (_, i) => {\n const start = first + i * width;\n // Six instants inside the bucket, never the next bucket's start.\n const values = Array.from({ length: 6 }, (_v, j) => this.valueFor(name, start + (width * j) / 6, walk));\n const sum = values.reduce((a, v) => a + v, 0);\n return { bucketStart: new Date(start).toISOString(), name, avg: sum / values.length, min: Math.min(...values), max: Math.max(...values), sum, count: values.length };\n });\n });\n }\n return names.map((name) => {\n const walk = new Map<string, number>();\n const values = Array.from({ length: 120 }, (_, i) => this.valueFor(name, from + ((to - from) * i) / 119, walk));\n const sum = values.reduce((a, v) => a + v, 0);\n return {\n bucketStart: window.startTime,\n name,\n avg: sum / values.length,\n min: Math.min(...values),\n max: Math.max(...values),\n sum,\n count: values.length,\n };\n });\n }\n\n // A preview fleet: a handful of fabricated devices, each out of phase with the others (the\n // series is keyed by device and name together), and one that never reports its second\n // measurement, so a table previews its \"does not report this\" cell. An unbound slot has no\n // members, as in the live hub.\n private previewMembers(datasource: DatasourceSelector): MemberRef[] {\n if (datasource.kind === 'unbound') return [];\n return Array.from({ length: 5 }, (_, i) => ({ token: `synthetic-${i + 1}`, name: `Unit ${i + 1}` }));\n }\n\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n const members = this.previewMembers(datasource);\n const names = datasource.measurements.length > 0 ? datasource.measurements : [DEFAULT_NAME];\n const walk = new Map<string, number>();\n let seq = 0;\n const emit = (token: string, tMs: number): void => {\n // One member never reports past its first measurement.\n for (const name of token === 'synthetic-4' ? names.slice(0, 1) : names) {\n sink.next({\n id: `syn-m-${seq++}`,\n deviceToken: token,\n eventType: 0,\n occurredTime: new Date(tMs).toISOString(),\n name,\n value: this.valueFor(`${token}/${name}`, tMs, walk),\n classifier: null,\n });\n }\n };\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n for (const m of members) emit(m.token, Date.now());\n const timer = setInterval(() => {\n for (const m of members) emit(m.token, Date.now());\n }, this.intervalMs);\n this.timers.add(timer);\n return () => {\n if (this.timers.delete(timer)) clearInterval(timer);\n };\n }\n\n // A preview fleet's connectivity: three online, one offline (silent for hours) and one that never\n // reported, with no state row, so every state a presence widget draws is previewed. Last-active\n // times are offsets from now, so the preview always reads as just-seen for the online ones.\n subscribePresence(datasource: DatasourceSelector, sink: PresenceSink): () => void {\n // A device is its own roster of one; a group is the preview fleet.\n const members = datasource.kind === 'device' ? [{ token: datasource.deviceToken, name: null }] : this.previewMembers(datasource);\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n const emit = (): void => {\n const now = Date.now();\n const states: DevicePresence[] = [];\n members.forEach((m, i) => {\n if (m.token === 'synthetic-5') return; // never reported: no state row\n const offline = m.token === 'synthetic-4';\n states.push({\n deviceToken: m.token,\n active: !offline,\n lastActivityTime: new Date(now - (offline ? 2 * 3600_000 : (i + 1) * 20_000)).toISOString(),\n inactivityTimeout: 600,\n });\n });\n sink.next(states);\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 // A preview has no history to read: its members report the moment they are subscribed, so the\n // seed settles empty and the live feed alone fills the cells.\n async readMemberSeeds(\n deviceTokens: readonly string[],\n _plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null) => void,\n ): Promise<void> {\n for (const token of deviceTokens) onSeed(token, { latest: {}, observed: [] });\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\nimport type { AnchorTarget } from './types';\n\n// The board-recording format: one captured run of a board, as a plain JSON document a\n// RecordedDataSource can play back. This file owns the SHAPE and the parser; playback is in\n// recorded.ts. The document is produced by the Sitepulse recording converter\n// (demos/sitepulse-unity/tools/recording_to_board.py, version 1, with a demo's profile; Towerpulse's is demos/towerpulse-unity/tools/board-replay.profile.json) and by dc-simulator's\n// sitepulse-record (backend/sims/dc-simulator/recording, version 2); each writer's own\n// documentation is its side of this contract, and they must change together.\n//\n// \uD83D\uDD34 THE PARSER IS FAIL-CLOSED, AND THAT IS ITS WHOLE JOB. A recording is data produced by\n// another tool and published as a static file, so the reader is the last place a bad one\n// can be stopped before it is shown to a person as \"what the platform reported\". An\n// unknown key, a wrong kind or version, a ragged column, an unsorted time axis, a device or\n// metric index out of range, or a channel claim the document cannot honour is a thrown\n// RecordingFormatError naming the path, never a best-effort read. In particular an unknown\n// key is refused rather than ignored: an ignored key is a field somebody believed was being\n// honoured.\n//\n// Layout (formatVersion 1). Every time is `t`/`tMs`, in milliseconds from the START of the\n// run, as the recording viewer APPLIED the update (the first moment a viewer could have\n// seen it). Measurements are COLUMNS (one entry per row, parallel arrays): `d` indexes\n// `devices`, `n` indexes `channels.measurements`, `t` is the time, `v` the value and `s` is\n// 1 for a row the platform itself seeded at the start of the run. Alarms are a fold over\n// history: `snapshots` (the whole alarm set at a moment) plus `events` (one alarm's new\n// state), with ids re-minted as alarm-N. An excerpt carries `excerpt{fromMs,toMs}`: it\n// holds only the measurement rows of that window, but ALL alarm history up to its end.\n//\n// What a recording does NOT hold is declared, not implied: `channels` says which channels\n// were recorded at all. A player answers a channel that was not recorded with \"not in this\n// recording\" rather than with empty data, which would read as a fact about the machines.\n// No version records command data (`channels.commands` is always false).\n//\n// formatVersion 2 adds POSITIONS. `channels.locations` may be true, and `locations` is then\n// columns like the measurements': `d` (device index), `t` (ms from the start), `lat` and\n// `lon` (WGS84 degrees), `hdg` (degrees clockwise from north) and `spd` (m/s), the last two\n// null where the fix did not carry them. With `channels.locations` false the columns are\n// empty. Version 1 (no positions, `locations: []`) is still read, and parses to the same\n// shape with empty columns. Version 2 also lets `converter` carry the producing tool's\n// `name`: a recording read back from the platform's own history (dc-simulator's\n// sitepulse-record) times every row by the device's own stamp, where the Unity converter\n// times it by when the recording viewer applied it. And in version 2 an alarm may carry `val`:\n// the reading that RAISED it, which the platform shows as the alarm's value. sitepulse-record\n// writes it only from the device's own publish log (the value it sent for the alarm's metric\n// at the raise instant), so an alarm raised before the run carries none, and plays with no\n// value rather than a guessed one.\n//\n// Version 2 may also carry `declaredMetrics`: for each device `kind`, the measurement names that\n// kind's device-type profile DECLARES (what the live board reads from the active profile\n// version). It is what lets a replayed fleet tell \"does not report this\" (the profile never\n// declares it: the crusher has no fuel) from \"no data in range\" (declared, and silent). A kind\n// with no entry is unknown, and a player counts its devices as declaring everything, which is the\n// live board's rule for a profile it cannot read. Every key is a kind some device has, and every\n// list names at least one measurement, each once.\n\n// Version 2 may also carry `anchors`: the groupings (an area, a floor, a customer) the run's devices belonged to, each with the\n// device tokens that were members. A board bound to an area or a floor cannot be replayed without them: a recording that carried\n// only devices could not say which of them are on floor 4, and a player that guessed would show one building's numbers as another's.\n// A recording without `anchors` keeps the older rule (the board's one customer anchor holds every device). Each anchor is named once,\n// has at least one member, and every member is a device of the run.\n\n// The newest version this reader understands; it reads every version in\n// RECORDING_FORMAT_VERSIONS.\nexport const RECORDING_FORMAT_VERSION = 2;\nexport const RECORDING_FORMAT_VERSIONS = [1, 2] as const;\nexport const RECORDING_KIND = 'sitepulse-board-recording';\n\nconst ALARM_STATES = ['ACTIVE', 'CLEARED'] as const;\nconst ALARM_SEVERITIES = ['CRITICAL', 'MAJOR', 'MINOR', 'WARNING'] as const;\n\nexport class RecordingFormatError extends Error {\n constructor(\n public readonly path: string,\n reason: string,\n ) {\n super(`invalid recording at ${path}: ${reason}`);\n this.name = 'RecordingFormatError';\n }\n}\n\n// One segment of the platform's simulation clock: from `fromMs` on, it ran in `mode` at\n// `scale` times real time (1 = real time). Disclosed by the host; playback does not use it.\nexport interface RecordedClockSegment {\n fromMs: number;\n mode: string;\n scale: number;\n}\n\nexport interface RecordedDevice {\n id: string;\n token: string;\n kind: string;\n}\n\nexport interface RecordedChannels {\n measurements: string[];\n alarms: boolean;\n // Whether positions were recorded (only ever true from formatVersion 2).\n locations: boolean;\n // Always false: no version carries command data.\n commands: false;\n}\n\nexport interface RecordedChapter {\n tMs: number;\n name: string;\n note: string;\n}\n\n// The measurement rows, as parallel columns sorted by `t`.\nexport interface RecordedMeasurementColumns {\n d: number[];\n n: number[];\n t: number[];\n v: number[];\n s: Array<0 | 1>;\n}\n\n// One alarm as the recording saw it. `occ` is the platform's own time for THIS state: when\n// the alarm was raised for an ACTIVE row, when it cleared for a CLEARED row. `ack` is\n// present only when the alarm was acknowledged. `dev` is a device token.\nexport interface RecordedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: (typeof ALARM_STATES)[number];\n sev: (typeof ALARM_SEVERITIES)[number];\n occ: string;\n ack?: true;\n // The reading that raised the alarm (version 2; absent when the recording does not know it).\n val?: number;\n}\n\n// A snapshot is the whole alarm set as the recorder's query returned it. `requestedAtMs` is\n// when that query was SENT (it answers at `tMs`); an event applied after it is newer than\n// anything the answer can say about that alarm. `total` always equals the alarms listed: a\n// truncated answer cannot be played as if it were complete.\nexport interface RecordedAlarmSnapshot {\n tMs: number;\n requestedAtMs: number;\n total: number;\n alarms: RecordedAlarm[];\n}\n\nexport interface RecordedAlarmEvent extends RecordedAlarm {\n tMs: number;\n}\n\n// The position rows, as parallel columns sorted by `t`. Empty unless channels.locations.\nexport interface RecordedLocationColumns {\n d: number[];\n t: number[];\n lat: number[];\n lon: number[];\n hdg: Array<number | null>;\n spd: Array<number | null>;\n}\n\n// What each device kind's profile declares (formatVersion 2, optional): kind -> measurement names.\nexport type RecordedDeclarations = Record<string, string[]>;\n\n// A grouping the run's devices belonged to (formatVersion 2, optional): which devices were its members, by token.\nexport interface RecordedAnchor extends AnchorTarget {\n devices: string[];\n}\n\nexport interface BoardRecording {\n kind: typeof RECORDING_KIND;\n formatVersion: (typeof RECORDING_FORMAT_VERSIONS)[number];\n runId: string;\n startedAtUtc: string;\n durationMs: number;\n platformVersion: string;\n instance: string;\n tenant: string;\n build: { gitSha: string; sdkCommit: string };\n clock: RecordedClockSegment[];\n devices: RecordedDevice[];\n // Absent when the recording does not say (every version 1 recording, and older version 2 ones).\n declaredMetrics?: RecordedDeclarations;\n // Absent when the recording does not say (every version 1 recording, and older version 2 ones).\n anchors?: RecordedAnchor[];\n channels: RecordedChannels;\n board: { path: string; sourceCommit: string; sha256: string };\n sourceHashes: Record<string, string>;\n converter: { version: number; name?: string };\n chapters: RecordedChapter[];\n excerpt?: { fromMs: number; toMs: number };\n measurements: RecordedMeasurementColumns;\n alarms: { snapshots: RecordedAlarmSnapshot[]; events: RecordedAlarmEvent[] };\n locations: RecordedLocationColumns;\n}\n\n// ---- helpers that read a parsed recording ---------------------------------\n\n// The span a player may cover: the whole run, or the excerpt's window.\nexport function recordingBounds(rec: BoardRecording): { fromMs: number; toMs: number } {\n return rec.excerpt ? { ...rec.excerpt } : { fromMs: 0, toMs: rec.durationMs };\n}\n\n// The wall-clock instant (epoch ms) a recording offset corresponds to. Offsets are\n// measured in real elapsed time from the run's start, so this is the start plus the offset;\n// an accelerated SIMULATION clock changes what the platform's own timestamps say, not how\n// long the recording took (see simulationScaleAt for disclosing that).\nexport function recordedWallTimeMs(rec: BoardRecording, tMs: number): number {\n return Date.parse(rec.startedAtUtc) + tMs;\n}\n\n// The simulation clock's scale at a recording offset, or null before the first segment\n// begins (the recorder had not yet reported its clock).\nexport function simulationScaleAt(rec: BoardRecording, tMs: number): { mode: string; scale: number } | null {\n let hit: RecordedClockSegment | null = null;\n for (const seg of rec.clock) {\n if (seg.fromMs <= tMs) hit = seg;\n }\n return hit ? { mode: hit.mode, scale: hit.scale } : null;\n}\n\n// ---- parser ---------------------------------------------------------------\n\ntype Obj = Record<string, unknown>;\n\nfunction isObj(v: unknown): v is Obj {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\n// strict returns `v` as an object after checking it carries every required key and no key\n// outside required + optional.\nfunction strict(v: unknown, path: string, required: readonly string[], optional: readonly string[] = []): Obj {\n if (!isObj(v)) throw new RecordingFormatError(path, 'expected an object');\n const allowed = new Set([...required, ...optional]);\n for (const key of Object.keys(v)) {\n if (!allowed.has(key)) throw new RecordingFormatError(`${path}.${key}`, 'unknown key');\n }\n for (const key of required) {\n if (!Object.prototype.hasOwnProperty.call(v, key)) {\n throw new RecordingFormatError(`${path}.${key}`, 'missing required key');\n }\n }\n return v;\n}\n\nfunction str(v: unknown, path: string): string {\n if (typeof v !== 'string' || v === '') throw new RecordingFormatError(path, 'expected a non-empty string');\n return v;\n}\n\nfunction bool(v: unknown, path: string): boolean {\n if (typeof v !== 'boolean') throw new RecordingFormatError(path, 'expected a boolean');\n return v;\n}\n\nfunction num(v: unknown, path: string): number {\n if (typeof v !== 'number' || !Number.isFinite(v)) throw new RecordingFormatError(path, 'expected a finite number');\n return v;\n}\n\nfunction arr(v: unknown, path: string): unknown[] {\n if (!Array.isArray(v)) throw new RecordingFormatError(path, 'expected an array');\n return v;\n}\n\nfunction oneOf<T extends string>(v: unknown, path: string, allowed: readonly T[]): T {\n if (typeof v !== 'string' || !(allowed as readonly string[]).includes(v)) {\n throw new RecordingFormatError(path, `expected one of ${allowed.join(', ')}`);\n }\n return v as T;\n}\n\nfunction matching(v: unknown, path: string, re: RegExp, what: string): string {\n if (typeof v !== 'string' || !re.test(v)) throw new RecordingFormatError(path, `expected ${what}`);\n return v;\n}\n\nconst HEX64 = /^[0-9a-f]{64}$/;\nconst HEX40 = /^[0-9a-f]{40}$/;\nconst HEX_SHORT = /^[0-9a-f]{7,40}$/;\n\nconst INSTANT = /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d+))?Z$/;\n\n// Strict: a date that does not exist (2026-02-30) is refused, not rolled over to March.\nfunction instant(v: unknown, path: string): string {\n const s = str(v, path);\n const m = INSTANT.exec(s);\n if (m) {\n const [y, mo, d, h, mi, sec] = m.slice(1, 7).map(Number);\n const t = new Date(Date.UTC(y, mo - 1, d, h, mi, sec));\n if (\n t.getUTCFullYear() === y && t.getUTCMonth() === mo - 1 && t.getUTCDate() === d &&\n t.getUTCHours() === h && t.getUTCMinutes() === mi && t.getUTCSeconds() === sec\n ) {\n return s;\n }\n }\n throw new RecordingFormatError(path, 'expected an RFC 3339 UTC instant ending in Z that exists on the calendar');\n}\n\n// Orders two instants as parsed times, not as strings: \"...:10Z\" is BEFORE \"...:10.5Z\"\n// although it sorts after it as text. Fractions compare to the full digits given (the\n// platform writes up to 7), not just to the millisecond Date can hold.\nexport function compareInstants(a: string, b: string): number {\n const pa = INSTANT.exec(a);\n const pb = INSTANT.exec(b);\n if (!pa || !pb) throw new RangeError(`compareInstants: not an instant: ${pa ? b : a}`);\n const whole = (m: RegExpExecArray): number => Date.parse(`${m[1]}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6]}Z`);\n const wa = whole(pa);\n const wb = whole(pb);\n if (wa !== wb) return wa < wb ? -1 : 1;\n const fa = (pa[7] ?? '').padEnd(12, '0');\n const fb = (pb[7] ?? '').padEnd(12, '0');\n return fa < fb ? -1 : fa > fb ? 1 : 0;\n}\n\nfunction column<T>(v: unknown, path: string, item: (x: unknown, p: string) => T, length?: number): T[] {\n const a = arr(v, path);\n if (length !== undefined && a.length !== length) {\n throw new RecordingFormatError(path, `length ${a.length} does not match the time column (${length})`);\n }\n return a.map((x, i) => item(x, `${path}[${i}]`));\n}\n\nfunction nonNegative(v: unknown, path: string): number {\n const n = num(v, path);\n if (n < 0) throw new RecordingFormatError(path, 'must not be negative');\n return n;\n}\n\nexport function parseBoardRecording(input: unknown): BoardRecording {\n const root = strict(\n input,\n '$',\n [\n 'kind', 'formatVersion', 'runId', 'startedAtUtc', 'durationMs', 'platformVersion', 'instance', 'tenant',\n 'build', 'clock', 'devices', 'channels', 'board', 'sourceHashes', 'converter', 'chapters',\n 'measurements', 'alarms', 'locations',\n ],\n ['excerpt', 'declaredMetrics', 'anchors'],\n );\n\n if (root.kind !== RECORDING_KIND) {\n throw new RecordingFormatError('$.kind', `expected ${JSON.stringify(RECORDING_KIND)}`);\n }\n const version = root.formatVersion;\n if (version !== 1 && version !== 2) {\n throw new RecordingFormatError(\n '$.formatVersion',\n `unsupported version ${JSON.stringify(root.formatVersion)}; this reader understands ${RECORDING_FORMAT_VERSIONS.join(', ')}`,\n );\n }\n const durationMs = num(root.durationMs, '$.durationMs');\n if (durationMs <= 0) throw new RecordingFormatError('$.durationMs', 'must be positive');\n\n const b = strict(root.build, '$.build', ['gitSha', 'sdkCommit']);\n const build = {\n gitSha: matching(b.gitSha, '$.build.gitSha', HEX_SHORT, 'a lowercase hex commit id'),\n sdkCommit: matching(b.sdkCommit, '$.build.sdkCommit', HEX_SHORT, 'a lowercase hex commit id'),\n };\n\n const clock = column(root.clock, '$.clock', (x, p): RecordedClockSegment => {\n const o = strict(x, p, ['fromMs', 'mode', 'scale']);\n const scale = num(o.scale, `${p}.scale`);\n if (scale <= 0) throw new RecordingFormatError(`${p}.scale`, 'must be positive');\n return { fromMs: nonNegative(o.fromMs, `${p}.fromMs`), mode: str(o.mode, `${p}.mode`), scale };\n });\n clock.forEach((seg, i) => {\n if (i > 0 && seg.fromMs <= clock[i - 1].fromMs) throw new RecordingFormatError(`$.clock[${i}].fromMs`, 'must increase');\n });\n\n const devices = column(root.devices, '$.devices', (x, p): RecordedDevice => {\n const o = strict(x, p, ['id', 'token', 'kind']);\n return { id: str(o.id, `${p}.id`), token: str(o.token, `${p}.token`), kind: str(o.kind, `${p}.kind`) };\n });\n const tokens = new Set(devices.map((d) => d.token));\n if (tokens.size !== devices.length || new Set(devices.map((d) => d.id)).size !== devices.length) {\n throw new RecordingFormatError('$.devices', 'duplicate device id or token');\n }\n\n let declaredMetrics: RecordedDeclarations | undefined;\n if (root.declaredMetrics !== undefined) {\n if (version === 1) throw new RecordingFormatError('$.declaredMetrics', 'version 1 carries no declarations');\n const dm = root.declaredMetrics;\n if (!isObj(dm)) throw new RecordingFormatError('$.declaredMetrics', 'expected an object');\n const kinds = new Set(devices.map((d) => d.kind));\n declaredMetrics = {};\n for (const kind of Object.keys(dm)) {\n const p = `$.declaredMetrics.${kind}`;\n if (!kinds.has(kind)) throw new RecordingFormatError(p, `no device in $.devices is of kind '${kind}'`);\n const names = column(dm[kind], p, str);\n if (names.length === 0) throw new RecordingFormatError(p, 'declares nothing; leave the kind out instead');\n if (new Set(names).size !== names.length) throw new RecordingFormatError(p, 'duplicate measurement name');\n declaredMetrics[kind] = names;\n }\n }\n\n let anchors: RecordedAnchor[] | undefined;\n if (root.anchors !== undefined) {\n if (version === 1) throw new RecordingFormatError('$.anchors', 'version 1 carries no anchors');\n const listed = column(root.anchors, '$.anchors', (x, p): RecordedAnchor => {\n const o = strict(x, p, ['relationship', 'targetType', 'targetToken', 'devices']);\n const members = column(o.devices, `${p}.devices`, str);\n if (members.length === 0) throw new RecordingFormatError(`${p}.devices`, 'has no member; leave the anchor out instead');\n if (new Set(members).size !== members.length) throw new RecordingFormatError(`${p}.devices`, 'duplicate member');\n for (const m of members) {\n if (!tokens.has(m)) throw new RecordingFormatError(`${p}.devices`, `'${m}' is not a device of the run`);\n }\n return {\n relationship: str(o.relationship, `${p}.relationship`),\n targetType: oneOf(o.targetType, `${p}.targetType`, ['customer', 'area', 'asset'] as const),\n targetToken: str(o.targetToken, `${p}.targetToken`),\n devices: members,\n };\n });\n if (listed.length === 0) throw new RecordingFormatError('$.anchors', 'declares nothing; leave it out instead');\n const seen = new Set<string>();\n listed.forEach((a, i) => {\n const key = `${a.relationship}\\u0000${a.targetType}\\u0000${a.targetToken}`;\n if (seen.has(key)) throw new RecordingFormatError(`$.anchors[${i}]`, 'the same anchor is declared twice');\n seen.add(key);\n });\n anchors = listed;\n }\n\n const ch = strict(root.channels, '$.channels', ['measurements', 'alarms', 'locations', 'commands']);\n if (version === 1 && ch.locations !== false) {\n throw new RecordingFormatError('$.channels.locations', 'version 1 records no positions; must be false');\n }\n if (ch.commands !== false) {\n throw new RecordingFormatError('$.channels.commands', 'this version records no command data; must be false');\n }\n const channels: RecordedChannels = {\n measurements: column(ch.measurements, '$.channels.measurements', str),\n alarms: bool(ch.alarms, '$.channels.alarms'),\n locations: bool(ch.locations, '$.channels.locations'),\n commands: false,\n };\n if (new Set(channels.measurements).size !== channels.measurements.length) {\n throw new RecordingFormatError('$.channels.measurements', 'duplicate measurement name');\n }\n\n const bd = strict(root.board, '$.board', ['path', 'sourceCommit', 'sha256']);\n const board = {\n path: str(bd.path, '$.board.path'),\n sourceCommit: matching(bd.sourceCommit, '$.board.sourceCommit', HEX40, 'a full lowercase hex commit id'),\n sha256: matching(bd.sha256, '$.board.sha256', HEX64, 'a lowercase hex SHA-256'),\n };\n\n const sh = root.sourceHashes;\n if (!isObj(sh)) throw new RecordingFormatError('$.sourceHashes', 'expected an object');\n const sourceHashes: Record<string, string> = {};\n for (const key of Object.keys(sh)) {\n // Assigning '__proto__' on a plain object swaps its prototype instead of adding a key.\n if (key === '__proto__') throw new RecordingFormatError(`$.sourceHashes.${key}`, 'reserved key');\n sourceHashes[key] = matching(sh[key], `$.sourceHashes.${key}`, HEX64, 'a lowercase hex SHA-256');\n }\n\n const cv = strict(root.converter, '$.converter', ['version'], version >= 2 ? ['name'] : []);\n const converter: BoardRecording['converter'] = { version: num(cv.version, '$.converter.version') };\n if (cv.name !== undefined) converter.name = str(cv.name, '$.converter.name');\n\n let excerpt: { fromMs: number; toMs: number } | undefined;\n if (root.excerpt !== undefined) {\n const e = strict(root.excerpt, '$.excerpt', ['fromMs', 'toMs']);\n excerpt = { fromMs: nonNegative(e.fromMs, '$.excerpt.fromMs'), toMs: nonNegative(e.toMs, '$.excerpt.toMs') };\n if (excerpt.fromMs >= excerpt.toMs || excerpt.toMs > durationMs) {\n throw new RecordingFormatError('$.excerpt', 'must be a non-empty window inside the run');\n }\n }\n const lo = excerpt ? excerpt.fromMs : 0;\n const hi = excerpt ? excerpt.toMs : durationMs;\n\n const chapters = column(root.chapters, '$.chapters', (x, p): RecordedChapter => {\n const o = strict(x, p, ['tMs', 'name', 'note']);\n const tMs = num(o.tMs, `${p}.tMs`);\n if (tMs < 0 || tMs > durationMs) throw new RecordingFormatError(`${p}.tMs`, 'is outside the run');\n if (excerpt && (tMs < lo || tMs > hi)) throw new RecordingFormatError(`${p}.tMs`, 'is outside the excerpt window');\n return { tMs, name: str(o.name, `${p}.name`), note: str(o.note, `${p}.note`) };\n });\n chapters.forEach((c, i) => {\n if (i > 0 && c.tMs < chapters[i - 1].tMs) throw new RecordingFormatError(`$.chapters[${i}].tMs`, 'chapters must not decrease in time');\n });\n\n const m = strict(root.measurements, '$.measurements', ['d', 'n', 't', 'v', 's']);\n // A time column (measurements and positions alike): offsets inside [lo, hi], never decreasing.\n const times = (v: unknown, path: string): number[] => {\n const col = column(v, path, num);\n let prev = lo;\n col.forEach((x, i) => {\n if (x < lo || x > hi) throw new RecordingFormatError(`${path}[${i}]`, `${x} is outside [${lo}, ${hi}]`);\n if (x < prev) throw new RecordingFormatError(`${path}[${i}]`, 'time column must not decrease');\n prev = x;\n });\n return col;\n };\n const t = times(m.t, '$.measurements.t');\n const index = (name: string, limit: number) => (x: unknown, p: string): number => {\n const n = num(x, p);\n if (!Number.isInteger(n) || n < 0 || n >= limit) throw new RecordingFormatError(p, `${name} index out of range`);\n return n;\n };\n const measurements: RecordedMeasurementColumns = {\n d: column(m.d, '$.measurements.d', index('device', devices.length), t.length),\n n: column(m.n, '$.measurements.n', index('measurement', channels.measurements.length), t.length),\n t,\n v: column(m.v, '$.measurements.v', num, t.length),\n s: column(\n m.s,\n '$.measurements.s',\n (x, p): 0 | 1 => {\n if (x !== 0 && x !== 1) throw new RecordingFormatError(p, 'expected 0 or 1');\n return x;\n },\n t.length,\n ),\n };\n\n const al = strict(root.alarms, '$.alarms', ['snapshots', 'events']);\n const alarm = (o: Obj, p: string): RecordedAlarm => {\n const id = matching(o.id, `${p}.id`, /^alarm-\\d+$/, 'a re-minted id (alarm-N)');\n const dev = str(o.dev, `${p}.dev`);\n if (!tokens.has(dev)) throw new RecordingFormatError(`${p}.dev`, `device '${dev}' is not in $.devices`);\n if (o.ack !== undefined && o.ack !== true) throw new RecordingFormatError(`${p}.ack`, 'present only when true');\n if (o.val !== undefined && (typeof o.val !== 'number' || !Number.isFinite(o.val))) {\n throw new RecordingFormatError(`${p}.val`, 'expected a finite number');\n }\n const out: RecordedAlarm = {\n id,\n dev,\n key: str(o.key, `${p}.key`),\n metric: str(o.metric, `${p}.metric`),\n state: oneOf(o.state, `${p}.state`, ALARM_STATES),\n sev: oneOf(o.sev, `${p}.sev`, ALARM_SEVERITIES),\n occ: instant(o.occ, `${p}.occ`),\n };\n if (o.ack === true) out.ack = true;\n if (typeof o.val === 'number') out.val = o.val;\n return out;\n };\n const ALARM_KEYS = ['id', 'dev', 'key', 'metric', 'state', 'sev', 'occ'];\n // `val` is a version 2 field: a version 1 document that carries it is refused, not read.\n const ALARM_OPTIONAL = version >= 2 ? ['ack', 'val'] : ['ack'];\n const checkTime = (tMs: number, p: string): number => {\n if (tMs < 0 || tMs > hi) throw new RecordingFormatError(p, `${tMs} is outside [0, ${hi}]`);\n return tMs;\n };\n const snapshots = column(al.snapshots, '$.alarms.snapshots', (x, p): RecordedAlarmSnapshot => {\n const o = strict(x, p, ['tMs', 'requestedAtMs', 'total', 'alarms']);\n const alarms = column(o.alarms, `${p}.alarms`, (a, ap) => alarm(strict(a, ap, ALARM_KEYS, ALARM_OPTIONAL), ap));\n const ids = new Set<string>();\n alarms.forEach((a, i) => {\n if (ids.has(a.id)) throw new RecordingFormatError(`${p}.alarms[${i}].id`, `duplicate alarm ${a.id} in one snapshot`);\n ids.add(a.id);\n });\n // A snapshot that lists fewer alarms than its total is a truncated answer. Folding it\n // as the whole set would erase every alarm it left out.\n const total = nonNegative(o.total, `${p}.total`);\n if (total !== alarms.length) {\n throw new RecordingFormatError(`${p}.total`, `is ${total} but ${alarms.length} alarms are listed (truncated snapshot)`);\n }\n const tMs = checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`);\n const requestedAtMs = nonNegative(o.requestedAtMs, `${p}.requestedAtMs`);\n if (!Number.isInteger(requestedAtMs)) throw new RecordingFormatError(`${p}.requestedAtMs`, 'expected an integer');\n if (requestedAtMs > tMs) throw new RecordingFormatError(`${p}.requestedAtMs`, 'a query cannot be sent after its answer');\n return { tMs, requestedAtMs, total, alarms };\n });\n const events = column(al.events, '$.alarms.events', (x, p): RecordedAlarmEvent => {\n const o = strict(x, p, [...ALARM_KEYS, 'tMs'], ALARM_OPTIONAL);\n return { ...alarm(o, p), tMs: checkTime(num(o.tMs, `${p}.tMs`), `${p}.tMs`) };\n });\n for (const [name, list] of [['snapshots', snapshots], ['events', events]] as const) {\n list.forEach((e, i) => {\n if (i > 0 && e.tMs < list[i - 1].tMs) throw new RecordingFormatError(`$.alarms.${name}[${i}].tMs`, 'must not decrease');\n });\n }\n // An alarm id names one alarm on one device for the whole recording.\n const deviceOf = new Map<string, string>();\n const everyAlarm: Array<[string, RecordedAlarm]> = [\n ...snapshots.flatMap((s, i) => s.alarms.map((a, j): [string, RecordedAlarm] => [`$.alarms.snapshots[${i}].alarms[${j}]`, a])),\n ...events.map((e, i): [string, RecordedAlarm] => [`$.alarms.events[${i}]`, e]),\n ];\n for (const [path, a] of everyAlarm) {\n const seen = deviceOf.get(a.id);\n if (seen !== undefined && seen !== a.dev) {\n throw new RecordingFormatError(`${path}.dev`, `alarm ${a.id} was on '${seen}' and cannot move to '${a.dev}'`);\n }\n deviceOf.set(a.id, a.dev);\n }\n if (!channels.alarms && (snapshots.length > 0 || events.length > 0)) {\n throw new RecordingFormatError('$.alarms', 'history present but $.channels.alarms is false');\n }\n\n let locations: RecordedLocationColumns;\n if (version === 1) {\n if (!Array.isArray(root.locations) || root.locations.length !== 0) {\n throw new RecordingFormatError('$.locations', 'version 1 carries no positions; must be an empty array');\n }\n locations = { d: [], t: [], lat: [], lon: [], hdg: [], spd: [] };\n } else {\n const lc = strict(root.locations, '$.locations', ['d', 't', 'lat', 'lon', 'hdg', 'spd']);\n const lt = times(lc.t, '$.locations.t');\n const within = (min: number, max: number, what: string) => (x: unknown, p: string): number => {\n const n = num(x, p);\n if (n < min || n > max) throw new RecordingFormatError(p, `${what} ${n} is outside [${min}, ${max}]`);\n return n;\n };\n const optional = (min: number, max: number, what: string) => (x: unknown, p: string): number | null =>\n x === null ? null : within(min, max, what)(x, p);\n locations = {\n d: column(lc.d, '$.locations.d', index('device', devices.length), lt.length),\n t: lt,\n lat: column(lc.lat, '$.locations.lat', within(-90, 90, 'latitude'), lt.length),\n lon: column(lc.lon, '$.locations.lon', within(-180, 180, 'longitude'), lt.length),\n hdg: column(lc.hdg, '$.locations.hdg', optional(0, 360, 'heading'), lt.length),\n spd: column(lc.spd, '$.locations.spd', optional(0, Number.MAX_VALUE, 'speed'), lt.length),\n };\n if (!channels.locations && lt.length > 0) {\n throw new RecordingFormatError('$.locations', 'positions present but $.channels.locations is false');\n }\n }\n\n const rec: BoardRecording = {\n kind: RECORDING_KIND,\n formatVersion: version,\n runId: str(root.runId, '$.runId'),\n startedAtUtc: instant(root.startedAtUtc, '$.startedAtUtc'),\n durationMs,\n platformVersion: str(root.platformVersion, '$.platformVersion'),\n instance: str(root.instance, '$.instance'),\n tenant: str(root.tenant, '$.tenant'),\n build,\n clock,\n devices,\n channels,\n board,\n sourceHashes,\n converter,\n chapters,\n measurements,\n alarms: { snapshots, events },\n locations,\n };\n if (excerpt) rec.excerpt = excerpt;\n if (declaredMetrics) rec.declaredMetrics = declaredMetrics;\n if (anchors) rec.anchors = anchors;\n return rec;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// RecordedDataSource \u2014 plays one recorded run (recording.ts) through the SAME\n// WidgetDataSource contract the live DashboardHub and the SyntheticDataSource implement,\n// so the dashboard renderer and every widget consume it unchanged.\n//\n// It is deliberately NOT a mode of SyntheticDataSource. Synthetic data is canned and\n// labelled synthetic; this is observed data, and folding the two together would blur the\n// one boundary a viewer relies on. It also implements WidgetDataSource ONLY, not\n// WidgetActions: a recording cannot acknowledge, clear or send anything, so no write\n// control may render (the alarm table shows its action buttons only when an actions seam\n// exists).\n//\n// \u2500\u2500 Time \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A RecordedClock owns the cursor (play / pause / seek / rate). A source VIEW follows the\n// clock forward: as the cursor passes a row's `t` the row is delivered to the widgets\n// that asked for it.\n//\n// \uD83D\uDD34 A SEEK ENDS A VIEW; IT DOES NOT REWIND ONE. Widget streams only append (a chart\n// window, a \"latest\" card keep what they were given), so a backwards jump cannot be\n// retracted through a sink. The host therefore builds a NEW RecordedDataSource over the\n// same parsed recording after every seek: the renderer sees a new data source, every\n// widget re-subscribes and resets its buffer, and the new view back-fills the preceding\n// window at the new cursor. The old view goes quiet on the seek event (it never emits\n// another row, in either direction) and `isStale` reports it, so a host that forgets to\n// swap it freezes visibly in a test instead of replaying the wrong moment.\n//\n// \u2500\u2500 Re-timing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A source may be built with a TIME SHIFT (RecordedSourceOptions.timeShiftMs, usually\n// retimeShiftMs: \"the cursor is now\"). Every instant the source hands out is then moved by\n// that one amount: sample and position times, alarm raise and clear times, now(), and the\n// windows a stat asks buckets for are moved back by it before the rows are read. One\n// shift for everything, so ages, alarm times, aggregates and positions agree with each\n// other exactly as they did in the run; only the date they claim is different. A shift is\n// a presentation choice of the HOST (the showcase embed), which must say on screen that it\n// is a replay.\n//\n// \u2500\u2500 What the recording does not hold \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// A channel the recording did not capture raises NotInRecordingError through the sink's\n// error path. It NEVER answers with an empty snapshot: an empty map would read as \"no\n// machine has a position\" when the truth is \"this recording has no positions\". The widget\n// frame already renders a sink error as \"Data unavailable\" plus the error text, so the\n// message below is what a viewer reads.\n//\n// No network: this module touches no fetch, socket or timer of its own beyond the clock's\n// optional frame ticker, and the tests trap any attempt.\n\nimport type {\n AlarmSnapshot,\n AlarmStreamSink,\n AlarmSubscription,\n CommandStreamSink,\n CommandSubscription,\n DeviceResolver,\n LocationStreamSink,\n LocationSubscription,\n WidgetDataSource,\n WidgetStreamSink,\n} from './hub';\nimport type {\n AlarmRow,\n AnchorTarget,\n DatasourceSelector,\n EntityCandidateLister,\n EntityListKind,\n MeasurementSample,\n} from './types';\nimport { alarmOverlaps } from './event-window';\nimport type { HistoryWindow } from './history';\nimport type { MeasurementBucket } from './queries';\nimport {\n compareInstants,\n recordedWallTimeMs,\n recordingBounds,\n type BoardRecording,\n type RecordedAlarm,\n type RecordedAlarmEvent,\n type RecordedAlarmSnapshot,\n} from './recording';\nimport type { MemberSeed, MemberSeedPlan, MemberSink } from './members';\n\n// How far back from the cursor a fresh subscription back-fills measurement rows. Raw rows,\n// not buckets: a recording has no aggregate to show honestly.\nexport const RECORDED_HISTORY_WINDOW_MS = 60 * 60 * 1000;\n\n// ---- the typed \"not in this recording\" error ------------------------------\n\nexport type RecordedChannel = 'measurements' | 'alarms' | 'locations' | 'commands';\n\nexport class NotInRecordingError extends Error {\n readonly channel: RecordedChannel;\n readonly detail: string;\n\n constructor(channel: RecordedChannel, detail = '') {\n super(`Not in this recording${detail ? `: ${detail}` : ''}`);\n this.name = 'NotInRecordingError';\n this.channel = channel;\n this.detail = detail;\n }\n\n // The widget error frame prints String(error). The default would read\n // \"NotInRecordingError: Not in this recording\", which says the name twice; the message\n // alone is what a viewer should see.\n toString(): string {\n return this.message;\n }\n}\n\n// ---- the replay clock -----------------------------------------------------\n\nexport const PLAYBACK_RATES = [1, 4, 16] as const;\nexport type PlaybackRate = (typeof PLAYBACK_RATES)[number];\n\n// 'tick' = the cursor advanced during playback; 'seek' = the cursor was moved (any\n// direction); 'state' = play / pause / rate changed with the cursor where it was.\nexport interface ClockEvent {\n timeMs: number;\n reason: 'tick' | 'seek' | 'state';\n}\n\n// A frame source for the clock. The default drives requestAnimationFrame (or a timer where\n// there is none); a test injects its own, or drives the clock by hand with advance().\nexport interface ClockTicker {\n // Start calling onFrame(wallDeltaMs) until the returned stopper is called.\n start(onFrame: (wallDeltaMs: number) => void): () => void;\n}\n\nfunction defaultTicker(): ClockTicker {\n return {\n start(onFrame) {\n const now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now());\n let last = now();\n const step = (): void => {\n const t = now();\n const dt = t - last;\n last = t;\n onFrame(dt);\n };\n if (typeof requestAnimationFrame === 'function') {\n let handle = 0;\n let stopped = false;\n const loop = (): void => {\n if (stopped) return;\n step();\n handle = requestAnimationFrame(loop);\n };\n handle = requestAnimationFrame(loop);\n return () => {\n stopped = true;\n if (typeof cancelAnimationFrame === 'function') cancelAnimationFrame(handle);\n };\n }\n const timer = setInterval(step, 100);\n return () => clearInterval(timer);\n },\n };\n}\n\nexport class RecordedClock {\n // The end of the playable span, and its beginning (0 for a whole run; an excerpt starts\n // at its window).\n readonly durationMs: number;\n readonly minMs: number;\n private time: number;\n private isPlaying = false;\n private playbackRate: PlaybackRate = 1;\n private stopTicker: (() => void) | null = null;\n private readonly ticker: ClockTicker;\n private readonly listeners = new Set<(ev: ClockEvent) => void>();\n\n constructor(durationMs: number, options: { ticker?: ClockTicker; startMs?: number; minMs?: number } = {}) {\n if (!Number.isFinite(durationMs) || durationMs <= 0) {\n throw new RangeError(`RecordedClock: durationMs must be positive and finite, got ${durationMs}`);\n }\n this.durationMs = durationMs;\n this.minMs = options.minMs ?? 0;\n if (!Number.isFinite(this.minMs) || this.minMs < 0 || this.minMs >= durationMs) {\n throw new RangeError(`RecordedClock: minMs must be in [0, ${durationMs}), got ${this.minMs}`);\n }\n this.ticker = options.ticker ?? defaultTicker();\n this.time = this.clampTime(options.startMs ?? this.minMs);\n }\n\n get timeMs(): number {\n return this.time;\n }\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get rate(): PlaybackRate {\n return this.playbackRate;\n }\n\n get ended(): boolean {\n return this.time >= this.durationMs;\n }\n\n // play starts the cursor moving. At the end of the run it does nothing: playback stops\n // there (no loop seam) and a host that wants to start over seeks to 0 first.\n play(): void {\n if (this.isPlaying || this.ended) return;\n this.isPlaying = true;\n this.stopTicker = this.ticker.start((dt) => this.advance(dt));\n this.emit('state');\n }\n\n pause(): void {\n if (!this.isPlaying) return;\n this.halt();\n this.emit('state');\n }\n\n // seek moves the cursor, clamped to [minMs, durationMs]. Playback state is unchanged, except\n // that seeking to the very end stops it (there is nothing left to play).\n seek(tMs: number): void {\n if (!Number.isFinite(tMs)) throw new RangeError(`RecordedClock.seek: not a finite time: ${tMs}`);\n this.time = this.clampTime(tMs);\n if (this.isPlaying && this.ended) this.halt();\n this.emit('seek');\n }\n\n setRate(rate: PlaybackRate): void {\n if (!(PLAYBACK_RATES as readonly number[]).includes(rate)) {\n throw new RangeError(`RecordedClock.setRate: ${rate} is not one of ${PLAYBACK_RATES.join(', ')}`);\n }\n this.playbackRate = rate;\n this.emit('state');\n }\n\n // advance moves the cursor by wallDeltaMs of real time (scaled by the playback rate).\n // The ticker calls this each frame; a test calls it directly. It does nothing while\n // paused, and stops at the end of the run.\n advance(wallDeltaMs: number): void {\n if (!this.isPlaying) return;\n if (!Number.isFinite(wallDeltaMs) || wallDeltaMs < 0) {\n throw new RangeError(`RecordedClock.advance: expected a non-negative finite delta, got ${wallDeltaMs}`);\n }\n this.time = this.clampTime(this.time + wallDeltaMs * this.playbackRate);\n if (this.ended) this.halt();\n this.emit('tick');\n }\n\n subscribe(listener: (ev: ClockEvent) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n private halt(): void {\n this.isPlaying = false;\n this.stopTicker?.();\n this.stopTicker = null;\n }\n\n private clampTime(t: number): number {\n return t < this.minMs ? this.minMs : t > this.durationMs ? this.durationMs : t;\n }\n\n private emit(reason: ClockEvent['reason']): void {\n for (const listener of [...this.listeners]) listener({ timeMs: this.time, reason });\n }\n}\n\n// ---- helpers --------------------------------------------------------------\n\n// First index whose value is >= x (the array is non-decreasing).\nfunction lowerBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] < x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// First index whose value is > x.\nfunction upperBound(a: readonly number[], x: number): number {\n let lo = 0;\n let hi = a.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (a[mid] <= x) lo = mid + 1;\n else hi = mid;\n }\n return lo;\n}\n\n// createRecordedClock builds a clock covering exactly what the recording can play: the\n// whole run, or an excerpt's window (starting at its beginning).\nexport function createRecordedClock(rec: BoardRecording, options: { ticker?: ClockTicker } = {}): RecordedClock {\n const { fromMs, toMs } = recordingBounds(rec);\n return new RecordedClock(toMs, { minMs: fromMs, ticker: options.ticker });\n}\n\n// ---- anchors, resolver and lister -----------------------------------------\n\n// The recording does not carry the anchor a board is bound to; it carries the devices. The\n// host names the anchors (taken from the board definition it plays) whose members are the\n// run's devices. Every device of a Sitepulse run is a member of the one site CUSTOMER, so\n// that anchor expands to ALL of the run's devices. Exactly one anchor may be declared, and\n// it must be a customer: an area or asset anchor would claim every device is in it, which\n// the recording cannot know. An anchor that was not declared is unknown and is refused,\n// never answered with an empty set. Declaring nothing declares no anchor at all.\nexport interface RecordedSiteOptions {\n siteAnchors?: readonly AnchorTarget[];\n}\n\n// A source's options: the site, and an optional time shift (see \"Re-timing\" above).\nexport interface RecordedSourceOptions extends RecordedSiteOptions {\n // Milliseconds added to every instant the source hands out. 0 (the default) shows the\n // run's own times.\n timeShiftMs?: number;\n}\n\n// retimeShiftMs is the shift that makes recording offset `cursorMs` read as `nowMs` (the\n// wall clock by default): the source built with it says the cursor is now.\nexport function retimeShiftMs(rec: BoardRecording, cursorMs: number, nowMs: number = Date.now()): number {\n return Math.round(nowMs - recordedWallTimeMs(rec, cursorMs));\n}\n\nfunction sameAnchor(a: AnchorTarget, b: AnchorTarget): boolean {\n return a.relationship === b.relationship && a.targetType === b.targetType && a.targetToken === b.targetToken;\n}\n\n// The one declared site anchor, or null when none was declared. Anything else is refused\n// when the source, resolver or lister is built, not at some later lookup.\nfunction declaredSite(options: RecordedSiteOptions, rec?: BoardRecording): AnchorTarget | null {\n // The recording names its own anchors, so the host's declaration is not what decides membership.\n if (rec?.anchors) return null;\n const anchors = options.siteAnchors;\n if (anchors === undefined) return null;\n if (anchors.length !== 1) {\n throw new Error(`RecordedSiteOptions.siteAnchors must declare exactly one anchor, got ${anchors.length}`);\n }\n if (anchors[0].targetType !== 'customer') {\n throw new Error(\n `RecordedSiteOptions.siteAnchors must be a customer anchor, got '${anchors[0].targetType}': ` +\n 'a recording cannot say that every device belongs to an area or an asset',\n );\n }\n return anchors[0];\n}\n\n// A recording that carries its own anchors answers from them: an anchor it does not list is unknown, never an empty set. One that does\n// not falls back to the single declared customer anchor, which holds every device.\nfunction recordedMembers(rec: BoardRecording, anchor: AnchorTarget): string[] | null {\n if (!rec.anchors) return null;\n const hit = rec.anchors.find((a) => sameAnchor(a, anchor));\n if (!hit) {\n throw new Error(`anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`);\n }\n return hit.devices;\n}\n\nfunction anchorMembers(rec: BoardRecording, site: AnchorTarget | null, anchor: AnchorTarget): string[] {\n const own = recordedMembers(rec, anchor);\n if (own) return own;\n if (!site || !sameAnchor(site, anchor)) {\n throw new Error(\n `anchor ${anchor.targetType} '${anchor.targetToken}' (${anchor.relationship}) is not in this recording`,\n );\n }\n return rec.devices.map((d) => d.token);\n}\n\n// createRecordedResolver answers the hub's DeviceResolver questions from the recording: a\n// site anchor expands to the run's devices, and a device exists if the run has it. An\n// anchor the host did not declare THROWS (an empty answer would read as \"the site had no\n// machines\").\nexport function createRecordedResolver(rec: BoardRecording, options: RecordedSiteOptions = {}): DeviceResolver {\n const devices = new Set(rec.devices.map((d) => d.token));\n const site = declaredSite(options, rec);\n return {\n devicesForAnchor: async (anchor) => anchorMembers(rec, site, anchor),\n deviceExists: async (token) => devices.has(token),\n };\n}\n\n// createRecordedLister backs a root entity-selector's candidate list from the recording.\n//\n// \uD83D\uDD34 WITHOUT A LISTER THE ENTITY SELECTOR SAYS \"Selection is available on the live\n// dashboard.\" A replay must never say that. The lister is what turns the widget's\n// selection on, so a host that mounts a recording passes this one.\n//\n// Devices are listed by token (the recording carries no display names). The other kinds\n// list the declared site anchors of that type; a kind with none yields an empty list,\n// which is true of the recording.\nexport function createRecordedLister(rec: BoardRecording, options: RecordedSiteOptions = {}): EntityCandidateLister {\n const site = declaredSite(options, rec);\n return async (kind: EntityListKind) => {\n if (kind === 'device') return rec.devices.map((d) => ({ token: d.token, name: null }));\n if (rec.anchors) return rec.anchors.filter((a) => a.targetType === kind).map((a) => ({ token: a.targetToken, name: null }));\n return site && site.targetType === kind ? [{ token: site.targetToken, name: null }] : [];\n };\n}\n\n// ---- the recording, indexed -----------------------------------------------\n\ninterface Series {\n device: string;\n name: string;\n // Row indices into the measurement columns, ascending in time, and the same rows' times.\n rows: number[];\n times: number[];\n}\n\n// One alarm record on the merged history timeline. A snapshot REPLACES the whole alarm set;\n// an event sets one alarm. At equal times a snapshot comes first.\ntype TimelineEntry =\n | { tMs: number; snapshot: RecordedAlarmSnapshot }\n | { tMs: number; event: RecordedAlarmEvent };\n\ninterface Indexed {\n byDevice: Map<string, Series[]>;\n timeline: TimelineEntry[];\n timelineTimes: number[];\n}\n\n// The parts only some boards use (positions, aggregates), fetched on first use: one import\n// site, so the bundle carries one loader for them.\ntype RecordedExtra = typeof import('./recorded-extra');\nconst loadExtra = (): Promise<RecordedExtra> => import('./recorded-extra');\n\n// A view is rebuilt after every seek, so the index is built once per parsed recording.\nconst indexCache = new WeakMap<BoardRecording, Indexed>();\n\nfunction indexOf(rec: BoardRecording): Indexed {\n const cached = indexCache.get(rec);\n if (cached) return cached;\n\n const bySeries = new Map<string, Series>();\n const byDevice = new Map<string, Series[]>();\n const { d, n, t } = rec.measurements;\n for (let i = 0; i < t.length; i++) {\n const key = d[i] * rec.channels.measurements.length + n[i];\n let s = bySeries.get(String(key));\n if (!s) {\n s = { device: rec.devices[d[i]].token, name: rec.channels.measurements[n[i]], rows: [], times: [] };\n bySeries.set(String(key), s);\n const list = byDevice.get(s.device);\n if (list) list.push(s);\n else byDevice.set(s.device, [s]);\n }\n s.rows.push(i);\n s.times.push(t[i]);\n }\n\n const timeline: TimelineEntry[] = [\n ...rec.alarms.snapshots.map((snapshot): TimelineEntry => ({ tMs: snapshot.tMs, snapshot })),\n ...rec.alarms.events.map((event): TimelineEntry => ({ tMs: event.tMs, event })),\n ];\n // Array.prototype.sort is stable: snapshots were listed first, so they stay first at ties.\n timeline.sort((a, b) => a.tMs - b.tMs);\n\n const indexed: Indexed = { byDevice, timeline, timelineTimes: timeline.map((e) => e.tMs) };\n indexCache.set(rec, indexed);\n return indexed;\n}\n\n// An alarm as the fold holds it.\ninterface FoldedAlarm {\n id: string;\n dev: string;\n key: string;\n metric: string;\n state: string;\n sev: string;\n raised: string | null;\n cleared: string | null;\n ack: boolean;\n // The reading that raised it, when the recording holds it.\n val: number | null;\n // The recording time this state is known as of: the event's time, or for an alarm taken\n // from a snapshot, when that snapshot's query was sent.\n asOfMs: number;\n}\n\n// ---- the data source ------------------------------------------------------\n\ninterface Subscription {\n // Called with the new cursor whenever the clock ticks forward.\n advance(cursor: number): void;\n dispose(): void;\n}\n\ninterface DeviceNames {\n deviceToken: string;\n names: Set<string>;\n}\n\nexport class RecordedDataSource implements WidgetDataSource {\n private readonly rec: BoardRecording;\n private readonly site: AnchorTarget | null;\n private readonly index: Indexed;\n private readonly startMs: number;\n private readonly devices: ReadonlySet<string>;\n private readonly kinds: ReadonlyMap<string, string>;\n private readonly subs = new Set<Subscription>();\n private readonly unlisten: () => void;\n private cursor: number;\n private stale = false;\n private readonly shiftMs: number;\n\n // `clock` is the shared replay clock; the view starts at its current time. `options`\n // names the site anchors this recording's devices belong to (see RecordedSiteOptions)\n // and the time shift, if any (see \"Re-timing\" above).\n constructor(rec: BoardRecording, clock: RecordedClock, options: RecordedSourceOptions = {}) {\n this.rec = rec;\n this.site = declaredSite(options, rec);\n this.index = indexOf(rec);\n this.cursor = clock.timeMs;\n const shift = options.timeShiftMs ?? 0;\n if (!Number.isFinite(shift)) throw new RangeError(`RecordedDataSource: timeShiftMs must be finite, got ${shift}`);\n this.shiftMs = shift;\n this.startMs = Date.parse(rec.startedAtUtc);\n this.devices = new Set(rec.devices.map((d) => d.token));\n this.kinds = new Map(rec.devices.map((d) => [d.token, d.kind]));\n this.unlisten = clock.subscribe((ev) => this.onClock(ev));\n }\n\n // True once the clock has been seeked since this view was built. A stale view emits\n // nothing further; the host builds a new view at the new cursor.\n get isStale(): boolean {\n return this.stale;\n }\n\n // The instant the view is at: samples are dated by the run (moved by the shift, if any), so a\n // time span is measured from here, not from the wall clock.\n now(): number {\n return this.startMs + this.cursor + this.shiftMs;\n }\n\n // The shift every instant is moved by (0 for the run's own times).\n get timeShiftMs(): number {\n return this.shiftMs;\n }\n\n private onClock(ev: ClockEvent): void {\n if (this.stale) return;\n if (ev.reason === 'seek') {\n this.stale = true;\n this.unlisten();\n return;\n }\n if (ev.reason !== 'tick') return;\n this.cursor = ev.timeMs;\n for (const sub of [...this.subs]) sub.advance(this.cursor);\n }\n\n private iso(offsetMs: number): string {\n return new Date(this.startMs + offsetMs + this.shiftMs).toISOString();\n }\n\n // A recorded instant (an alarm's raise or clear time), moved by the shift. Unshifted, the\n // recording's own string is kept, digits and all.\n private shifted(instant: string | null): string | null {\n if (instant === null || this.shiftMs === 0) return instant;\n return new Date(Date.parse(instant) + this.shiftMs).toISOString();\n }\n\n // open tracks a subscription and returns its disposer. `start` runs once, in a\n // microtask, so the disposer is in the caller's hands before anything is emitted (the\n // contract every source keeps: a synchronous disposer, and nothing emitted after it).\n // `advance` runs on each forward tick after that.\n private open(handlers: {\n start(cursor: number, isDisposed: () => boolean): void;\n advance(cursor: number, isDisposed: () => boolean): void;\n }): () => void {\n let disposed = false;\n let started = false;\n const isDisposed = (): boolean => disposed;\n const sub: Subscription = {\n advance: (cursor) => {\n if (started && !disposed) handlers.advance(cursor, isDisposed);\n },\n dispose: () => {\n disposed = true;\n this.subs.delete(sub);\n },\n };\n this.subs.add(sub);\n queueMicrotask(() => {\n if (disposed || this.stale) return;\n started = true;\n handlers.start(this.cursor, isDisposed);\n });\n return sub.dispose;\n }\n\n // Resolve a selector to the devices it names, synchronously and from the recording.\n // Mirrors the hub: unbound is zero devices, an unresolved slot is a loud error.\n private resolve(ds: DatasourceSelector): DeviceNames[] {\n switch (ds.kind) {\n case 'device':\n return [{ deviceToken: ds.deviceToken, names: new Set(ds.measurements) }];\n case 'anchor':\n return anchorMembers(this.rec, this.site, ds.anchor).map((deviceToken) => ({\n deviceToken,\n names: new Set(ds.measurements),\n }));\n case 'unbound':\n return [];\n case 'slot':\n throw new Error(\n `dashboard slot '${ds.slot}' reached the data source unresolved; ` +\n 'resolve it through the bindings first (resolveWidgetDatasource)',\n );\n default:\n throw new Error(`dashboard selector kind '${ds.kind}' is not supported yet`);\n }\n }\n\n // Report a failure on a sink, asynchronously and only if the subscription is still live.\n private failLater(err: unknown, sink: { error?: (e: unknown) => void }): () => void {\n let disposed = false;\n queueMicrotask(() => {\n if (!disposed) sink.error?.(err);\n });\n return () => {\n disposed = true;\n };\n }\n\n // \u2500\u2500 measurements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void {\n let groups: DeviceNames[];\n try {\n groups = this.resolve(datasource);\n for (const g of groups) {\n for (const name of g.names) {\n if (!this.rec.channels.measurements.includes(name)) {\n throw new NotInRecordingError('measurements', `no '${name}' series was recorded`);\n }\n }\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // Each selected series keeps its own next-row position, so a tick delivers exactly the\n // rows the cursor has newly passed.\n const picked: Array<{ s: Series; next: number }> = [];\n for (const g of groups) {\n for (const s of this.index.byDevice.get(g.deviceToken) ?? []) {\n if (g.names.size === 0 || g.names.has(s.name)) picked.push({ s, next: 0 });\n }\n }\n\n const { t, v } = this.rec.measurements;\n const flush = (cursor: number, isDisposed: () => boolean): void => {\n const due: Array<{ t: number; sample: MeasurementSample }> = [];\n for (const p of picked) {\n const { s } = p;\n while (p.next < s.times.length && s.times[p.next] <= cursor) {\n const row = s.rows[p.next++];\n due.push({\n t: t[row],\n sample: {\n id: `${s.device}|${s.name}|${row}`,\n deviceToken: s.device,\n eventType: 0,\n // The time the viewer applied the row: the recording holds no separate\n // occurred time for measurements.\n occurredTime: this.iso(t[row]),\n name: s.name,\n value: v[row],\n classifier: null,\n },\n });\n }\n }\n // Chronological across series (stable for equal times), so a widget's window and a\n // \"latest\" card agree on what arrived last.\n due.sort((a, b) => a.t - b.t);\n for (const d of due) {\n if (isDisposed()) return;\n sink.next(d.sample);\n }\n };\n\n return this.open({\n // Back-fill: rows from the preceding window up to the cursor, then play on.\n start: (cursor, isDisposed) => {\n for (const p of picked) p.next = lowerBound(p.s.times, cursor - RECORDED_HISTORY_WINDOW_MS);\n flush(cursor, isDisposed);\n },\n advance: flush,\n });\n }\n\n // \u2500\u2500 fleets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A fleet widget's roster (the run's devices) and then the same back-filled, ticking rows\n // subscribeWidget delivers, each carrying its device.\n //\n // \uD83D\uDD34 A member has NO NAME here, so the widget labels it by its token, exactly as the live board\n // does. A recorded device's `id` is its external id (the scene's binding key, \"SP-HL-0003\"),\n // not a device name: the live roster reads device NAMES, which the sim never sets, and every\n // other surface of the board (a gauge's subject line, the machine selector, an alarm's\n // originator) names a device by its token. Labelling the table's rows by external id made one\n // machine \"SP-HL-0003\" in the table and \"sp-hauler-03\" in the alarm beside it.\n //\n // A member's `deviceType` is its recorded KIND (\"Hauler\"), the key declaredMetrics answers by:\n // a recording names no device-type tokens, and nothing but the declarations reads it.\n subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void {\n let members: Array<{ token: string; name: string | null; deviceType: string }>;\n try {\n members = this.resolve(datasource).map((g) => ({ token: g.deviceToken, name: null, deviceType: this.kinds.get(g.deviceToken) as string }));\n } catch (err) {\n return this.failLater(err, sink);\n }\n let stop: (() => void) | undefined;\n const off = this.open({\n start: () => {\n sink.roster({ members, total: members.length, unbound: datasource.kind === 'unbound' });\n stop = this.subscribeWidget(datasource, sink);\n },\n advance: () => {},\n });\n return () => {\n off();\n stop?.();\n };\n }\n\n // What a kind's profile declares, as the recording carries it (recording.ts, `declaredMetrics`),\n // or null when it does not say: a recording made before it carried declarations, or a kind it\n // has no entry for. Null is \"unknown\", which the fleet widgets read as declared -- never as\n // absent, so an old recording shows \"no data in range\" rather than a confident \"does not report\".\n async declaredMetrics(kind: string): Promise<readonly string[] | null> {\n const declared = this.rec.declaredMetrics;\n return declared && Object.prototype.hasOwnProperty.call(declared, kind) ? declared[kind] : null;\n }\n\n // The same for one device, through its recorded kind.\n async declaredMetricsForDevice(deviceToken: string): Promise<readonly string[] | null> {\n const kind = this.kinds.get(deviceToken);\n return kind === undefined ? null : this.declaredMetrics(kind);\n }\n\n // Each device's seed comes from the recording itself, with no read to make: its newest reading per\n // measurement inside the history window, and every measurement it reported in that window, which\n // is how \"does not report this\" (a dozer's tyres) is told from \"sent nothing\".\n async readMemberSeeds(\n deviceTokens: readonly string[],\n _plan: MemberSeedPlan,\n onSeed: (deviceToken: string, seed: MemberSeed | null) => void,\n ): Promise<void> {\n const { t, v } = this.rec.measurements;\n for (const token of deviceTokens) {\n const seed: MemberSeed = { latest: {}, observed: [] };\n for (const s of this.index.byDevice.get(token) ?? []) {\n const at = upperBound(s.times, this.cursor) - 1;\n if (at < 0 || s.times[at] < this.cursor - RECORDED_HISTORY_WINDOW_MS) continue;\n const row = s.rows[at];\n seed.observed.push(s.name);\n seed.latest[s.name] = {\n id: `${token}|${s.name}|${row}`,\n deviceToken: token,\n eventType: 0,\n occurredTime: this.iso(t[row]),\n name: s.name,\n value: v[row],\n classifier: null,\n };\n }\n onSeed(token, seed);\n }\n }\n\n // \u2500\u2500 alarms \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void {\n let scope: Set<string> | null = null; // null = tenant-wide (no datasource)\n try {\n if (!this.rec.channels.alarms) throw new NotInRecordingError('alarms');\n // A recording carries no device types, so it can neither filter nor count by one. Refused\n // rather than answered with every alarm, or with no type at all, either of which would read\n // as a real answer. (Resolving rows' types, enrichDeviceType, leaves them unresolved: an\n // unknown type is a state a row already has.)\n if (subscription.deviceType !== undefined || subscription.countByDeviceType) {\n throw new NotInRecordingError('alarms', 'device types');\n }\n if (subscription.datasource) {\n scope = new Set(this.resolve(subscription.datasource).map((g) => g.deviceToken));\n }\n } catch (err) {\n return this.failLater(err, sink);\n }\n\n // The fold is monotone because a view only moves forward: apply each history entry\n // once, in order. A snapshot replaces the set; an event sets one alarm.\n const { timeline, timelineTimes } = this.index;\n const alarms = new Map<string, FoldedAlarm>();\n let applied = 0;\n let lastSignature: string | null = null;\n\n const apply = (a: RecordedAlarm, asOfMs: number): void => {\n const prior = alarms.get(a.id);\n alarms.set(a.id, {\n id: a.id,\n dev: a.dev,\n key: a.key,\n metric: a.metric,\n state: a.state,\n sev: a.sev,\n // `occ` is the time of THIS state: the raise time for an ACTIVE row, the clear time\n // for a CLEARED one. A cleared alarm keeps the raise time its ACTIVE row gave it\n // (null if the history never showed it being raised).\n raised: a.state === 'ACTIVE' ? a.occ : (prior?.raised ?? null),\n cleared: a.state === 'CLEARED' ? a.occ : null,\n // Each record's own `ack` decides; acknowledgement is not carried over from an\n // earlier record of the same alarm.\n ack: a.ack === true,\n // The reading that raised the alarm. A cleared alarm keeps its raise's reading, as the\n // platform's row does (a resolve carries no value).\n val: a.val ?? (a.state === 'CLEARED' ? (prior?.val ?? null) : null),\n asOfMs,\n });\n };\n\n const emit = (cursor: number, isDisposed: () => boolean): void => {\n const upTo = upperBound(timelineTimes, cursor);\n for (; applied < upTo; applied++) {\n const entry = timeline[applied];\n if ('snapshot' in entry) {\n // A snapshot is the answer to a query sent at requestedAtMs, so it knows nothing\n // of an alarm that changed after that: those keep the event's (newer) state, even\n // when the snapshot omits or contradicts them. Everything else is replaced.\n const { requestedAtMs } = entry.snapshot;\n const newer = [...alarms.values()].filter((a) => a.asOfMs > requestedAtMs);\n alarms.clear();\n for (const a of entry.snapshot.alarms) apply(a, requestedAtMs);\n for (const a of newer) alarms.set(a.id, a);\n } else {\n apply(entry.event, entry.event.tMs);\n }\n }\n const matches = [...alarms.values()].filter(\n (a) =>\n (scope === null || scope.has(a.dev)) &&\n (!subscription.state || a.state === subscription.state) &&\n (!subscription.severity || a.sev === subscription.severity) &&\n (subscription.acknowledged == null || a.ack === subscription.acknowledged) &&\n // The range is applied before paging, as device-management applies it, so `total`\n // counts the range. Measured against the view's now(), in the view's (shifted) time.\n (!subscription.window ||\n alarmOverlaps({ raisedTime: this.shifted(a.raised), clearedTime: this.shifted(a.cleared) }, subscription.window, this.now())),\n );\n // Newest first by raise time, compared as instants (not as text); the id breaks ties\n // so the order is deterministic.\n const when = (a: FoldedAlarm): string | null => a.raised ?? a.cleared;\n const byTime = (a: FoldedAlarm, b: FoldedAlarm): number => {\n const wa = when(a);\n const wb = when(b);\n if (wa === null || wb === null) return wa === wb ? 0 : wa === null ? 1 : -1;\n return compareInstants(wb, wa);\n };\n matches.sort((a, b) => byTime(a, b) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));\n if (subscription.countBySeverity) {\n // Counted from the same fold the rows come from, so the counts sum to what an alarm count\n // over this recording shows. (A type facet was refused above: the recording has no types.)\n const bySeverity = new Map<string, number>();\n for (const a of matches) bySeverity.set(a.sev, (bySeverity.get(a.sev) ?? 0) + 1);\n const severityCounts = [...bySeverity].map(([severity, count]) => ({ severity, count }));\n const signature = JSON.stringify(severityCounts);\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n sink.next({ alarms: [], total: matches.length, severityCounts });\n return;\n }\n const page = matches.slice(0, subscription.pageSize);\n // Whole snapshots, re-emitted only when the fold actually changed.\n const signature = JSON.stringify([matches.length, page]);\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n const snapshot: AlarmSnapshot = {\n alarms: page.map((a) => toAlarmRow({ ...a, raised: this.shifted(a.raised), cleared: this.shifted(a.cleared) })),\n total: matches.length,\n };\n sink.next(snapshot);\n };\n\n return this.open({ start: emit, advance: emit });\n }\n\n // \u2500\u2500 commands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // The recording holds no command data, so this channel is never available.\n subscribeCommands(_subscription: CommandSubscription, sink: CommandStreamSink): () => void {\n return this.failLater(new NotInRecordingError('commands'), sink);\n }\n\n // \u2500\u2500 locations \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A selector that names no location series asks for nothing, and one that resolves to no\n // device has nothing to place: both get the empty snapshot, as from the hub. Naming a\n // location series for devices that exist, on a recording with no positions, is a request\n // for data the recording does not have, which is an error, never an empty map.\n //\n // With positions recorded, each snapshot is every bound device's LAST position at the\n // cursor, as device-state answers the live map: a device not yet located is absent (the\n // service's contract), and the snapshot is sent again whenever one of them moves on.\n subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void {\n const ds = subscription.datasource;\n let tokens: string[] = [];\n if (ds?.location) {\n try {\n tokens = this.resolve(ds).map((g) => g.deviceToken);\n } catch (err) {\n return this.failLater(err, sink);\n }\n if (tokens.length > 0 && !this.rec.channels.locations) {\n return this.failLater(new NotInRecordingError('locations', 'no positions were recorded'), sink);\n }\n }\n let extra: RecordedExtra | null = null;\n let lastSignature: string | null = null;\n const emit = (cursor: number, isDisposed: () => boolean): void => {\n if (!extra) return;\n const { signature, locations } = extra.lastPositions(this.rec, tokens, cursor, (t) => this.iso(t));\n if (signature === lastSignature || isDisposed()) return;\n lastSignature = signature;\n sink.next({ kind: 'positions', deviceTokens: tokens, locations });\n };\n return this.open({\n start: (_cursor, isDisposed) => {\n // Nothing to place: the empty snapshot, as from the hub, and nothing more to fetch.\n if (tokens.length === 0) {\n if (!isDisposed()) sink.next({ kind: 'positions', deviceTokens: [], locations: [] });\n return;\n }\n loadExtra().then(\n (m) => {\n extra = m;\n if (!this.stale) emit(this.cursor, isDisposed);\n },\n (err: unknown) => {\n if (!isDisposed()) sink.error?.(err);\n },\n );\n },\n advance: emit,\n });\n }\n\n // \u2500\u2500 aggregates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 buckets a stat widget folds into a span's aggregates, computed from the recording's\n // own rows: every (device, measurement) row whose time falls in the window AND at or\n // before the cursor (a replay must not know what happens next), grouped into buckets\n // `intervalSeconds` wide aligned on the epoch like the event store's, each with its exact\n // count, minimum, maximum and sum. Like the store, a bucket that holds no reading is not\n // returned. The window is in the source's own (shifted) time and is moved back by the\n // shift before the rows are read, so a re-timed board aggregates the same rows.\n async bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]> {\n const groups = this.resolve(datasource);\n for (const g of groups) {\n for (const name of g.names) {\n if (!this.rec.channels.measurements.includes(name)) {\n throw new NotInRecordingError('measurements', `no '${name}' series was recorded`);\n }\n }\n }\n const { foldBuckets } = await loadExtra();\n return foldBuckets(this.rec, groups, this.index.byDevice, window, this.startMs + this.shiftMs, this.cursor);\n }\n\n // \u2500\u2500 availability \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n // A device selector is available iff the run has that device. Anchors, unbound slots and\n // no datasource have a legitimate empty state and are available, as on the hub.\n async isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean> {\n if (datasource?.kind === 'device' && datasource.deviceToken) return this.devices.has(datasource.deviceToken);\n return true;\n }\n\n // Stop every subscription and leave the clock. Call when the view is replaced.\n disposeAll(): void {\n this.unlisten();\n for (const sub of [...this.subs]) sub.dispose();\n }\n}\n\nfunction toAlarmRow(a: FoldedAlarm): AlarmRow {\n return {\n token: a.id,\n originatorType: 'device',\n originatorToken: a.dev,\n alarmKey: a.key,\n metricKey: a.metric,\n state: a.state,\n acknowledged: a.ack,\n severity: a.sev,\n raisedTime: a.raised,\n clearedTime: a.cleared,\n // The recording notes THAT an alarm was acknowledged, not when or by whom.\n acknowledgedTime: null,\n acknowledgedBy: null,\n // The reading that raised it, as the recording holds it (recording.ts: `val`). A recording\n // without one plays with none; a nearby measurement would invent the integrator's state.\n // Null renders as an em dash.\n lastValue: a.val,\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The portable half of a widget registration: a plain-data DESCRIPTOR naming what a widget\n// is, which data channel feeds it, what options it owns and how its data is bound. A\n// descriptor holds no React component, callback, import URL, token or executable code \u2014\n// it is JSON-serializable by construction (and checked to be so at runtime, see\n// validateWidgetDescriptor), because the definition parser and the headless validators\n// must be able to read one without loading, or trusting, any widget implementation.\n//\n// The trusted React implementation that goes with a descriptor lives in\n// @devicechain/widget-sdk; this module is the React-free vocabulary both sides share.\n\nimport type {\n AggregateStreamState,\n AlarmStreamState,\n CommandStreamState,\n LocationStreamState,\n MeasurementStreamState,\n} from './channel-snapshots';\nimport type { DefaultsOf, OptionConstraint, OptionKeysOfKind, WidgetOptionSpecs } from './option-schema';\nimport { WIDGET_TYPES, type WidgetType } from './types';\n\n// ---- Channels ---------------------------------------------------------------\n\n// A widget's data channel: which hub subscription binds it. `selection` and `none` open no\n// data channel at all \u2014 a selection widget drives a slot from the ambient candidate\n// provider, and a `none` widget is static.\nexport type WidgetChannel = 'measurement' | 'alarm' | 'control' | 'selection' | 'location' | 'aggregate' | 'none';\n\n// The resolved data a component on channel C receives. `null` marks the empty channel.\nexport type ChannelSnapshot<C extends WidgetChannel> = C extends 'measurement'\n ? MeasurementStreamState\n : C extends 'alarm'\n ? AlarmStreamState\n : C extends 'control'\n ? CommandStreamState\n : C extends 'location'\n ? LocationStreamState\n : C extends 'aggregate'\n ? AggregateStreamState\n : null;\n\n// ---- Ids --------------------------------------------------------------------\n\nexport type BuiltinWidgetId = WidgetType;\n// `scope/name`, e.g. 'acme/tank-level'. Namespace ownership is an operator convention, not\n// proof of publisher identity.\nexport type NamespacedWidgetId = `${string}/${string}`;\nexport type WidgetId = BuiltinWidgetId | NamespacedWidgetId;\n\nconst BUILTIN_IDS: ReadonlySet<string> = new Set(WIDGET_TYPES);\n\nexport function isBuiltinWidgetId(id: unknown): id is BuiltinWidgetId {\n return typeof id === 'string' && BUILTIN_IDS.has(id);\n}\n\nconst NAMESPACED_ID = /^[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9-]*$/;\n\nexport function isNamespacedWidgetId(id: unknown): id is NamespacedWidgetId {\n return typeof id === 'string' && NAMESPACED_ID.test(id);\n}\n\n// ---- Datasource -------------------------------------------------------------\n\nexport type DatasourceSelectorKind = 'device' | 'anchor' | 'slot';\n\n// Whether a widget binds a datasource, and which selector kinds its author may pick.\n// `none`/`selection` widgets must be `{mode:'none'}`; a control widget's command target\n// keeps its device-only requirement, so it offers only device and slot.\nexport type DatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly DatasourceSelectorKind[] };\n\ntype ControlDatasourcePolicy =\n | { mode: 'none' }\n | { mode: 'optional' | 'required'; selectors: readonly ('device' | 'slot')[] };\n\nexport type DatasourcePolicyFor<C extends WidgetChannel> = C extends 'none' | 'selection'\n ? { mode: 'none' }\n : C extends 'control'\n ? ControlDatasourcePolicy\n : DatasourcePolicy;\n\n// ---- Binding policy ---------------------------------------------------------\n\n// A constant, or a reference to a declared option key. References are by NAME and checked\n// against the option's kind \u2014 the descriptor never carries a function or a query string.\nexport interface ConstBinding<T> {\n const: T;\n}\n\nexport interface NumberOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'number'>;\n default?: number;\n min?: number;\n}\n\nexport interface StringOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'string' | 'enum'>;\n default?: string;\n}\n\n// The alarm acknowledged filter is the STRING 'true'/'false' (see the built-in\n// `acknowledged` option), so it references an enum option whose legal values are a subset of\n// those two. The kind is checked at compile time; the value set at registration.\nexport interface AckOptionRef<S extends WidgetOptionSpecs = WidgetOptionSpecs> {\n option: OptionKeysOfKind<S, 'enum'>;\n default?: 'true' | 'false';\n}\n\ntype PageSize<S extends WidgetOptionSpecs> = ConstBinding<number> | NumberOptionRef<S>;\n\nexport type ChannelBindingPolicy<C extends WidgetChannel, S extends WidgetOptionSpecs = WidgetOptionSpecs> =\n C extends 'aggregate'\n ? {\n // The span the aggregates cover: the option pins it (seconds); unset, the board's range.\n windowSeconds?: NumberOptionRef<S>;\n }\n : C extends 'measurement'\n ? {\n window?: NumberOptionRef<S>;\n // Declaring it makes the widget TIME-RANGED: it draws a span rather than a sample count.\n // The referenced option pins the span (seconds); unset, the widget follows the board's\n // time range, which a viewer may change.\n windowSeconds?: NumberOptionRef<S>;\n // Makes the widget a FLEET widget: its datasource is read member by member (one row or bar\n // per device of an anchor) and `data.members` is set. `{ const: true }` always; or an option\n // reference with the value that turns the mode on (a bar chart's \"one bar per device\").\n members?: ConstBinding<true> | (StringOptionRef<S> & { equals: string });\n // Makes the widget a PRESENCE widget: it reads its devices' connectivity (online or offline,\n // and when each was last active) rather than their readings, and `data.members` is set with\n // each member's `presence`. `{ const: true }` only.\n presence?: ConstBinding<true>;\n // Makes the widget a HISTORY widget: it reads one device's stored history in time buckets over\n // its span (never the live feed), and is handed the buckets. `{ const: true }` only.\n history?: ConstBinding<true>;\n }\n : C extends 'alarm'\n ? {\n pageSize: PageSize<S>;\n state?: StringOptionRef<S>;\n severity?: StringOptionRef<S>;\n acknowledged?: AckOptionRef<S>;\n // Makes the widget group its alarms by the originating device's type (a tab strip):\n // the referenced option holding `equals` turns the mode on. `data.typeTabs` is then set.\n groupBy?: StringOptionRef<S> & { equals: string };\n // Makes the widget read the per-severity COUNTS of its alarms instead of alarm rows:\n // `data.severityCounts` is set and `data.alarms` is empty. `{ const: true }` only.\n countBySeverity?: ConstBinding<true>;\n // Only alarms on devices of the type (token) the referenced option holds; unset, all.\n deviceType?: StringOptionRef<S>;\n }\n : C extends 'control'\n ? { pageSize: PageSize<S> }\n : Record<string, never>;\n\n// ---- Interactions -----------------------------------------------------------\n\n// The host callbacks a widget asks for. Declaring one is a request, intersected with what\n// the host supports and the viewer is allowed \u2014 not a security boundary.\nexport interface WidgetInteractionDeclaration {\n alarmActions?: true;\n commandSend?: true;\n selection?: true;\n candidates?: true;\n}\n\n// `alarmActions` only makes sense on the alarm channel and `commandSend` only on control.\n// `'alarm' extends C` (rather than `C extends 'alarm'`) keeps the broad, unparameterized\n// descriptor able to name every key.\nexport type InteractionsFor<C extends WidgetChannel> = { selection?: true; candidates?: true } & ('alarm' extends C\n ? { alarmActions?: true }\n : unknown) &\n ('control' extends C ? { commandSend?: true } : unknown);\n\n// ---- Display ----------------------------------------------------------------\n\nexport interface WidgetDisplay {\n title: string;\n description?: string;\n category?: string;\n // Message ids a host may resolve in its own catalog before falling back to the text above.\n titleId?: string;\n categoryId?: string;\n}\n\n// ---- Size -------------------------------------------------------------------\n\n// A size in grid CELLS: columns across, rows down.\nexport interface WidgetSpan {\n cols: number;\n rows: number;\n}\n\n// Which axes a viewer-author may resize. 'both' is the default.\nexport type WidgetResizeMode = 'both' | 'horizontal' | 'vertical' | 'none';\n\n// How big a widget is allowed to be, declared by the widget and checked by the layout lint.\n// Plain JSON, validated when the catalog is built.\n//\n// Spans are counted in SQUARE cells, never smaller than the board's minCellSize (56 px by\n// default), so a minSpan sized at that cell is readable at every width the layout renders at.\n//\n// \uD83D\uDD11 The move to square cells did NOT bump SDK_API_VERSION, deliberately. The numbers here\n// changed meaning (they were cells of a 24-column grid with 40 px rows, roughly twice as many\n// columns), but the block is advisory size metadata: the editor and the lint read it, and\n// nothing renders from it. A package still declaring the old numbers keeps rendering, and a\n// board placing it outside them is a lint error, which is the right size of failure. A bump would\n// instead turn every stored widget of every extension into an unresolved placeholder, and refuse\n// every descriptor, until each package was re-released: a hard break for a soft change.\nexport interface WidgetLayoutSpec {\n // The size a freshly added widget takes.\n defaultSpan: WidgetSpan;\n // The resize floor. Absent means 1 x 1.\n minSpan?: WidgetSpan;\n // The resize ceiling. Absent means none.\n maxSpan?: WidgetSpan;\n // Absent means 'both'. A restricted mode PINS the other axis at defaultSpan: 'horizontal'\n // pins rows, 'vertical' pins columns, 'none' pins both. A minSpan or maxSpan, if declared,\n // must agree with defaultSpan on a pinned axis; neither is needed to pin it.\n resize?: WidgetResizeMode;\n}\n\n// The axes a resize mode holds at defaultSpan.\nexport function pinnedAxes(mode: WidgetResizeMode | undefined): readonly ('cols' | 'rows')[] {\n switch (mode) {\n case 'horizontal':\n return ['rows'];\n case 'vertical':\n return ['cols'];\n case 'none':\n return ['cols', 'rows'];\n default:\n return [];\n }\n}\n\n// Third-party widgets that declare no `layout` are added at this size, with no limits.\nexport const FALLBACK_DEFAULT_SPAN: Readonly<WidgetSpan> = Object.freeze({ cols: 4, rows: 2 });\n\n// ---- The descriptor ---------------------------------------------------------\n\nexport const SDK_API_VERSION = 1;\n\nexport interface WidgetDescriptor<\n C extends WidgetChannel = WidgetChannel,\n S extends WidgetOptionSpecs = WidgetOptionSpecs,\n> {\n id: WidgetId;\n // Strict MAJOR.MINOR.PATCH of this widget's release. Provenance, not a compatibility\n // key: a differing version still renders.\n version: string;\n // The contract generation, independent of platform and widget versions.\n sdkApiVersion: 1;\n // The persisted settings shape. Bump when stored options stop meaning the same thing.\n optionsVersion: number;\n display: WidgetDisplay;\n channel: C;\n datasource: DatasourcePolicyFor<C>;\n binding: ChannelBindingPolicy<C, S>;\n // The option schema this widget owns.\n options: S;\n // Rules spanning more than one option, as data.\n constraints?: readonly OptionConstraint<OptionKeysOfKind<S, 'number'>>[];\n // Starting values for a freshly added widget. Every key optional \u2014 see DefaultsOf.\n defaults: DefaultsOf<S>;\n interactions: InteractionsFor<C>;\n // Opt in to the frame's age readout (\"12 sec. ago\") for the widget's newest sample, and to\n // the stale treatment when the widget's `staleAfterSeconds` option sets a threshold. Only a\n // measurement widget has samples to age. Optional and absent by default, so a descriptor\n // that predates it, and every stored widget, is unchanged.\n freshness?: true;\n // Size contract (see WidgetLayoutSpec). Required of built-ins by a registry test; optional for\n // a third-party widget, which then gets FALLBACK_DEFAULT_SPAN and no limits.\n layout?: WidgetLayoutSpec;\n}\n\n// defineWidgetDescriptor is an identity that exists for inference: it infers the channel\n// and the schema from the literal, so a binding reference, a constraint or a default that\n// disagrees with the schema is a compile error. The registry-facing helper for a full\n// widget (descriptor + component) is `defineWidget` in @devicechain/widget-sdk.\nexport function defineWidgetDescriptor<const C extends WidgetChannel, const S extends WidgetOptionSpecs>(\n descriptor: WidgetDescriptor<C, S>,\n): WidgetDescriptor<C, S> {\n return descriptor;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// lintLayout: a pure check of a definition's placement against what each widget can show.\n//\n// The parser refuses what cannot be DRAWN (two grid footprints on one cell, a relative widget\n// with no host); this is the question it leaves open: is each placement a USABLE one? A widget\n// past the right edge, a widget smaller or larger than it declares it can be, a relative widget\n// pushed off the canvas. Answered once, here, for every producer of a definition (the sim\n// fixtures today; the editor and the publish gate next).\n//\n// Cells are square and of near-constant size, so a span in cells IS a readable size: a widget\n// that declares a sound minSpan cannot be too small to read at any width the board renders\n// without reflowing. There is therefore no pixel floor.\n//\n// Widget knowledge is INJECTED. `dashboards` must not depend on `widgets`, so the span limits a\n// widget type declares arrive through `spanLimitsOf`. Overlap is deliberately not a code: the\n// parser is where it is refused.\n\nimport {\n BASE_BREAKPOINT,\n breakpointColumns,\n gutterPx,\n layoutBreakpoints,\n layoutRects,\n resolvePlacement,\n spanPx,\n} from './grid';\nimport { sameLabel } from './same-label';\nimport { widgetBinding } from './slots';\nimport type { DashboardDefinition, Placement, WidgetInstance } from './types';\nimport { pinnedAxes, type WidgetLayoutSpec, type WidgetSpan } from './widget-descriptor';\n\nexport type LayoutLintSeverity = 'error' | 'warning' | 'info';\n\nexport type LayoutLintCode =\n | 'span-out-of-limits'\n | 'overrun-columns'\n | 'relative-off-canvas'\n | 'no-layout-declared'\n | 'title-duplicates-subtitle';\n\nexport interface LayoutFinding {\n code: LayoutLintCode;\n severity: LayoutLintSeverity;\n widgetId: string;\n message: string;\n // The breakpoint whose placement the finding is about, when it is not 'base'. An editor needs\n // it to point at the right placement, and two breakpoints' findings must not collapse into one.\n breakpoint?: string;\n}\n\n// The findings that BLOCK a publish: the placement is outside what the widget can show, or off the\n// canvas. A published version is immutable and is what viewers are served, so these are refused\n// there; the draft stays editable. The editor cannot normally produce them, so they catch what\n// arrives some other way (an import, an older descriptor, the API). The rest (`no-layout-declared`,\n// `title-duplicates-subtitle`) are advice.\nexport const PUBLISH_BLOCKING_LAYOUT_CODES: readonly LayoutLintCode[] = [\n 'span-out-of-limits',\n 'overrun-columns',\n 'relative-off-canvas',\n];\n\nexport function blockingLayoutFindings(findings: readonly LayoutFinding[]): LayoutFinding[] {\n return findings.filter((f) => PUBLISH_BLOCKING_LAYOUT_CODES.includes(f.code));\n}\n\n// What a widget type allows, per axis. Any bound may be absent, on either axis: a widget that\n// declares none can be any size, and one that pins only its rows has no column ceiling.\nexport interface SpanLimits {\n minSpan?: Partial<WidgetSpan>;\n maxSpan?: Partial<WidgetSpan>;\n}\n\nexport interface LayoutLintOptions {\n // The column count the `base` placements are checked against: the authored count, or a\n // reflow count. A breakpoint that declares its own count is checked at that count.\n columns: number;\n // undefined means the widget declares no limits at all, which is reported (info) rather\n // than assumed fine, so a type that forgot to declare them is visible.\n spanLimitsOf: (widget: WidgetInstance) => SpanLimits | undefined;\n}\n\n// The limits a descriptor's `layout` block declares, in the shape lintLayout takes: its\n// minSpan and maxSpan, with an axis its resize mode pins held at defaultSpan. A descriptor with\n// no block yields undefined (reported as `no-layout-declared`), so a host wires\n// `spanLimitsOf: (w) => spanLimitsOfLayout(registry.descriptor(w.type)?.layout)`.\nexport function spanLimitsOfLayout(layout: WidgetLayoutSpec | undefined): SpanLimits | undefined {\n if (!layout) return undefined;\n const min: Partial<WidgetSpan> = { ...layout.minSpan };\n const max: Partial<WidgetSpan> = { ...layout.maxSpan };\n for (const axis of pinnedAxes(layout.resize)) {\n min[axis] = layout.defaultSpan[axis];\n max[axis] = layout.defaultSpan[axis];\n }\n const limits: SpanLimits = {};\n if (Object.keys(min).length > 0) limits.minSpan = min;\n if (Object.keys(max).length > 0) limits.maxSpan = max;\n return limits;\n}\n\n// The cell sizes a relative widget's box is measured at. Its position is a straight line in the\n// cell size (an offset is in cell pitches, the gutter is fixed px), so a box inside the canvas at\n// the smallest cell and at a very large one is inside it at every size between: the smallest is\n// where the gutter weighs most, the large one where the cells do.\n// Exported to the editor model (not the package), so fitToLimits repairs against the same cells.\nexport const RELATIVE_PROBE_CELLS = (minCellSize: number) => [minCellSize, minCellSize * 16];\n\n// Rounding slack, in px, for a box that sits exactly on the canvas edge.\nconst EDGE_EPSILON = 0.01;\n\nexport function lintLayout(def: DashboardDefinition, options: LayoutLintOptions): LayoutFinding[] {\n const findings: LayoutFinding[] = [];\n const columnsAt = (bp: string) =>\n bp === BASE_BREAKPOINT || breakpointColumns(def.canvas, bp) === def.canvas.grid.columns\n ? options.columns\n : breakpointColumns(def.canvas, bp);\n const breakpoints = layoutBreakpoints(def.canvas.breakpoints, def.widgets);\n\n for (const widget of def.widgets) {\n const limits = options.spanLimitsOf(widget);\n if (!limits) {\n findings.push({\n code: 'no-layout-declared',\n severity: 'info',\n widgetId: widget.id,\n message: `${widget.type} declares no span limits, so its size cannot be checked`,\n });\n }\n\n for (const [bp, placement] of Object.entries(widget.layout)) {\n const where = bp === BASE_BREAKPOINT ? '' : ` at breakpoint ${bp}`;\n const at = bp === BASE_BREAKPOINT ? {} : { breakpoint: bp };\n const columns = columnsAt(bp);\n if (placement.mode === 'grid' && placement.col + placement.cols > columns) {\n findings.push({\n code: 'overrun-columns',\n severity: 'error',\n widgetId: widget.id,\n ...at,\n message: `spans columns ${placement.col}..${placement.col + placement.cols} of ${columns}${where}`,\n });\n }\n const span = spanOf(def, widget, bp, placement);\n if (limits && span) {\n const problem = spanProblem(span.cols, span.rows, limits);\n if (problem) {\n findings.push({\n code: 'span-out-of-limits',\n severity: 'error',\n widgetId: widget.id,\n ...at,\n message: `${span.cols}x${span.rows} ${problem}${where}`,\n });\n }\n }\n }\n\n const duplicate = titleDuplicatesSubtitle(def, widget);\n if (duplicate) {\n findings.push({\n code: 'title-duplicates-subtitle',\n severity: 'warning',\n widgetId: widget.id,\n message: duplicate,\n });\n }\n }\n\n // A relative widget is checked at every breakpoint it is SHOWN at, its inherited base placement\n // included, wherever that can differ from base: its host (or its host's host) may have its own\n // placement there, or the breakpoint may be narrower. Where nothing differs, base already said it.\n const byId = new Map(def.widgets.map((w) => [w.id, w]));\n const differsFromBase = (w: WidgetInstance, bp: string): boolean => {\n if (columnsAt(bp) !== options.columns) return true;\n for (let cur: WidgetInstance | undefined = w, hops = 0; cur && hops < 3; hops++) {\n if (Object.prototype.hasOwnProperty.call(cur.layout, bp)) return true;\n const p = resolvePlacement(cur.layout, bp);\n cur = p.mode === 'relative' ? byId.get(p.host) : undefined;\n }\n return false;\n };\n for (const bp of breakpoints) {\n const columns = columnsAt(bp);\n const gutter = gutterPx(def.canvas.grid);\n const off = new Set<string>();\n for (const cell of RELATIVE_PROBE_CELLS(def.canvas.grid.minCellSize)) {\n const width = spanPx(columns, cell, gutter.x);\n for (const [id, r] of layoutRects(def.widgets, bp, cell, gutter)) {\n const w = byId.get(id)!;\n if (resolvePlacement(w.layout, bp).mode !== 'relative' || off.has(id)) continue;\n if (bp !== BASE_BREAKPOINT && !differsFromBase(w, bp)) continue;\n if (r.x < -EDGE_EPSILON || r.y < -EDGE_EPSILON || r.x + r.w > width + EDGE_EPSILON) {\n off.add(id);\n const where = bp === BASE_BREAKPOINT ? '' : ` at breakpoint ${bp}`;\n findings.push({\n code: 'relative-off-canvas',\n severity: 'error',\n widgetId: id,\n ...(bp === BASE_BREAKPOINT ? {} : { breakpoint: bp }),\n message: `falls outside the ${columns}-column canvas${where} (left ${round(r.x)}, top ${round(r.y)}, right ${round(r.x + r.w)} of ${round(width)} px at ${round(cell)} px cells)`,\n });\n }\n }\n }\n }\n\n return findings;\n}\n\nfunction round(n: number): number {\n return Math.round(n * 10) / 10;\n}\n\n// The span a placement covers: a grid footprint's own, a relative widget's size, or for a\n// relative widget that fills its host, the host's span (one level of chaining at most).\nfunction spanOf(\n def: DashboardDefinition,\n widget: WidgetInstance,\n bp: string,\n p: Placement,\n): { cols: number; rows: number } | undefined {\n if (p.mode === 'grid') return { cols: p.cols, rows: p.rows };\n if (!('fill' in p.size)) return p.size;\n const host = def.widgets.find((w) => w.id === p.host);\n if (!host || host === widget) return undefined;\n return spanOf(def, host, bp, resolvePlacement(host.layout, bp));\n}\n\nfunction spanProblem(cols: number, rows: number, limits: SpanLimits): string | undefined {\n const { minSpan = {}, maxSpan = {} } = limits;\n const bound = (b: Partial<WidgetSpan>) =>\n b.cols === undefined ? `${b.rows}-row` : b.rows === undefined ? `${b.cols}-column` : `${b.cols}x${b.rows}`;\n if (cols < (minSpan.cols ?? 1) || rows < (minSpan.rows ?? 1)) {\n return `is below the ${bound(minSpan)} minimum`;\n }\n if (cols > (maxSpan.cols ?? Infinity) || rows > (maxSpan.rows ?? Infinity)) {\n return `is above the ${bound(maxSpan)} maximum`;\n }\n return undefined;\n}\n\n// The frame shows the title option and, under it, the token of the entity the widget is\n// bound to. Two ways of putting one identity on screen twice are visible from the\n// definition, and both are reported:\n// - the title IS the token (a renderer can also see this one and suppress the subject line);\n// - the title is the bound SLOT's label, and that slot is bound to the entity the subject\n// line names. A slot label is the author's name for the entity, so the card reads e.g.\n// \"SP-DZ-0001\" over \"sp-dozer-01\": the same machine under two names, which no\n// string compare in the renderer can catch.\n// What it cannot see is a title that names the entity some third way the definition does\n// not record (an external id typed straight into the title with no slot label behind it):\n// that is for whoever builds the board, not for this check.\nfunction titleDuplicatesSubtitle(def: DashboardDefinition, widget: WidgetInstance): string | undefined {\n const title = widget.options?.title;\n if (typeof title !== 'string' || title.trim() === '') return undefined;\n const binding = widgetBinding(def, widget);\n const subject = binding?.kind === 'device' ? binding.deviceToken : binding?.anchor.targetToken;\n if (!subject) return undefined;\n const ds = widget.datasource;\n const slotLabel = ds?.kind === 'slot' ? def.slots?.[ds.slot]?.label : undefined;\n const same = (a: string | undefined) => sameLabel(a, title);\n if (same(subject)) return `the title and the subject line both read \"${title.trim()}\"`;\n if (same(slotLabel)) {\n return `the title \"${title.trim()}\" is the slot's name for \"${subject}\", which the subject line already shows`;\n }\n return undefined;\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, retitle, add) is unit-testable; the editor UI\n// (the console) wires these to its drag/resize and the save mutation. Lives here \u2014\n// next to parse/serialize \u2014 because it is the definition's edit lifecycle, not\n// app-specific glue (ADR-039 authoring in the console).\n//\n// The editor edits the 'base' placements only; per-breakpoint editing is deferred. Every\n// transform keeps the one invariant the parser enforces: no two grid footprints overlap. A move,\n// a resize, an add or a narrower grid that would collide pushes the widgets in the way DOWN\n// (pushDown), so the editor can never produce a definition that fails to load.\n\nimport { BASE_BREAKPOINT, generateWidgetId } from './definition';\nimport {\n breakpointColumns,\n firstFreeSlot,\n gutterPx,\n layoutBreakpoints,\n pushAside,\n pushDown,\n relativeRect,\n resolvePlacement,\n layoutRects,\n spanPx,\n type Gutter,\n type PlacedGrid,\n} from './grid';\nimport { lintLayout, RELATIVE_PROBE_CELLS, type SpanLimits } from './layout-lint';\nimport { DEFAULT_CANVAS_DENSITY } from './types';\nimport type {\n CanvasDensity,\n CanvasGrid,\n CanvasSizing,\n DashboardDefinition,\n GridPlacement,\n Placement,\n PlacementAnchor,\n RelativePlacement,\n ResolvedWidgetInstance,\n WidgetInstance,\n} from './types';\nimport { FALLBACK_DEFAULT_SPAN, type WidgetDescriptor } from './widget-descriptor';\n\n// The base placement a widget is edited by.\nexport function basePlacement(widget: WidgetInstance): Placement {\n return widget.layout[BASE_BREAKPOINT];\n}\n\n// resolveCollisions pushes apart the grid footprints that overlap at one breakpoint, the `first`\n// widgets keeping their place. At a non-base breakpoint a widget that has no entry of its own\n// shows its base placement; if that one has to move, the move is written as an entry for the\n// breakpoint, so the base layout is not disturbed by an edit to another one.\nfunction resolveCollisions(\n def: DashboardDefinition,\n breakpoint: string,\n first: readonly string[],\n aside = false,\n): DashboardDefinition {\n const placed: PlacedGrid[] = [];\n for (const w of def.widgets) {\n const p = resolvePlacement(w.layout, breakpoint);\n if (p.mode === 'grid') placed.push({ id: w.id, placement: p });\n }\n const resolved = aside ? pushAside(placed, first) : pushDown(placed, first);\n let changed = false;\n const widgets = def.widgets.map((w) => {\n const before = resolvePlacement(w.layout, breakpoint);\n const after = resolved.get(w.id);\n if (!after || after === before) return w;\n changed = true;\n return { ...w, layout: { ...w.layout, [breakpoint]: after } };\n });\n return changed ? { ...def, widgets } : def;\n}\n\n// resolveEverywhere resolves collisions at every breakpoint the parser checks, base first. An\n// edit to a base placement changes what every breakpoint without its own entry for that widget\n// shows, so a move that is clear at base can still land on another widget's override at a\n// breakpoint (and the parser refuses an overlap at any of them).\nexport function resolveEverywhere(def: DashboardDefinition, first: readonly string[], aside = false): DashboardDefinition {\n let next = def;\n // The swap is an edit of the base layout: a breakpoint arrangement the edit reaches only through\n // the inherited base placement is pushed apart, never rearranged.\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets))\n next = resolveCollisions(next, bp, first, aside && bp === BASE_BREAKPOINT);\n return next;\n}\n\n// How an edit settles the widgets around the one it changed. Either way the edited widget lands\n// exactly where it was put, only the widgets its new footprint covers move, and every other widget\n// keeps its place, so a gap the author left (to align or to group) is never closed by an\n// unrelated edit. By default a covered widget is pushed DOWN out of the way. With `nearest` it\n// settles at the free row nearest the one it had, up or down (pushAside), so dropping a widget\n// onto a neighbour swaps the two instead of shoving the neighbour further down the board: what an\n// Android launcher does. The editor's pointer and keyboard edits use `nearest`.\nexport interface EditOptions {\n nearest?: boolean;\n}\n\n// setWidgetPlacement replaces a widget's base placement. A grid placement pushes whatever it now\n// covers down out of its way, at base and at every breakpoint that shows it.\nexport function setWidgetPlacement(\n def: DashboardDefinition,\n id: string,\n placement: Placement,\n options: EditOptions = {},\n): DashboardDefinition {\n const next = {\n ...def,\n widgets: def.widgets.map((w) => (w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: placement } } : w)),\n };\n return placement.mode === 'grid' ? resolveEverywhere(next, [id], options.nearest) : next;\n}\n\n// clampSpan holds a span inside a widget's declared limits and inside the columns that remain to\n// the right of `col`. Where the two disagree the grid wins (a widget never leaves the canvas), so\n// a minSpan wider than what is left to the right of `col` yields to the edge: the caller moves\n// `col` back first when it can (see resizeWidget).\nexport function clampSpan(\n span: { cols: number; rows: number },\n limits: SpanLimits | undefined,\n room: number,\n): { cols: number; rows: number } {\n const cols = Math.min(room, Math.max(limits?.minSpan?.cols ?? 1, Math.min(limits?.maxSpan?.cols ?? Infinity, span.cols)));\n const rows = Math.max(limits?.minSpan?.rows ?? 1, Math.min(limits?.maxSpan?.rows ?? Infinity, span.rows));\n return { cols: Math.max(1, Math.round(cols)), rows: Math.max(1, Math.round(rows)) };\n}\n\n// moveWidget drags a grid widget so its top-left cell is (col, row): the span is kept, the\n// footprint is held inside the columns and above row 0, and whatever it now covers is pushed\n// down out of its way (setWidgetPlacement). A relative widget has no cell to drag, so it is\n// returned unchanged.\nexport function moveWidget(\n def: DashboardDefinition,\n id: string,\n to: { col: number; row: number },\n columns: number = def.canvas.grid.columns,\n options: EditOptions = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'grid') return def;\n const col = Math.min(Math.max(0, Math.round(to.col)), Math.max(0, columns - p.cols));\n const row = Math.max(0, Math.round(to.row));\n if (col === p.col && row === p.row) return def;\n return setWidgetPlacement(def, id, { ...p, col, row }, options);\n}\n\n// resizeWidget resizes a grid widget to `target` (a footprint, e.g. a snapped drag of a handle),\n// stopping at the widget's minSpan and maxSpan and at the canvas edge. `fromLeft` / `fromTop`\n// say which edge the handle moved: that edge follows the pointer and the opposite one stays put,\n// so a widget clamped at its minimum does not slide away from the edge it was held by.\n// Neighbours are pushed down.\nexport function resizeWidget(\n def: DashboardDefinition,\n id: string,\n target: { col: number; row: number; cols: number; rows: number },\n limits: SpanLimits | undefined,\n edges: { fromLeft?: boolean; fromTop?: boolean } = {},\n columns: number = def.canvas.grid.columns,\n options: EditOptions = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'grid') return def;\n const right = target.col + target.cols;\n const bottom = target.row + target.rows;\n const room = edges.fromLeft ? Math.max(1, right) : Math.max(1, columns - Math.max(0, target.col));\n const { cols, rows } = clampSpan(target, limits, Math.min(columns, room));\n const col = edges.fromLeft ? Math.max(0, right - cols) : Math.max(0, target.col);\n const row = edges.fromTop ? Math.max(0, bottom - rows) : Math.max(0, target.row);\n if (col === p.col && row === p.row && cols === p.cols && rows === p.rows) return def;\n return setWidgetPlacement(def, id, { mode: 'grid', col, row, cols, rows }, options);\n}\n\n// deleteWidget removes a widget by id. A widget that was placed relative to it would be left\n// with no host, which the parser refuses, so each one is put back on the grid instead, at its\n// own size (a `fill` one at its host's), in the first free slot.\nexport function deleteWidget(def: DashboardDefinition, id: string): DashboardDefinition {\n const gone = def.widgets.find((w) => w.id === id);\n let next: DashboardDefinition = { ...def, widgets: def.widgets.filter((w) => w.id !== id) };\n if (!gone) return next;\n for (const bp of layoutBreakpoints(def.canvas.breakpoints, def.widgets)) {\n const hostPlacement = resolvePlacement(gone.layout, bp);\n const columns = breakpointColumns(def.canvas, bp);\n for (const w of next.widgets) {\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'relative' || p.host !== id) continue;\n const size = 'fill' in p.size ? hostSpan(hostPlacement, next, bp) : p.size;\n const occupied = next.widgets\n .map((o) => resolvePlacement(o.layout, bp))\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const cols = Math.min(size.cols, columns);\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n const grid: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n next = { ...next, widgets: next.widgets.map((o) => (o.id === w.id ? { ...o, layout: { ...o.layout, [bp]: grid } } : o)) };\n }\n }\n // A slot free at one breakpoint can be taken at another that inherits it.\n return resolveEverywhere(next, []);\n}\n\n// The span a (possibly relative) placement covers, for re-homing a `fill` widget.\nexport function hostSpan(p: Placement, def: DashboardDefinition, bp: string): { cols: number; rows: number } {\n if (p.mode === 'grid') return { cols: p.cols, rows: p.rows };\n if (!('fill' in p.size)) return p.size;\n const host = def.widgets.find((w) => w.id === p.host);\n return host ? hostSpan(resolvePlacement(host.layout, bp), def, bp) : { cols: 1, rows: 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 into the canvas, returning a new definition. It\n// owns the grid invariants (a live edit never round-trips through parse): columns floors to an\n// integer >= 1 and minCellSize to >= 1. When columns SHRINKS, every placement in the grid's own\n// column count is clamped back inside it, and anything the clamp makes collide is pushed down.\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.minCellSize !== undefined) grid.minCellSize = Math.max(1, patch.minCellSize);\n let next: DashboardDefinition = { ...def, canvas: { ...def.canvas, grid } };\n if (grid.columns >= def.canvas.grid.columns) return next;\n // Breakpoints that declare their own column count are unaffected.\n const affected = layoutBreakpoints(def.canvas.breakpoints, def.widgets).filter(\n (bp) => breakpointColumns(next.canvas, bp) === grid.columns,\n );\n for (const bp of affected) {\n next = {\n ...next,\n widgets: next.widgets.map((w) => {\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'grid' || p.col + p.cols <= grid.columns) return w;\n const col = Math.min(p.col, grid.columns - 1);\n return { ...w, layout: { ...w.layout, [bp]: { ...p, col, cols: Math.min(p.cols, grid.columns - col) } } };\n }),\n };\n next = resolveCollisions(next, bp, []);\n }\n return next;\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// setCanvasDensity sets the authored density. `comfortable` (the default) is stored as absent so\n// a board nobody densified serializes exactly as before.\nexport function setCanvasDensity(def: DashboardDefinition, density: CanvasDensity): DashboardDefinition {\n const { density: _prev, ...rest } = def.canvas;\n return { ...def, canvas: density === DEFAULT_CANVAS_DENSITY ? rest : { ...rest, density } };\n}\n\n// updateWidget replaces the widget with the matching id, returning a new definition.\nexport function updateWidget(def: DashboardDefinition, id: string, next: WidgetInstance): DashboardDefinition {\n return { ...def, widgets: def.widgets.map((w) => (w.id === id ? next : w)) };\n}\n\n// addWidget appends a new widget of the described kind at the descriptor's default span (no\n// wider than the grid), in the first free slot scanning rows top to bottom, so it never lands on\n// top of another widget. Datasource left undefined. Its starting options are the descriptor's own\n// `defaults` \u2014 the widget's declaration, not a table in the editor keyed by widget id. Returns\n// the new definition and the new widget's id so the caller can select it.\n//\n// The new widget records the registration it was authored against: its release, its settings\n// version and its SDK generation (`widget: { version, optionsVersion, sdkApiVersion }`). For\n// an extension that record is what lets the next load match it to the installed package at\n// all \u2014 a registered extension stored without it is incomplete and parses to a placeholder.\n// `optionsVersion` and `sdkApiVersion` are the compatibility key (with the id), so both are\n// recorded: a reference without the SDK generation could never be refused by a host that has\n// moved to a newer one. `version` is the provenance the editor's version notice compares.\nexport function addWidget(\n def: DashboardDefinition,\n descriptor: Pick<WidgetDescriptor, 'id' | 'defaults' | 'version' | 'optionsVersion' | 'sdkApiVersion' | 'layout'>,\n): { definition: DashboardDefinition; id: string } {\n const id = generateWidgetId();\n const span = descriptor.layout?.defaultSpan ?? FALLBACK_DEFAULT_SPAN;\n const columns = def.canvas.grid.columns;\n const cols = Math.min(span.cols, columns);\n const occupied = def.widgets.map(basePlacement).filter((p): p is GridPlacement => p.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, span.rows, columns);\n const placement: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: span.rows };\n const common = {\n id,\n widget: {\n version: descriptor.version,\n optionsVersion: descriptor.optionsVersion,\n sdkApiVersion: descriptor.sdkApiVersion,\n },\n layout: { [BASE_BREAKPOINT]: placement },\n options: { ...descriptor.defaults },\n };\n // The id came from a registered descriptor, so it names either a built-in or a namespaced\n // extension; which one is the registry's knowledge, and this transform does not branch on it.\n const widget = { ...common, type: descriptor.id } as ResolvedWidgetInstance;\n // The slot is free at base; a breakpoint override elsewhere may cover it, so resolve there too\n // (the new widget keeps its place, and whatever it covers moves).\n return { definition: resolveEverywhere({ ...def, widgets: [...def.widgets, widget] }, [id]), id };\n}\n\n// A pixel rectangle a drag/resize reports.\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: the live (square) cell and the gutters. What the\n// canvas measures so it can map placements to and from the pixel rects its drag/resize works in.\nexport interface GridGeometry {\n cell: number;\n gutter: Gutter;\n}\n\n// gridPlacementToPx renders a grid placement to a pixel rect, relative to the grid's top-left\n// corner. The stride between cell starts is (cell + gutter); a span of N covers N cells and the\n// N-1 gutters between them.\nexport function gridPlacementToPx(p: GridPlacement, geom: GridGeometry): PixelRect {\n return {\n x: p.col * (geom.cell + geom.gutter.x),\n y: p.row * (geom.cell + geom.gutter.y),\n w: spanPx(p.cols, geom.cell, geom.gutter.x),\n h: spanPx(p.rows, geom.cell, geom.gutter.y),\n };\n}\n\n// pxToGridPlacement snaps a pixel rect back to a grid placement. Clamped so a widget can't leave\n// the canvas (col,row >= 0) or vanish (spans >= 1); when `columns` is given, also clamped to the\n// RIGHT edge (col <= columns-1, cols <= columns-col) so a boundary drag can't commit a footprint\n// that overruns the grid.\nexport function pxToGridPlacement(px: PixelRect, geom: GridGeometry, columns?: number): GridPlacement {\n const colStride = Math.max(1, geom.cell + geom.gutter.x);\n const rowStride = Math.max(1, geom.cell + geom.gutter.y);\n let col = Math.max(0, Math.round(px.x / colStride));\n let cols = Math.max(1, Math.round((px.w + geom.gutter.x) / colStride));\n if (columns !== undefined) {\n col = Math.min(col, columns - 1);\n cols = Math.min(cols, columns - col);\n }\n return {\n mode: 'grid',\n col,\n row: Math.max(0, Math.round(px.y / rowStride)),\n cols,\n rows: Math.max(1, Math.round((px.h + geom.gutter.y) / rowStride)),\n };\n}\n\n// ---- Size readout -------------------------------------------------------------------\n\n// formatSpan is the \"2\u00D73\" readout shown while a widget is resized, and the footprint the add-widget\n// picker lists: columns by rows, with the multiplication sign.\nexport function formatSpan(span: { cols: number; rows: number }): string {\n return `${span.cols}\u00D7${span.rows}`;\n}\n\n// ---- Placement mode -----------------------------------------------------------------\n\n// The step a relative widget's offset moves in: whole cells, or quarter cells for fine placement.\nexport const OFFSET_STEP = 1;\nexport const OFFSET_STEP_FINE = 0.25;\n\n// snapOffset rounds an offset (in cell pitches) to the step: whole cells, or quarter cells with\n// `fine` (the Shift key). Never returns -0.\nexport function snapOffset(value: number, fine = false): number {\n const step = fine ? OFFSET_STEP_FINE : OFFSET_STEP;\n const snapped = Math.round(value / step) * step;\n return snapped === 0 ? 0 : snapped;\n}\n\n// What a relative widget's box is measured in, to keep it on the canvas as it is dragged.\nexport interface RelativeBounds {\n cell: number;\n gutter: Gutter;\n columns: number;\n}\n\nfunction replaceBase(def: DashboardDefinition, id: string, placement: Placement): DashboardDefinition {\n return {\n ...def,\n widgets: def.widgets.map((w) => (w.id === id ? { ...w, layout: { ...w.layout, [BASE_BREAKPOINT]: placement } } : w)),\n };\n}\n\n// setRelativeOffset moves a relative widget by setting its offset, snapped to whole cells or, with\n// `fine`, quarter cells. When `bounds` is given the offset is then pulled back, in whole steps, until\n// the box is inside the canvas at that cell size (the editor cannot produce an off-canvas widget by\n// dragging; the lint still checks the extremes of cell size). A grid widget has no offset, so it is\n// returned unchanged. A zero offset is stored as absent.\nexport function setRelativeOffset(\n def: DashboardDefinition,\n id: string,\n offset: { x: number; y: number },\n options: { fine?: boolean; bounds?: RelativeBounds } = {},\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'relative') return def;\n const step = options.fine ? OFFSET_STEP_FINE : OFFSET_STEP;\n let x = snapOffset(offset.x, options.fine);\n let y = snapOffset(offset.y, options.fine);\n const { bounds } = options;\n if (bounds) {\n const host = layoutRects(def.widgets, BASE_BREAKPOINT, bounds.cell, bounds.gutter).get(p.host);\n if (host) {\n const r = relativeRect({ ...p, offset: { x, y } }, host, bounds.cell, bounds.gutter);\n const px = (bounds.cell + bounds.gutter.x) * step;\n const py = (bounds.cell + bounds.gutter.y) * step;\n const width = spanPx(bounds.columns, bounds.cell, bounds.gutter.x);\n if (r.x < 0) x += Math.ceil(-r.x / px) * step;\n else if (r.x + r.w > width) x -= Math.ceil((r.x + r.w - width) / px) * step;\n if (r.y < 0) y += Math.ceil(-r.y / py) * step;\n }\n }\n const next: RelativePlacement = { ...p };\n if (x === 0 && y === 0) delete next.offset;\n else next.offset = { x, y };\n if ((next.offset?.x ?? 0) === (p.offset?.x ?? 0) && (next.offset?.y ?? 0) === (p.offset?.y ?? 0)) return def;\n return replaceBase(def, id, next);\n}\n\n// A widget can host another when it is a grid widget at every breakpoint it is shown at (the\n// parser allows one level of chaining only, and a host that is itself relative could close a\n// chain), and is not the widget being placed.\nfunction canHost(def: DashboardDefinition, host: WidgetInstance | undefined, id: string): host is WidgetInstance {\n if (!host || host.id === id) return false;\n return layoutBreakpoints(def.canvas.breakpoints, def.widgets).every(\n (bp) => resolvePlacement(host.layout, bp).mode === 'grid',\n );\n}\n\n// canHostRelative says whether `hostId` is a valid host for the widget `id` (what placeRelative\n// would accept), so an editor can offer only the widgets that will work.\nexport function canHostRelative(def: DashboardDefinition, id: string, hostId: string): boolean {\n return canHost(def, def.widgets.find((x) => x.id === hostId), id);\n}\n\n// placeRelative puts a widget against a HOST: it keeps its current size, sits at the host's\n// `anchor` point (the same point of itself, so a badge at `top-right` sits in that corner), and\n// leaves the grid, so it no longer pushes anything. Placing an already-relative widget re-homes\n// it (a new host and anchor, the size kept, the offset dropped). A host that is missing, is the\n// widget itself, or is not a grid widget at every breakpoint is refused: the definition comes back\n// unchanged. Only the base placement is edited; a breakpoint with its own entry for the widget\n// keeps it.\nexport function placeRelative(\n def: DashboardDefinition,\n id: string,\n hostId: string,\n anchor: PlacementAnchor,\n): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n if (!canHost(def, def.widgets.find((x) => x.id === hostId), id)) return def;\n const p = basePlacement(w);\n const size = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : p.size;\n return replaceBase(def, id, { mode: 'relative', host: hostId, anchor, size });\n}\n\n// setRelativeAnchor changes the host point a relative widget hangs from, keeping the rest.\nexport function setRelativeAnchor(def: DashboardDefinition, id: string, anchor: PlacementAnchor): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n const p = w && basePlacement(w);\n if (!p || p.mode !== 'relative' || p.anchor === anchor) return def;\n return replaceBase(def, id, { ...p, anchor });\n}\n\n// placeOnGrid is the way back: a relative widget takes its own size (a `fill` one its host's) in\n// the first free slot of the grid, pushing nothing. A grid widget is returned unchanged.\nexport function placeOnGrid(def: DashboardDefinition, id: string): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = basePlacement(w);\n if (p.mode !== 'relative') return def;\n const columns = def.canvas.grid.columns;\n const size = 'fill' in p.size ? hostSpan(p, def, BASE_BREAKPOINT) : p.size;\n const cols = Math.min(size.cols, columns);\n const occupied = def.widgets\n .filter((o) => o.id !== id)\n .map(basePlacement)\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n const grid: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n return resolveEverywhere(replaceBase(def, id, grid), [id]);\n}\n\n// ---- Fit to limits ------------------------------------------------------------------\n\n// fitToLimits is the one-click repair for a board whose widgets are outside what they declare they\n// can be (a converted legacy board, an import, a definition the API wrote), and the way out of\n// every finding that blocks a publish:\n// - every placement, at every breakpoint, has its span clamped into the widget's limits and into\n// the columns, and a footprint that overran the right edge is pulled back inside it;\n// - a relative widget that `fill`s a host its own limits do not allow is given an explicit size,\n// the host's span clamped into those limits;\n// - a relative widget off the canvas is moved back onto it, keeping as much of its offset as the\n// canvas allows, at every cell size the lint measures (fitRelativeOnCanvas). At a breakpoint\n// where it shows its base placement, the fix is written as an entry for that breakpoint, so\n// base is not disturbed;\n// - any collision a grown span causes is then pushed apart through the model. Nothing is pulled\n// up into a gap: a board the author spaced on purpose keeps its spacing, and a widget that\n// shrank leaves the room it gave up.\n// Returns the same object when nothing needed fixing, so a caller can tell \"already fits\" from\n// \"fixed\".\nexport function fitToLimits(\n def: DashboardDefinition,\n spanLimitsOf: (widget: WidgetInstance) => SpanLimits | undefined,\n): DashboardDefinition {\n let changed = false;\n const columnsAt = (bp: string) => breakpointColumns(def.canvas, bp);\n const clamped: DashboardDefinition = {\n ...def,\n widgets: def.widgets.map((w) => {\n const limits = spanLimitsOf(w);\n const layout: Record<string, Placement> = {};\n let touched = false;\n for (const [bp, p] of Object.entries(w.layout)) {\n const columns = columnsAt(bp);\n let q: Placement = p;\n if (p.mode === 'grid') {\n const span = clampSpan(p, limits, columns);\n const col = Math.max(0, Math.min(p.col, columns - span.cols));\n if (span.cols !== p.cols || span.rows !== p.rows || col !== p.col) q = { ...p, ...span, col };\n } else if (!('fill' in p.size)) {\n const size = clampSpan(p.size, limits, columns);\n if (size.cols !== p.size.cols || size.rows !== p.size.rows) q = { ...p, size };\n }\n if (q !== p) touched = true;\n layout[bp] = q;\n }\n if (!touched) return w;\n changed = true;\n return { ...w, layout };\n }),\n };\n // A `fill` widget's span is its host's, read once the hosts are clamped.\n let next: DashboardDefinition = {\n ...clamped,\n widgets: clamped.widgets.map((w) => {\n const limits = spanLimitsOf(w);\n if (!limits) return w;\n let layout: WidgetInstance['layout'] | undefined;\n for (const [bp, p] of Object.entries(w.layout)) {\n if (p.mode !== 'relative' || !('fill' in p.size)) continue;\n const span = hostSpan(p, clamped, bp);\n const size = clampSpan(span, limits, columnsAt(bp));\n if (size.cols === span.cols && size.rows === span.rows) continue;\n layout = { ...(layout ?? w.layout), [bp]: { ...p, size } };\n }\n if (!layout) return w;\n changed = true;\n return { ...w, layout };\n }),\n };\n // A relative widget off the canvas is moved back onto it. Its host may itself be a relative\n // widget being moved, so a second pass sees the host where it ends up.\n for (let pass = 0; pass < 3; pass++) {\n const off = lintLayout(next, { columns: def.canvas.grid.columns, spanLimitsOf }).filter(\n (f) => f.code === 'relative-off-canvas',\n );\n if (off.length === 0) break;\n for (const f of off) {\n const fixed = fitRelativeOnCanvas(next, f.widgetId, f.breakpoint ?? BASE_BREAKPOINT);\n if (fixed !== next) changed = true;\n next = fixed;\n }\n }\n if (!changed) return def;\n return resolveEverywhere(next, []);\n}\n\n// fitRelativeOnCanvas moves the relative widget `id`, as it is shown at `bp`, back inside the\n// canvas at every cell size the lint measures. A box's position is a straight line in the cell\n// size and in its offset, so the offsets that keep it inside are, per axis, a range at each probed\n// cell; the one kept is the widget's own offset clamped into the intersection of those ranges, in\n// quarter cells. When no quarter-cell offset fits every cell size (a box as wide as the canvas\n// whose `self` point differs from its anchor), the widget is put on the grid at that breakpoint,\n// in the first free slot, at its own size: the one placement that is always on the canvas.\nfunction fitRelativeOnCanvas(def: DashboardDefinition, id: string, bp: string): DashboardDefinition {\n const w = def.widgets.find((x) => x.id === id);\n if (!w) return def;\n const p = resolvePlacement(w.layout, bp);\n if (p.mode !== 'relative') return def;\n const columns = breakpointColumns(def.canvas, bp);\n const gutter = gutterPx(def.canvas.grid);\n const { offset, ...bare } = p;\n let loX = -Infinity;\n let hiX = Infinity;\n let loY = -Infinity;\n for (const cell of RELATIVE_PROBE_CELLS(def.canvas.grid.minCellSize)) {\n const host = layoutRects(def.widgets, bp, cell, gutter).get(p.host);\n if (!host) return def;\n const r = relativeRect(bare, host, cell, gutter);\n const width = spanPx(columns, cell, gutter.x);\n loX = Math.max(loX, -r.x / (cell + gutter.x));\n hiX = Math.min(hiX, (width - r.x - r.w) / (cell + gutter.x));\n loY = Math.max(loY, -r.y / (cell + gutter.y));\n }\n const step = OFFSET_STEP_FINE;\n const up = (v: number) => Math.ceil(v / step - 1e-9) * step;\n const down = (v: number) => Math.floor(v / step + 1e-9) * step;\n let placement: Placement;\n if (up(loX) <= down(hiX)) {\n const x = Math.min(Math.max(offset?.x ?? 0, up(loX)), down(hiX)) || 0;\n const y = Math.max(offset?.y ?? 0, up(loY)) || 0;\n placement = x === 0 && y === 0 ? bare : { ...bare, offset: { x, y } };\n } else {\n const size = 'fill' in p.size ? hostSpan(p, def, bp) : p.size;\n const cols = Math.min(size.cols, columns);\n const occupied = def.widgets\n .filter((o) => o.id !== id)\n .map((o) => resolvePlacement(o.layout, bp))\n .filter((o): o is GridPlacement => o.mode === 'grid');\n const slot = firstFreeSlot(occupied, cols, size.rows, columns);\n placement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: size.rows };\n }\n return {\n ...def,\n widgets: def.widgets.map((x) => (x.id === id ? { ...x, layout: { ...x.layout, [bp]: placement } } : x)),\n };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The editor's undo/redo history: a bounded stack of SNAPSHOTS over the pure editor-model\n// transforms. Every state it holds is one the working copy really had, so undo restores a\n// definition exactly (never a recomputed approximation) and can only ever bring back a state that\n// was on screen before, including one the publish gate blocks only if the author really had it.\n//\n// Generic over the state and the label, and free of React and the DOM: the console wires it to\n// its working copy and keyboard. Kept in its own module, outside anything the viewer imports.\n//\n// - `past` holds, newest last, the state BEFORE each edit and the edit's label; `future` holds,\n// newest last, the state an undo left and the label of the edit undone.\n// - An edit committed with a `key` equal to the one still open (the previous commit's) merges\n// into that entry: typing into one field is one step. Undo, redo and `sealHistory` close the\n// key, so the next commit opens a new entry even for the same field.\n// - The depth is bounded; the oldest entries fall off.\n\nexport const DEFAULT_HISTORY_LIMIT = 100;\n\nexport interface HistoryEntry<T, L> {\n state: T;\n label: L;\n}\n\nexport interface EditHistory<T, L> {\n readonly past: readonly HistoryEntry<T, L>[];\n readonly present: T;\n readonly future: readonly HistoryEntry<T, L>[];\n // The coalescing key of the last commit, while it may still be extended.\n readonly open?: string;\n readonly limit: number;\n}\n\nexport function createEditHistory<T, L = string>(present: T, options: { limit?: number } = {}): EditHistory<T, L> {\n return { past: [], present, future: [], limit: Math.max(1, Math.floor(options.limit ?? DEFAULT_HISTORY_LIMIT)) };\n}\n\nexport interface CommitOptions<T> {\n // Commits with the same key, one after another, are one entry.\n key?: string;\n // When two states are the same edit-wise. Defaults to identity.\n equals?: (a: T, b: T) => boolean;\n}\n\n// commitEdit records `next` as the new present. A commit that changes nothing returns the history\n// unchanged; a new edit drops whatever could have been redone.\nexport function commitEdit<T, L>(\n h: EditHistory<T, L>,\n next: T,\n label: L,\n options: CommitOptions<T> = {},\n): EditHistory<T, L> {\n const equals = options.equals ?? Object.is;\n if (next === h.present || equals(next, h.present)) return h;\n const last = h.past[h.past.length - 1];\n if (options.key !== undefined && options.key === h.open && last) {\n // Typed back to where the entry started: the entry is no edit at all. The key closes with it,\n // or the next keystroke would merge into the unrelated entry beneath.\n if (equals(next, last.state)) return { past: h.past.slice(0, -1), present: next, future: [], limit: h.limit };\n return { ...h, present: next, future: [] };\n }\n const past = [...h.past, { state: h.present, label }];\n if (past.length > h.limit) past.splice(0, past.length - h.limit);\n return { past, present: next, future: [], open: options.key, limit: h.limit };\n}\n\n// sealHistory closes the open key: the next commit is a new entry whatever its key.\nexport function sealHistory<T, L>(h: EditHistory<T, L>): EditHistory<T, L> {\n if (h.open === undefined) return h;\n const { open: _closed, ...rest } = h;\n return rest;\n}\n\nexport interface Step<T, L> {\n history: EditHistory<T, L>;\n // The edit that was undone or redone; undefined when there was none.\n label?: L;\n}\n\nexport function undoEdit<T, L>(h: EditHistory<T, L>): Step<T, L> {\n const last = h.past[h.past.length - 1];\n if (!last) return { history: h };\n return {\n history: {\n past: h.past.slice(0, -1),\n present: last.state,\n future: [...h.future, { state: h.present, label: last.label }],\n limit: h.limit,\n },\n label: last.label,\n };\n}\n\nexport function redoEdit<T, L>(h: EditHistory<T, L>): Step<T, L> {\n const next = h.future[h.future.length - 1];\n if (!next) return { history: h };\n return {\n history: {\n past: [...h.past, { state: h.present, label: next.label }],\n present: next.state,\n future: h.future.slice(0, -1),\n limit: h.limit,\n },\n label: next.label,\n };\n}\n\nexport const canUndo = (h: EditHistory<unknown, unknown>): boolean => h.past.length > 0;\nexport const canRedo = (h: EditHistory<unknown, unknown>): boolean => h.future.length > 0;\n\n// rebaseHistory applies a change that is not an edit (a save retiring the \"converted\" mark) to\n// every state the history holds, so neither undo nor redo can bring the old form back.\nexport function rebaseHistory<T, L>(h: EditHistory<T, L>, fn: (state: T) => T): EditHistory<T, L> {\n const map = (e: HistoryEntry<T, L>) => ({ ...e, state: fn(e.state) });\n return { ...h, past: h.past.map(map), present: fn(h.present), future: h.future.map(map) };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Duplicate, copy and paste for the canvas editor: pure transforms over a definition, free of\n// React and the DOM (the console owns the keyboard and the system clipboard). Kept in its own\n// module, outside anything the viewer imports.\n//\n// What a copy carries is the widget as a STORED widget, so a paste reads it back through the same\n// parser a load does, against the pasting console's widget registry: a type that console does not\n// have, or a release it cannot run, is refused and named rather than pasted as something else.\n//\n// Decisions a reader needs:\n// - A pasted or duplicated widget gets a new id and lands at its own span (no wider than the\n// grid) in the first free slot, scanning rows, exactly as an added widget does.\n// - A RELATIVE widget is copied ONTO THE GRID, at its own size (a `fill` one at its host's\n// span). Kept relative, a duplicate would sit exactly on top of the original, invisible, and\n// a paste on another board would name a host that is not there. The author re-hosts it with\n// one click. Only the base placement is copied; per-breakpoint entries are left behind.\n// - A slot binding is KEPT when the target board has the slot: on the same board, a slot of that\n// name and kind; on another board, a slot of that name, kind AND default binding (slot names\n// are minted `slot-N` per board, so the same name on two boards usually means two different\n// devices, and following the name would silently show the wrong one). Otherwise the widget is\n// pasted UNBOUND: it points at a new slot of the same kind with nothing bound to it, and the\n// paste reports it, so the editor can say why. Never dropped to \"no datasource\": to an alarm\n// widget an absent datasource means the whole tenant; an unbound slot means no device yet.\n// - \uD83D\uDD34 A copy is stamped with the TENANT it was made in, and a paste into any other tenant keeps\n// NO entity reference at all. Device and entity tokens are only unique within a tenant, so a\n// binding carried across would quietly show the same-named device of the other tenant (or\n// publish the first tenant's identifiers on a board the second tenant's users can read). One\n// browser tab can change tenant, and its clipboard outlives the change, so this is decided at\n// paste time, from the stamp, never assumed. Every slot reference, and every concrete device or\n// entity selector, becomes a new unbound slot with no label (a slot's label is often the token\n// it was bound to), and the board stamp is ignored: board tokens are per tenant too.\n\nimport { CURRENT_SCHEMA_VERSION } from './types';\nimport { BASE_BREAKPOINT, generateWidgetId, parseDashboardDefinition, storedDefinition, type ParseDefinitionOptions } from './definition';\nimport { basePlacement, hostSpan, resolveEverywhere } from './editor-model';\nimport { firstFreeSlot } from './grid';\nimport { nextSlotName, sameBinding } from './slots';\nimport {\n UNKNOWN_WIDGET_TYPE,\n type Canvas,\n type DashboardDefinition,\n type GridPlacement,\n type ResolvedWidgetInstance,\n type SlotDefinition,\n type WidgetInstance,\n} from './types';\n\nexport const WIDGET_CLIPBOARD_FORMAT = 'devicechain/dashboard-widgets';\nexport const WIDGET_CLIPBOARD_VERSION = 1;\n\n// Where a copy was made, or where a paste lands: the tenant and the board token within it.\nexport interface ClipboardOrigin {\n tenant: string;\n board: string;\n}\n\n// What a copy holds: the widgets (on the grid, ids as they were), the slots they reference as the\n// source board defined them, the tenant and board they came from and the canvas they were authored on.\nexport interface WidgetClipboard {\n tenant: string;\n board: string;\n canvas: Canvas;\n widgets: ResolvedWidgetInstance[];\n slots: Record<string, SlotDefinition>;\n}\n\n// A widget a paste could not take: its type and why.\nexport interface RefusedWidget {\n type: string;\n reason: string;\n}\n\n// A pasted widget that lost its binding: it now points at `slot`, a new slot with nothing bound\n// to it, because the board has no slot `from` (`missing`), has one bound to something else, or\n// of the other kind (`different`), or the copy was made in another tenant (`tenant`: `from` is\n// then the source slot's name, or empty for a widget that named its device directly).\nexport interface UnboundPaste {\n widgetId: string;\n slot: string;\n from: string;\n reason: 'missing' | 'different' | 'tenant';\n}\n\nexport interface PasteResult {\n definition: DashboardDefinition;\n ids: string[];\n unbound: UnboundPaste[];\n}\n\n// The slot names a widget references: the one its datasource binds, and the one a selector widget\n// re-points (`options.selectionTarget`).\nfunction slotReferences(w: WidgetInstance): string[] {\n const out: string[] = [];\n if (w.datasource?.kind === 'slot' && w.datasource.slot) out.push(w.datasource.slot);\n const target = w.options?.selectionTarget;\n if (typeof target === 'string' && target !== '' && !out.includes(target)) out.push(target);\n return out;\n}\n\nconst own = <V>(map: Record<string, V> | undefined, key: string): V | undefined =>\n map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : undefined;\n\n// copyWidget takes widget `id` of `def` (on `origin`'s board) into a clipboard. A placeholder for a\n// widget this console cannot show is not copied: it carries no settings this build can vouch for.\nexport function copyWidget(def: DashboardDefinition, id: string, origin: ClipboardOrigin): WidgetClipboard | undefined {\n const w = def.widgets.find((x) => x.id === id);\n if (!w || w.type === UNKNOWN_WIDGET_TYPE) return undefined;\n const p = basePlacement(w);\n const size = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : 'fill' in p.size ? hostSpan(p, def, BASE_BREAKPOINT) : p.size;\n const placement: GridPlacement = { mode: 'grid', col: 0, row: 0, ...size };\n const widget = structuredClone({ ...w, layout: { [BASE_BREAKPOINT]: placement } }) as ResolvedWidgetInstance;\n const slots: Record<string, SlotDefinition> = {};\n for (const name of slotReferences(w)) {\n const slot = own(def.slots, name);\n if (slot) slots[name] = structuredClone(slot);\n }\n return { tenant: origin.tenant, board: origin.board, canvas: structuredClone(def.canvas), widgets: [widget], slots };\n}\n\n// serializeWidgetClipboard is the text a copy puts on the system clipboard.\nexport function serializeWidgetClipboard(clip: WidgetClipboard): string {\n const definition = storedDefinition({\n schemaVersion: CURRENT_SCHEMA_VERSION,\n title: '',\n canvas: clip.canvas,\n widgets: clip.widgets,\n ...(Object.keys(clip.slots).length > 0 ? { slots: clip.slots } : {}),\n });\n return JSON.stringify({\n format: WIDGET_CLIPBOARD_FORMAT,\n version: WIDGET_CLIPBOARD_VERSION,\n tenant: clip.tenant,\n board: clip.board,\n definition,\n });\n}\n\n// parseWidgetClipboard reads clipboard text back, validating every widget against the registry\n// `options.catalog` names (the built-ins without one). Text that is not a widget clipboard, of\n// another version, or whose definition does not parse, is undefined: the caller leaves the paste\n// alone, and so is text with no tenant stamp: a paste must know where a copy came from. A widget\n// the registry cannot resolve is refused and named.\n//\n// \uD83D\uDD34 One exception to \"undefined\": a copy made by a NEWER release (its definition's schemaVersion\n// above this build's) THROWS the parser's DashboardDefinitionError, code 'schemaTooNew', exactly as\n// a load of that board would. It is a widget clipboard, so \"nothing to paste\" would be a lie, and\n// reading it as the current format would paste it drawn wrong; the caller says why it refused.\nexport function parseWidgetClipboard(\n text: string,\n options: ParseDefinitionOptions = {},\n): { clipboard: WidgetClipboard; refused: RefusedWidget[] } | undefined {\n let env: unknown;\n try {\n env = JSON.parse(text);\n } catch {\n return undefined;\n }\n if (typeof env !== 'object' || env === null) return undefined;\n const { format, version, tenant, board, definition } = env as Record<string, unknown>;\n if (format !== WIDGET_CLIPBOARD_FORMAT || version !== WIDGET_CLIPBOARD_VERSION) return undefined;\n if (typeof tenant !== 'string' || tenant === '' || typeof board !== 'string') return undefined;\n let parsed: DashboardDefinition;\n try {\n parsed = parseDashboardDefinition(definition, options);\n } catch (err) {\n // By `code`, not instanceof: it holds across two bundled copies of this package.\n if ((err as { code?: unknown } | null)?.code === 'schemaTooNew') throw err;\n return undefined;\n }\n const widgets: ResolvedWidgetInstance[] = [];\n const refused: RefusedWidget[] = [];\n for (const w of parsed.widgets) {\n if (w.type !== UNKNOWN_WIDGET_TYPE) {\n widgets.push(w);\n continue;\n }\n const type = typeof w.raw.type === 'string' ? w.raw.type : '';\n refused.push({ type, reason: w.reason?.message ?? '' });\n }\n return { clipboard: { tenant, board, canvas: parsed.canvas, widgets, slots: parsed.slots ?? {} }, refused };\n}\n\n// A selector that names its entity directly rather than through a slot.\nconst CONCRETE_KINDS: ReadonlySet<string> = new Set(['device', 'anchor', 'devices', 'relatedTraversal']);\n\n// pasteWidgets adds the clipboard's widgets to `def`, the board `at`: each with a new id, in the\n// first free slot at its span, its slot references kept or unbound as described above.\nexport function pasteWidgets(def: DashboardDefinition, clip: WidgetClipboard, at: ClipboardOrigin): PasteResult {\n // An empty stamp is never a match: a paste that cannot tell where a copy came from treats it as\n // another tenant's.\n const sameTenant = clip.tenant !== '' && clip.tenant === at.tenant;\n const sameBoard = sameTenant && clip.board === at.board;\n const columns = def.canvas.grid.columns;\n const slots: Record<string, SlotDefinition> = { ...(def.slots ?? {}) };\n // One decision per source slot, so widgets pasted together that shared a slot still share one.\n const mapped = new Map<string, { slot: string; reason?: UnboundPaste['reason'] }>();\n const mapSlot = (from: string) => {\n const known = mapped.get(from);\n if (known) return known;\n const source = own(clip.slots, from);\n const target = own(slots, from);\n const keep =\n sameTenant &&\n target !== undefined &&\n source !== undefined &&\n target.type === source.type &&\n (sameBoard || sameBinding(target.defaultBinding, source.defaultBinding));\n let result: { slot: string; reason?: UnboundPaste['reason'] };\n if (keep) result = { slot: from };\n else {\n const slot = nextSlotName(slots);\n // Another tenant's label is not carried: it is often the token the slot was bound to.\n const label = sameTenant ? source?.label : undefined;\n slots[slot] = { type: source?.type ?? 'device', ...(label !== undefined ? { label } : {}) };\n result = { slot, reason: !sameTenant ? 'tenant' : target ? 'different' : 'missing' };\n }\n mapped.set(from, result);\n return result;\n };\n\n const occupied = def.widgets.map(basePlacement).filter((p): p is GridPlacement => p.mode === 'grid');\n const added: WidgetInstance[] = [];\n const ids: string[] = [];\n const unbound: UnboundPaste[] = [];\n for (const source of clip.widgets) {\n const id = generateWidgetId();\n const p = basePlacement(source);\n const span = p.mode === 'grid' ? { cols: p.cols, rows: p.rows } : 'fill' in p.size ? { cols: 1, rows: 1 } : p.size;\n const cols = Math.min(span.cols, columns);\n const slot = firstFreeSlot(occupied, cols, span.rows, columns);\n const placement: GridPlacement = { mode: 'grid', col: slot.col, row: slot.row, cols, rows: span.rows };\n occupied.push(placement);\n const w = structuredClone(source) as ResolvedWidgetInstance;\n w.id = id;\n w.layout = { [BASE_BREAKPOINT]: placement };\n for (const from of slotReferences(source)) {\n const m = mapSlot(from);\n if (m.reason) unbound.push({ widgetId: id, slot: m.slot, from, reason: m.reason });\n if (m.slot === from) continue;\n if (w.datasource?.kind === 'slot' && w.datasource.slot === from) w.datasource = { ...w.datasource, slot: m.slot };\n if (w.options?.selectionTarget === from) w.options = { ...w.options, selectionTarget: m.slot };\n }\n // A widget from another tenant that names its device or entity directly keeps the series it\n // reads and loses the entity: it points at a new unbound slot of the matching kind.\n const ds = w.datasource;\n if (!sameTenant && ds && CONCRETE_KINDS.has(ds.kind) && ds.kind !== 'slot' && ds.kind !== 'unbound') {\n const slot = nextSlotName(slots);\n slots[slot] = { type: ds.kind === 'anchor' ? 'anchor' : 'device' };\n w.datasource = {\n kind: 'slot',\n slot,\n measurements: [...ds.measurements],\n ...(ds.location ? { location: { ...ds.location } } : {}),\n };\n unbound.push({ widgetId: id, slot, from: '', reason: 'tenant' });\n }\n added.push(w);\n ids.push(id);\n }\n const next: DashboardDefinition = { ...def, widgets: [...def.widgets, ...added] };\n if (Object.keys(slots).length > 0) next.slots = slots;\n return { definition: resolveEverywhere(next, ids), ids, unbound };\n}\n\n// duplicateWidget pastes a copy of widget `id` onto its own board. Undefined for a widget that\n// cannot be copied (missing, or a placeholder).\nexport function duplicateWidget(\n def: DashboardDefinition,\n id: string,\n): { definition: DashboardDefinition; id: string; unbound: UnboundPaste[] } | undefined {\n // The widget's own board, in its own tenant.\n const here: ClipboardOrigin = { tenant: 'here', board: 'here' };\n const clip = copyWidget(def, id, here);\n if (!clip) return undefined;\n const r = pasteWidgets(def, clip, here);\n return { definition: r.definition, id: r.ids[0], unbound: r.unbound };\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 a CUSTOMER anchor reads customerDevices(customer, relationship); an area\n// or asset anchor reads anchorDevices(targetType, target), each row being a\n// member device (the Hub opens one measurementStream per token, per ADR-044).\n//\n// \uD83D\uDD11 ONE PATH PER ANCHOR KIND, WHOEVER IS VIEWING. The relationship graph is closed to a member\n// limited to some customers (a customer user), so an anchor read through it showed that member\n// nothing. customerDevices and anchorDevices answer that member about the devices it can see, and\n// for every other viewer they answer exactly what the relationship search does, so there is no\n// viewer-dependent branch here beyond one: anchorDevices refuses a relationship type from a scoped\n// caller, which readAreaOrAssetPage retries without. An anchor the platform still declines\n// (FORBIDDEN, reason SCOPE_UNSUPPORTED) is reported by the hub as an unsupported binding\n// (isDatasourceSupported) rather than as empty.\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 { decodeToken, gql, isScoped, isScopeUnsupportedError, resolveAuthToken } from '@devicechain/client';\n\nimport type { DeviceResolver } from './hub';\nimport type { MemberRef } from './members';\nimport {\n ANCHOR_DEVICES,\n CUSTOMER_DEVICES,\n DECLARED_METRICS,\n DEVICES_BY_TOKEN,\n DEVICE_NAMES,\n type AnchorDevicesResult,\n type CustomerDevicesResult,\n type DeclaredMetricsResult,\n type DeviceNamesResult,\n type DevicesByTokenResult,\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.\nexport const ANCHOR_PAGE_SIZE = 500;\n\n// One page of an anchor's members: their tokens, the anchor's true size, their names and their\n// device type tokens (both anchor reads carry them, so the roster needs no second read).\ninterface AnchorPage {\n tokens: string[];\n total: number;\n names: Map<string, string | null>;\n types: Map<string, string | null>;\n}\n\n// The most device types one declaration read asks for.\nconst DECLARED_BATCH_SIZE = 100;\n\n// A server that reports no total is taken at its word: the page is the fleet.\nconst trueSize = (reported: number | null | undefined, page: number) => Math.max(reported ?? page, page);\n\n// Whether the viewer is a scoped member, learned once per resolver: from the access token's `scp`\n// claim (the server enforces the scope regardless; this only stops the client asking what it knows\n// will be refused), and from a refusal if the token could not say. Never widens anything.\ninterface ViewerScope {\n scoped: boolean | null;\n}\n\nasync function viewerIsScoped(viewer: ViewerScope): Promise<boolean> {\n if (viewer.scoped !== null) return viewer.scoped;\n let token: string | null = null;\n try {\n token = await resolveAuthToken();\n } catch {\n token = null;\n }\n // No token to read says nothing: stay unknown (a refusal will teach it) rather than caching false.\n if (!token) return false;\n viewer.scoped = isScoped(decodeToken(token));\n return viewer.scoped;\n}\n\nfunction readAnchorPage(anchor: AnchorTarget, viewer: ViewerScope): Promise<AnchorPage> {\n if (anchor.targetType === 'customer') {\n return gql('device-management', CUSTOMER_DEVICES, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n customer: anchor.targetToken,\n relationshipType: anchor.relationship,\n },\n }).then((r: CustomerDevicesResult) => {\n const rows = r.customerDevices.results;\n return {\n tokens: rows.map((d) => d.token),\n total: trueSize(r.customerDevices.pagination?.totalRecords, rows.length),\n names: new Map(rows.map((d) => [d.token, d.name])),\n types: new Map(rows.map((d) => [d.token, d.deviceType?.token ?? null])),\n };\n });\n }\n return readAreaOrAssetPage(anchor, viewer);\n}\n\n// An area or an asset: anchorDevices, which answers every viewer. The relationship type is sent\n// first, so an unscoped viewer gets exactly the devices linked by that type, as the relationship\n// search it replaces did. A member limited to some customers is refused the argument (it would let\n// them probe which edge types link their devices) and is answered the tracked ownership edges\n// instead, so that refusal is retried once without it. A second SCOPE_UNSUPPORTED is the platform\n// declining the anchor outright and surfaces as it always has. A viewer known to be scoped (from the\n// token, or from an earlier refusal) is never sent the argument at all, so the supported path makes\n// no refused call.\nasync function readAreaOrAssetPage(anchor: AnchorTarget, viewer: ViewerScope): Promise<AnchorPage> {\n const read = (relationshipType?: string) =>\n gql('device-management', ANCHOR_DEVICES, {\n criteria: {\n pageNumber: 1,\n pageSize: ANCHOR_PAGE_SIZE,\n targetType: anchor.targetType,\n target: anchor.targetToken,\n relationshipType,\n },\n });\n const first = (await viewerIsScoped(viewer)) ? read() : read(anchor.relationship);\n return first\n .catch((err: unknown) => {\n if (!isScopeUnsupportedError(err) || viewer.scoped === true) throw err;\n viewer.scoped = true;\n return read();\n })\n .then((r: AnchorDevicesResult) => {\n const rows = r.anchorDevices.results;\n return {\n tokens: rows.map((d) => d.token),\n total: trueSize(r.anchorDevices.pagination?.totalRecords, rows.length),\n names: new Map(rows.map((d) => [d.token, d.name])),\n types: new Map(rows.map((d) => [d.token, d.deviceType?.token ?? null])),\n };\n });\n}\n\nexport function createDeviceResolver(): DeviceResolver {\n const viewer: ViewerScope = { scoped: null };\n const anchorCache = new Map<string, Promise<AnchorPage>>();\n const memberCache = new Map<string, Promise<{ members: MemberRef[]; total: number }>>();\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 const typeCache = new Map<string, Promise<string | null>>();\n\n // The device types asked for declarations in the current tick, and who is waiting on each.\n type DeclaredWaiter = { resolve: (names: string[] | null) => void; reject: (err: unknown) => void };\n let declaredBatch: Map<string, DeclaredWaiter[]> | null = null;\n\n function anchorKey(anchor: AnchorTarget): string {\n return `${anchor.relationship}|${anchor.targetType}|${anchor.targetToken}`;\n }\n\n // One page of the anchor's members and the anchor's true size, memoized per anchor.\n function anchorPage(anchor: AnchorTarget): Promise<AnchorPage> {\n const key = anchorKey(anchor);\n let pending = anchorCache.get(key);\n if (!pending) {\n pending = readAnchorPage(anchor, viewer)\n .catch((err) => {\n anchorCache.delete(key);\n throw err;\n });\n anchorCache.set(key, pending);\n }\n return pending;\n }\n\n return {\n devicesForAnchor(anchor: AnchorTarget): Promise<string[]> {\n return anchorPage(anchor).then((page) => page.tokens);\n },\n\n // The members of an anchor with their names and the anchor's true size: ONE page, shared with\n // devicesForAnchor, which carries every member's name and device type.\n anchorMembers(anchor: AnchorTarget): Promise<{ members: MemberRef[]; total: number }> {\n const key = anchorKey(anchor);\n let pending = memberCache.get(key);\n if (!pending) {\n pending = anchorPage(anchor)\n .then(({ tokens, total, names, types }) => ({\n members: tokens.map((token) => ({ token, name: names.get(token) ?? null, deviceType: types.get(token) ?? null })),\n total,\n }))\n .catch((err) => {\n memberCache.delete(key);\n throw err;\n });\n memberCache.set(key, pending);\n }\n return pending;\n },\n\n // The names a device type's active profile version declares, or null when it has nothing to\n // say: no profile, never published, not found, or a published version that declares no metric\n // at all (an undeclared measurement is still accepted, so an empty declaration rules nothing\n // out). A failed read rejects: the caller treats that as unknown too, and does not cache it.\n // Every type asked in the same tick goes in ONE read, so a board of many types costs one\n // request rather than one per type.\n declaredMetrics(deviceType: string): Promise<string[] | null> {\n return new Promise((resolve, reject) => {\n let batch = declaredBatch;\n if (!batch) {\n batch = declaredBatch = new Map();\n queueMicrotask(() => {\n const asked = declaredBatch!;\n declaredBatch = null;\n const tokens = [...asked.keys()];\n for (let i = 0; i < tokens.length; i += DECLARED_BATCH_SIZE) {\n const chunk = tokens.slice(i, i + DECLARED_BATCH_SIZE);\n gql('device-management', DECLARED_METRICS, { tokens: chunk }).then(\n (r: DeclaredMetricsResult) => {\n for (const token of chunk) {\n const version = r.deviceTypesByToken.find((t) => t.token === token)?.profile?.activeProfileVersion;\n const names = version && version.metrics.length > 0 ? version.metrics.map((m) => m.metricKey) : null;\n for (const w of asked.get(token)!) w.resolve(names);\n }\n },\n (err: unknown) => {\n for (const token of chunk) for (const w of asked.get(token)!) w.reject(err);\n },\n );\n }\n });\n }\n const waiting = batch.get(deviceType) ?? [];\n waiting.push({ resolve, reject });\n batch.set(deviceType, waiting);\n });\n },\n\n // The device type's token, from the same names read the roster uses. A failed read rejects (the\n // caller treats that as unknown) and is not cached.\n deviceType(deviceToken: string): Promise<string | null> {\n let pending = typeCache.get(deviceToken);\n if (!pending) {\n pending = gql('device-management', DEVICE_NAMES, { tokens: [deviceToken] })\n .then((r: DeviceNamesResult) => r.devicesByToken.find((d) => d.token === deviceToken)?.deviceType?.token ?? null)\n .catch((err) => {\n typeCache.delete(deviceToken);\n throw err;\n });\n typeCache.set(deviceToken, 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 { DEFAULT_TIME_RANGE_SECONDS, bucketSecondsFor, rangeWindow } from './time-range';\nimport type { MeasurementSample, WidgetInstance } from './types';\n\nexport interface HistoryWindow {\n startTime: string;\n endTime: string;\n intervalSeconds: number;\n}\n\n// historyWindowFor is the seed window for a span: `spanSeconds` back from `end` (epoch ms),\n// bucketed at the width bucketSecondsFor picks so the read stays within MAX_SEED_BUCKETS per\n// measurement whatever the span (7 days is 240 buckets, not 10 080 minutes). `end` is the\n// moment the board is frozen at while paused, otherwise now.\nexport function historyWindowFor(spanSeconds: number, end: number): HistoryWindow {\n const { startTime, endTime } = rangeWindow({ spanSeconds, paused: false }, end);\n return { startTime, endTime, intervalSeconds: bucketSecondsFor(spanSeconds) };\n}\n\n// The default backfill: the last hour bucketed per minute (~60 points), enough to\n// give a chart shape on load without a heavy query. What a widget that is not time-ranged\n// (a card, a gauge: they need the latest value, not a span) is seeded with.\nexport function defaultHistoryWindow(): HistoryWindow {\n return historyWindowFor(DEFAULT_TIME_RANGE_SECONDS, Date.now());\n}\n\n// fetchWidgetHistory returns seed samples for one widget, or [] when it has no\n// device datasource (label/image, an anchor selector, an unbound slot). Never rejects\n// into the render path \u2014 a failed backfill just yields an empty seed and the live stream\n// still fills the widget.\n//\n// It takes the widget with its CONCRETE datasource \u2014 a slot already resolved through the\n// bindings (resolveWidgetDatasource), as the renderer passes it \u2014 so the seed is for\n// exactly the device the widget's live stream is for. A raw slot seeds nothing.\n//\n// `rethrow` makes a failed read reject instead. A caller REPLACING a seed it already shows (the\n// re-read after a live feed was interrupted) must not mistake a failure for \"no history\": an empty\n// answer would wipe the history on screen and claim the outage held no readings.\nexport async function fetchWidgetHistory(\n widget: WidgetInstance,\n window: HistoryWindow,\n options: { rethrow?: boolean } = {},\n): Promise<MeasurementSample[]> {\n const ds = widget.datasource;\n if (!ds || ds.kind !== 'device') return [];\n\n try {\n // measurementStream and bucketedMeasurements are keyed by device token (ADR-044),\n // so the token goes straight into the criteria \u2014 no token\u2192id hop.\n const deviceToken = ds.deviceToken;\n\n // Seed each requested measurement (or all, when the widget lists none).\n const names: Array<string | undefined> = ds.measurements.length ? ds.measurements : [undefined];\n const pages = await Promise.all(\n names.map((name) =>\n gql('event-management', BUCKETED_MEASUREMENTS, {\n criteria: {\n deviceToken,\n name,\n startTime: window.startTime,\n endTime: window.endTime,\n intervalSeconds: window.intervalSeconds,\n },\n }).then((r) => r.bucketedMeasurements),\n ),\n );\n\n return pages\n .flat()\n .filter((b) => b.avg != null)\n .map((b) => ({\n id: `${deviceToken}-${b.name}-${b.bucketStart}`,\n deviceToken,\n eventType: 0,\n occurredTime: b.bucketStart,\n name: b.name,\n value: b.avg,\n classifier: null,\n bucketSeconds: window.intervalSeconds,\n }))\n .sort((a, b) => (a.occurredTime! < b.occurredTime! ? -1 : 1));\n } catch (err) {\n if (options.rethrow) throw err;\n return [];\n }\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The option-schema vocabulary and the engine that checks an options bag against one.\n//\n// A widget's stored `options` is `Record<string, unknown>`: dashboard-management keeps a\n// definition as opaque JSON and the definition parser does not look inside it. That leaves\n// every option key owned by NOTHING unless something declares it, and this module is the\n// language the declaration is written in. It is React-free and knows no widget type, so a\n// headless validator, the widget SDK and the built-in widget package all read one engine \u2014\n// the built-in table (WIDGET_OPTIONS in @devicechain/widgets) is just one schema among the\n// ones a registry can hold.\n//\n// The schema is JSON-serializable data. Rules that span more than one option are therefore\n// declared as `constraints` (data), never as functions.\n\n// ---- Spec vocabulary --------------------------------------------------------\n\n// `required` marks an option whose ABSENCE leaves the widget with nothing to show.\n// The renderer never throws on a missing option \u2014 an image with no `url` renders \"No\n// image URL\", a command-button with no `commandName` renders \"No command configured\" \u2014\n// which is deliberate graceful degradation for a hand-edited definition, but is exactly\n// the failure a generated dashboard must not ship. So `required` is an AUTHORING\n// completeness contract, not a claim that the renderer rejects the option's absence.\n//\n// Absent here means absent AS THE RENDERER SEES IT, which for a required string includes\n// the EMPTY string: every one of the four required built-in options is read into a\n// truthiness test (`url ? \u2026 : 'No image URL'`, `!commandName`, `!target`), so `''`\n// produces the same placeholder as an omitted key. Treating `''` as present would let a\n// generated dashboard carrying `\"url\": \"\"` validate clean and render nothing.\ninterface OptionSpecBase {\n // What the option does. Short: this feeds the config panel's help text and the\n // published \"available widgets\" page. Required, and present on every descriptor a package\n // publishes. The one exception is not a type: @devicechain/widgets's render-only definitions\n // (`builtinWidgetDefinitionsWithoutDocs`) omit it so a host that only renders boards never\n // downloads prose it never shows, which is why the validator excuses a BUILT-IN id's missing doc.\n doc: string;\n required?: true;\n // Portable default text for the option's label and help. A host that cannot translate\n // an option it has never heard of falls back to these, so a third-party option is\n // understandable without a host message catalog. Optional so existing schemas stay valid.\n label?: string;\n help?: string;\n // Message ids a host MAY look up in its own catalog before falling back to the text\n // above. Built-ins declare today's console keys here; the host resolves them.\n labelId?: string;\n helpId?: string;\n // Names a SPECIALIZED authoring workflow for this option, chosen by the descriptor and never\n // inferred from the option's key. Absent means the generic field the spec kind implies.\n //\n // OPEN vocabulary, deliberately: any non-empty string registers, and a host that does not\n // implement the named workflow renders the generic field for that option. A closed list\n // here would make every package that names a newer control unregistrable on an older\n // host. Name a package's own controls in a namespace ('acme/valve-curve'); the built-in\n // names below are unprefixed.\n control?: string;\n}\n\n// The specialized workflows the DeviceChain console implements \u2014 what a built-in may name\n// and expect honoured. Not a validation whitelist: a control outside this list is valid and\n// simply falls back. `command-vocabulary` picks a device command and bakes its key, label and\n// parameters together; `slot-target` picks one of the dashboard's slots; `basemap` marks the\n// raster tile source/credit pair; `device-type` picks a device type from the tenant's catalogue and\n// stores its token (on a string option, or a string field of a list item).\nexport const HOST_OPTION_CONTROLS = ['command-vocabulary', 'slot-target', 'basemap', 'device-type'] as const;\nexport type HostOptionControl = (typeof HOST_OPTION_CONTROLS)[number];\n\nexport interface StringOptionSpec extends OptionSpecBase {\n kind: 'string';\n // 'json' additionally requires the string to parse as JSON. Used for the command\n // button's baked parameterSchema, where a malformed schema is silently swallowed\n // (parseParameterSchema returns []) and renders as a bare Send button.\n format?: 'json';\n}\n\nexport interface NumberOptionSpec extends OptionSpecBase {\n kind: 'number';\n min?: number;\n max?: number;\n integer?: true;\n}\n\nexport interface BooleanOptionSpec extends OptionSpecBase {\n kind: 'boolean';\n}\n\nexport interface EnumOptionSpec<V extends string = string> extends OptionSpecBase {\n kind: 'enum';\n values: readonly V[];\n // Portable text and optional host message ids for the choices and for the \"unset\" entry,\n // by the same lookup-then-fall-back rule as `label`. Absent, a choice shows its raw value.\n valueLabels?: Readonly<Record<string, string>>;\n valueLabelIds?: Readonly<Record<string, string>>;\n placeholder?: string;\n placeholderId?: string;\n}\n\n// The scalar kinds a list item's fields may take. Not `list` itself: nesting is not offered,\n// so every option stays authorable by one flat editor row.\nexport type ListItemFieldSpec = StringOptionSpec | NumberOptionSpec | BooleanOptionSpec | EnumOptionSpec;\n\n// An ordered list of small records, e.g. thresholds: `[{ value, level, label? }]`. Each item is\n// an object whose fields are declared once in `item`; a field marked `required` must be present\n// in every item, and a key the item does not declare is refused (a free `color` next to a\n// severity `level` is exactly what this exists to turn away).\nexport interface ListOptionSpec extends OptionSpecBase {\n kind: 'list';\n item: Readonly<Record<string, ListItemFieldSpec>>;\n maxItems?: number;\n // The ORDER of the entries means something (tab order, say), so an editor offers move up /\n // move down on each. Absent or false, a list is a set the editor need not let the author reorder.\n ordered?: boolean;\n}\n\nexport type OptionSpec = StringOptionSpec | NumberOptionSpec | BooleanOptionSpec | EnumOptionSpec | ListOptionSpec;\n\nexport type WidgetOptionSpecs = Record<string, OptionSpec>;\n\n// A rule spanning more than one option, as DATA. `greater-than` requires the value of\n// `key` to be strictly greater than the value of `than`, and is only judged when both are\n// finite numbers (an absent or mistyped value is reported by its own spec, not twice).\nexport interface GreaterThanConstraint<K extends string = string> {\n kind: 'greater-than';\n key: K;\n than: K;\n}\n\nexport type OptionConstraint<K extends string = string> = GreaterThanConstraint<K>;\n\n// ---- Derived compile-time types ---------------------------------------------\n\n// The value type a spec accepts. Enum first: an EnumOptionSpec's values carry the literal\n// union, which is the whole point of declaring one.\nexport type SpecValue<S> = S extends { kind: 'list'; item: infer I }\n ? OptionsOf<I>[]\n : S extends { kind: 'enum'; values: readonly (infer V)[] }\n ? V extends string\n ? V\n : never\n : S extends { kind: 'number' }\n ? number\n : S extends { kind: 'boolean' }\n ? boolean\n : S extends { kind: 'string' }\n ? string\n : never;\n\nexport type RequiredOptionKeys<S> = {\n [K in keyof S]: S[K] extends { required: true } ? K : never;\n}[keyof S];\n\n// The typed options bag a schema describes: every declared key at its declared type,\n// required ones mandatory.\nexport type OptionsOf<S> = {\n [K in RequiredOptionKeys<S>]: SpecValue<S[K]>;\n} & {\n [K in Exclude<keyof S, RequiredOptionKeys<S>>]?: SpecValue<S[K]>;\n};\n\n// A descriptor's starting values: every declared key at its declared type, but EVERY key\n// optional. A fresh widget is allowed to be authoring-incomplete \u2014 an image has no `url`\n// until the author supplies one \u2014 so `required` cannot bind the defaults.\nexport type DefaultsOf<S> = { [K in keyof S]?: SpecValue<S[K]> };\n\n// The declared keys of a given kind, for typed references from a binding policy. The `infer`\n// makes the test distribute over a union of specs, so the broad WidgetOptionSpecs (every\n// key a union of the four kinds) still names its number keys instead of collapsing to never.\nexport type OptionKeysOfKind<S, Kind extends OptionSpec['kind']> = {\n [K in keyof S & string]: S[K] extends infer X ? (X extends { kind: Kind } ? K : never) : never;\n}[keyof S & string];\n\n// ---- Runtime validation -----------------------------------------------------\n\nexport type OptionIssueCode =\n // a key the renderer for this widget type reads nothing from\n | 'unknown'\n // present, but the wrong JavaScript type \u2014 read back as undefined, rendered as default\n | 'type'\n // a string/number outside the option's declared legal values or bounds\n | 'enum'\n | 'range'\n // a `required` option absent, so the widget renders its \"not configured\" placeholder\n | 'missing'\n // a format:'json' string that does not parse\n | 'json'\n // a rule spanning more than one option (see OptionConstraint)\n | 'invariant';\n\nexport interface OptionIssue {\n // The id of the widget whose schema the bag was checked against (a built-in type or a\n // namespaced extension id). NOT a widget instance id.\n widgetId: string;\n // The offending option key. For a constraint, the key the fix belongs on.\n key: string;\n code: OptionIssueCode;\n message: string;\n}\n\nexport interface ValidateOptionsContext {\n constraints?: readonly OptionConstraint[];\n // Names the widget in messages and on each issue.\n label: string;\n}\n\n// hasOwnProperty.call rather than `in` or Object.hasOwn: every specs table inherits\n// Object.prototype, so `in` reports 'toString', 'constructor' and friends as declared.\n// A stored definition arrives via JSON.parse, which makes '__proto__' an ordinary own key\n// \u2014 a shape a real definition can carry. (And the package targets ES2020.)\nexport function hasOwn(obj: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(obj, key);\n}\n\n// validateOptions reports every way an options bag disagrees with its schema. An empty\n// result means every key present is declared, at a value the schema accepts, and no\n// required option is missing.\n//\n// It is deliberately a REPORT rather than a throw: the renderer is tolerant by design and\n// must stay that way for a hand-edited definition, so the strictness belongs to whoever\n// produces definitions (fixtures, generators, the config panel) and to the tests that\n// gate them. Callers decide which codes are fatal \u2014 a stored dashboard carrying an\n// 'unknown' leftover is untidy; a generated one carrying 'missing' is broken.\nexport function validateOptions(\n specs: WidgetOptionSpecs,\n options: Record<string, unknown> | undefined,\n context: ValidateOptionsContext,\n): OptionIssue[] {\n const { label: widgetId } = context;\n const issues: OptionIssue[] = [];\n const bag = options ?? {};\n\n for (const [key, spec] of Object.entries(specs)) {\n const value = bag[key];\n // '' counts as absent for a required option \u2014 see OptionSpecBase.\n if (value === undefined || (spec.required && value === '')) {\n if (spec.required) {\n issues.push({ widgetId, key, code: 'missing', message: `${key} is required but not set` });\n }\n continue;\n }\n issues.push(...checkValue(widgetId, key, spec, value));\n }\n\n for (const key of Object.keys(bag)) {\n if (hasOwn(specs, key)) continue;\n issues.push({\n widgetId,\n key,\n code: 'unknown',\n message: `${key} is not read by a ${widgetId} widget`,\n });\n }\n\n for (const constraint of context.constraints ?? []) {\n const issue = checkConstraint(constraint, bag);\n if (issue) issues.push({ widgetId, ...issue });\n }\n\n return issues;\n}\n\nfunction checkConstraint(\n constraint: OptionConstraint,\n bag: Record<string, unknown>,\n): Pick<OptionIssue, 'key' | 'code' | 'message'> | undefined {\n switch (constraint.kind) {\n case 'greater-than': {\n const value = bag[constraint.key];\n const than = bag[constraint.than];\n if (typeof value !== 'number' || typeof than !== 'number') return undefined;\n if (!Number.isFinite(value) || !Number.isFinite(than)) return undefined;\n if (value > than) return undefined;\n return {\n key: constraint.key,\n code: 'invariant',\n message: `${constraint.key} (${value}) must be greater than ${constraint.than} (${than})`,\n };\n }\n }\n}\n\nfunction checkValue(widgetId: string, key: string, spec: OptionSpec, value: unknown): OptionIssue[] {\n const issue = (code: OptionIssueCode, message: string): OptionIssue[] => [{ widgetId, key, code, message }];\n\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (spec.format === 'json' && !parsesAsJson(value)) {\n return issue('json', `${key} must be valid JSON`);\n }\n return [];\n\n case 'number':\n // Number.isFinite mirrors optNumber: NaN and \u00B1Infinity survive JSON round-trips\n // through some producers and are read back as undefined by the renderer.\n if (typeof value !== 'number' || !Number.isFinite(value)) {\n return issue('type', `${key} must be a finite number, got ${describe(value)}`);\n }\n if (spec.integer && !Number.isInteger(value)) {\n return issue('range', `${key} must be a whole number, got ${value}`);\n }\n if (spec.min !== undefined && value < spec.min) {\n return issue('range', `${key} must be at least ${spec.min}, got ${value}`);\n }\n if (spec.max !== undefined && value > spec.max) {\n return issue('range', `${key} must be at most ${spec.max}, got ${value}`);\n }\n return [];\n\n case 'boolean':\n // optBoolean treats anything but literal `true` as false, so a wrong type here is a\n // silently-disabled feature rather than a visible error.\n if (typeof value !== 'boolean') {\n return issue('type', `${key} must be a boolean, got ${describe(value)}`);\n }\n return [];\n\n case 'enum':\n if (typeof value !== 'string') {\n return issue('type', `${key} must be a string, got ${describe(value)}`);\n }\n if (!spec.values.includes(value)) {\n return issue('enum', `${key} must be one of ${spec.values.join(', ')}, got ${JSON.stringify(value)}`);\n }\n return [];\n\n case 'list': {\n if (!Array.isArray(value)) return issue('type', `${key} must be a list, got ${describe(value)}`);\n if (spec.maxItems !== undefined && value.length > spec.maxItems) {\n return issue('range', `${key} holds at most ${spec.maxItems} entries, got ${value.length}`);\n }\n const out: OptionIssue[] = [];\n value.forEach((entry, i) => {\n const at = `${key}[${i}]`;\n // Reported under the ENTRY's own key (`thresholds[0].level`), so the author is pointed at the cell.\n const at_ = (path: string, code: OptionIssueCode, message: string): OptionIssue => ({ widgetId, key: path, code, message });\n if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {\n out.push(at_(at, 'type', `${at} must be an object, got ${describe(entry)}`));\n return;\n }\n const record = entry as Record<string, unknown>;\n for (const [field, fieldSpec] of Object.entries(spec.item)) {\n const fieldValue = record[field];\n if (fieldValue === undefined || (fieldSpec.required && fieldValue === '')) {\n if (fieldSpec.required) out.push(at_(`${at}.${field}`, 'missing', `${at}.${field} is required but not set`));\n continue;\n }\n out.push(...checkValue(widgetId, `${at}.${field}`, fieldSpec, fieldValue));\n }\n for (const field of Object.keys(record)) {\n if (!hasOwn(spec.item, field)) {\n out.push(at_(`${at}.${field}`, 'unknown', `${at}.${field} is not a field of ${key} entries`));\n }\n }\n });\n return out;\n }\n }\n}\n\nfunction parsesAsJson(value: string): boolean {\n try {\n JSON.parse(value);\n return true;\n } catch {\n return false;\n }\n}\n\n// describe names the offending value's type in a message, quoting a string so an empty\n// one is visible and distinguishing null from an object.\nfunction describe(value: unknown): string {\n if (value === null) return 'null';\n if (typeof value === 'string') return `string ${JSON.stringify(value)}`;\n if (Array.isArray(value)) return 'an array';\n return `${typeof value} ${JSON.stringify(value)}`;\n}\n\n// numberSpec returns the declared bounds for a numeric option, so an authoring UI can\n// constrain its input from the schema rather than from a second, hand-written copy of the\n// same numbers.\n//\n// This exists because the console's numeric inputs were unbounded: a `precision` of\n// -1 is a legal number, reaches Number.prototype.toFixed, and throws a RangeError\n// DURING RENDER. formatValue clamps so the renderer survives, but the definition is\n// still wrong and the author had no way to know. A panel that reads the schema cannot\n// offer the value at all.\nexport function numberSpec(specs: WidgetOptionSpecs, key: string): NumberOptionSpec | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'number' ? spec : undefined;\n}\n\n// clampNumberOption folds a raw number into what a numeric option's schema allows, or\n// returns undefined when it is not a number at all.\n//\n// Separate from the input that calls it because the correction is the part worth\n// testing: an authoring UI's keystroke handling is awkward to exercise, and the\n// question that actually matters \u2014 what value gets STORED for a given input \u2014 is a\n// pure function of the schema and the number.\nexport function clampNumberOption(spec: NumberOptionSpec | undefined, value: number): number | undefined {\n if (!Number.isFinite(value)) return undefined;\n if (!spec) return value;\n let next = spec.integer ? Math.trunc(value) : value;\n if (spec.min !== undefined) next = Math.max(spec.min, next);\n if (spec.max !== undefined) next = Math.min(spec.max, next);\n return next;\n}\n\n// enumValues returns the legal values of an enum option, so an authoring UI can build its\n// dropdown from the schema instead of a private copy of the same list.\n//\n// A dropdown offering a value the schema rejects lets a panel author a definition the\n// validator refuses; one MISSING a value silently removes a filter an author could\n// previously express. Both are invisible without a shared source.\nexport function enumValues(specs: WidgetOptionSpecs, key: string): readonly string[] | undefined {\n if (!hasOwn(specs, key)) return undefined;\n const spec = specs[key];\n return spec.kind === 'enum' ? spec.values : undefined;\n}\n\n// applyOptionPatch is the VALIDATED write a registered editor is given: it returns the\n// options bag with `patch` applied, or throws on a write the schema cannot hold.\n//\n// An editor is developer code running in the host page, so a mistake in it should be loud at\n// the moment it is made, not a half-configured widget discovered at publish. The rules:\n// - a key the schema does not declare is refused (an editor cannot grow the stored bag);\n// - a value of the wrong kind, an enum value outside `values`, or a `format: 'json'`\n// string that does not parse, is refused;\n// - a number is folded into its bounds, as the generic field does;\n// - `undefined`, an empty string, and `false` DROP the key (an unset option reads as its\n// default, and an opt-in flag is absent-when-off), as the generic fields do.\n// Keys the patch does not name \u2014 including ones outside the schema that an older release\n// stored \u2014 are carried through untouched.\nexport function applyOptionPatch(\n specs: WidgetOptionSpecs,\n options: Readonly<Record<string, unknown>> | undefined,\n patch: Readonly<Record<string, unknown>>,\n label = 'widget',\n): Record<string, unknown> {\n const next: Record<string, unknown> = { ...(options ?? {}) };\n for (const [key, value] of Object.entries(patch)) {\n if (!hasOwn(specs, key)) throw new Error(`${label}: \"${key}\" is not a declared option`);\n const spec = specs[key];\n if (\n value === undefined ||\n value === '' ||\n (spec.kind === 'boolean' && value === false) ||\n (spec.kind === 'list' && Array.isArray(value) && value.length === 0)\n ) {\n delete next[key];\n continue;\n }\n const refuse = (expected: string): never => {\n throw new TypeError(`${label}: option \"${key}\" takes ${expected}, got ${JSON.stringify(value)}`);\n };\n switch (spec.kind) {\n case 'string':\n if (typeof value !== 'string') refuse('a string');\n // A `format: 'json'` option is judged as the option validator judges it: a string\n // that does not parse is refused here rather than flagged later at publish.\n if (spec.format === 'json' && !parsesAsJson(value as string)) refuse('a JSON string');\n next[key] = value;\n break;\n case 'boolean':\n if (typeof value !== 'boolean') refuse('a boolean');\n next[key] = value;\n break;\n case 'number': {\n if (typeof value !== 'number') refuse('a finite number');\n const clamped = clampNumberOption(spec, value as number);\n if (clamped === undefined) refuse('a finite number');\n next[key] = clamped;\n break;\n }\n case 'enum':\n if (typeof value !== 'string' || !spec.values.includes(value)) {\n refuse(`one of ${spec.values.map((v) => JSON.stringify(v)).join(', ')}`);\n }\n next[key] = value;\n break;\n case 'list': {\n // Judged exactly as the validator judges a stored list: a wrong-typed field, a missing\n // required one, or a key the entries do not declare is refused here, not found at publish.\n const bad = checkValue(label, key, spec, value);\n if (bad.length > 0) refuse(`a list of ${key} entries (${bad[0].message})`);\n next[key] = value;\n break;\n }\n }\n }\n return next;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The React-free catalog of widget descriptors: what the definition parser and the\n// headless validators see of a widget registry. Constructing one validates EVERY\n// descriptor and the set as a whole, reports every problem together, and returns a deeply\n// frozen, independent instance \u2014 no module state, no global registration, so two hosts on\n// one page can hold two catalogs that do not affect each other.\n\nimport { validateOptions, hasOwn, type OptionIssue, type WidgetOptionSpecs } from './option-schema';\nimport {\n SDK_API_VERSION,\n isBuiltinWidgetId,\n isNamespacedWidgetId,\n pinnedAxes,\n type WidgetChannel,\n type WidgetResizeMode,\n type WidgetDescriptor,\n} from './widget-descriptor';\nimport { isRecord } from './is-record';\n\nexport interface DescriptorIssue {\n // The widget id the issue is about, or `#<index>` when the descriptor has no usable id.\n id: string;\n // Where in the descriptor, e.g. `defaults.max` or `binding.window.option`. Empty for an\n // issue about the descriptor as a whole.\n path: string;\n message: string;\n}\n\nexport interface WidgetCatalog {\n has(id: string): boolean;\n get(id: string): WidgetDescriptor | undefined;\n // Descriptors in the order they were supplied.\n list(): readonly WidgetDescriptor[];\n}\n\nexport class WidgetCatalogError extends Error {\n readonly issues: readonly DescriptorIssue[];\n\n constructor(issues: readonly DescriptorIssue[], subject = 'widget catalog') {\n super(describeIssues(subject, issues));\n this.name = 'WidgetCatalogError';\n this.issues = Object.freeze([...issues]);\n }\n}\n\nexport function describeIssues(subject: string, issues: readonly DescriptorIssue[]): string {\n const lines = issues.map((i) => ` - ${i.id}${i.path ? `.${i.path}` : ''}: ${i.message}`);\n return `invalid ${subject} (${issues.length} problem${issues.length === 1 ? '' : 's'}):\\n${lines.join('\\n')}`;\n}\n\n// ---- Descriptor validation --------------------------------------------------\n\nconst CHANNELS: readonly WidgetChannel[] = ['measurement', 'alarm', 'control', 'selection', 'location', 'aggregate', 'none'];\nconst SEMVER = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)$/;\nconst SELECTOR_KINDS = ['device', 'anchor', 'slot'];\n\n// The keys each channel's binding policy may carry, mirroring ChannelBindingPolicy.\nconst BINDING_KEYS: Record<WidgetChannel, readonly string[]> = {\n measurement: ['window', 'windowSeconds', 'members', 'presence', 'history'],\n alarm: ['pageSize', 'state', 'severity', 'acknowledged', 'groupBy', 'countBySeverity', 'deviceType'],\n control: ['pageSize'],\n location: [],\n aggregate: ['windowSeconds'],\n selection: [],\n none: [],\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!isRecord(value)) return false;\n const proto = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\nconst join = (base: string, key: string): string => (base ? `${base}.${key}` : key);\n\n// validateWidgetDescriptor checks one descriptor at runtime and returns every problem it\n// finds \u2014 the same invariants the types express, because JSON from a package manifest or a\n// JS caller bypasses the compiler. Accepts `unknown` for that reason.\nexport function validateWidgetDescriptor(descriptor: unknown): DescriptorIssue[] {\n const rawId = isRecord(descriptor) && typeof descriptor.id === 'string' ? descriptor.id : '';\n return collectDescriptorIssues(descriptor, rawId || '#?');\n}\n\nfunction collectDescriptorIssues(d: unknown, id: string): DescriptorIssue[] {\n const issues: DescriptorIssue[] = [];\n const add = (path: string, message: string): void => {\n issues.push({ id, path, message });\n };\n\n if (!isPlainObject(d)) {\n add('', 'a descriptor must be a plain object');\n return issues;\n }\n\n jsonOnly(d, '', add, new Set());\n\n // id \u2014 shape and the reserved rule (a trusted-code convention, not an enforcement).\n if (typeof d.id !== 'string' || d.id === '') {\n add('id', 'id must be a non-empty string');\n } else if (!isBuiltinWidgetId(d.id) && !isNamespacedWidgetId(d.id)) {\n add('id', `id ${JSON.stringify(d.id)} must be a reserved built-in id or a namespaced \"scope/name\" id`);\n }\n\n if (typeof d.version !== 'string' || !SEMVER.test(d.version)) {\n add('version', `version must be MAJOR.MINOR.PATCH, got ${JSON.stringify(d.version)}`);\n }\n if (d.sdkApiVersion !== SDK_API_VERSION) {\n add('sdkApiVersion', `unsupported sdkApiVersion ${JSON.stringify(d.sdkApiVersion)}; this build supports ${SDK_API_VERSION}`);\n }\n if (typeof d.optionsVersion !== 'number' || !Number.isInteger(d.optionsVersion) || d.optionsVersion < 1) {\n add('optionsVersion', `optionsVersion must be a positive integer, got ${JSON.stringify(d.optionsVersion)}`);\n }\n\n if (!isRecord(d.display)) {\n add('display', 'display must be an object');\n } else {\n if (typeof d.display.title !== 'string' || d.display.title === '') {\n add('display.title', 'display.title must be a non-empty string');\n }\n // A host renders these as text and groups its add menu by category, so a non-string\n // here is a crash (or an \"[object Object]\" heading) in someone else's console.\n for (const text of ['description', 'category', 'titleId', 'categoryId'] as const) {\n const value = d.display[text];\n if (value !== undefined && typeof value !== 'string') add(`display.${text}`, `display.${text} must be a string`);\n }\n }\n\n const channel = CHANNELS.find((c) => c === d.channel);\n if (!channel) add('channel', `channel must be one of ${CHANNELS.join(', ')}, got ${JSON.stringify(d.channel)}`);\n\n // A built-in is documented in the widgets package's own table (see its builtin-option-docs.ts), not on\n // the descriptor the renderer loads, so only an extension's descriptor must carry its option docs.\n const specs = checkOptions(d.options, !isBuiltinWidgetId(d.id), add);\n const constraints = checkConstraints(d.constraints, specs, add);\n\n if (specs) checkDefaults(d.defaults, specs, constraints, typeof d.id === 'string' ? d.id : id, add);\n else if (!isRecord(d.defaults)) add('defaults', 'defaults must be an object');\n\n checkDatasource(d.datasource, channel, add);\n if (channel) checkBinding(d.binding, channel, specs, add);\n checkInteractions(d.interactions, channel, add);\n checkLayout(d.layout, add);\n\n if (d.freshness !== undefined) {\n if (d.freshness !== true) add('freshness', 'freshness must be true when present');\n else if (channel && channel !== 'measurement') add('freshness', 'freshness applies only to the measurement channel');\n }\n\n return issues;\n}\n\ntype Add = (path: string, message: string) => void;\n\n// Descriptor values must survive JSON.stringify unchanged: no function, symbol, bigint,\n// undefined, NaN/Infinity or non-plain object (a class instance, Date, Map\u2026).\nfunction jsonOnly(value: unknown, path: string, add: Add, ancestors: Set<object>): void {\n const here = path || '(descriptor)';\n switch (typeof value) {\n case 'string':\n case 'boolean':\n return;\n case 'number':\n if (!Number.isFinite(value)) add(path, `${here} is ${value}; descriptors hold JSON values only`);\n return;\n case 'undefined':\n add(path, `${here} is undefined; omit the key instead (descriptors hold JSON values only)`);\n return;\n case 'function':\n case 'symbol':\n case 'bigint':\n add(path, `${here} is a ${typeof value}; descriptors hold JSON values only`);\n return;\n }\n if (value === null) return;\n const obj = value as object;\n if (ancestors.has(obj)) {\n add(path, `${here} is a circular reference; descriptors hold JSON values only`);\n return;\n }\n ancestors.add(obj);\n if (Array.isArray(obj)) {\n obj.forEach((item, i) => jsonOnly(item, `${path}[${i}]`, add, ancestors));\n } else if (!isPlainObject(obj)) {\n add(path, `${here} is not a plain object; descriptors hold JSON values only`);\n } else {\n if (Object.getOwnPropertySymbols(obj).length > 0) add(path, `${here} has a symbol key; descriptors hold JSON values only`);\n for (const [key, item] of Object.entries(obj)) jsonOnly(item, join(path, key), add, ancestors);\n }\n ancestors.delete(obj);\n}\n\n// Returns the schema when it is structurally sound enough to check references and defaults\n// against, else undefined (the faults are already reported).\nfunction checkOptions(options: unknown, requireDoc: boolean, add: Add): WidgetOptionSpecs | undefined {\n if (!isRecord(options)) {\n add('options', 'options must be an object mapping option keys to specs');\n return undefined;\n }\n return checkOptionsAt(options, 'options.', requireDoc, add) ? (options as unknown as WidgetOptionSpecs) : undefined;\n}\n\n// Checks a table of option specs, reporting each fault under `prefix` + key. True when the table\n// is structurally sound. The same rules judge a list option's item fields (prefix\n// `options.<key>.item.`), so a field cannot be declared more loosely than an option.\nfunction checkOptionsAt(options: Record<string, unknown>, prefix: string, requireDoc: boolean, add: Add): boolean {\n let sound = true;\n for (const [key, spec] of Object.entries(options)) {\n const at = `${prefix}${key}`;\n if (!isRecord(spec)) {\n add(at, 'an option spec must be an object');\n sound = false;\n continue;\n }\n if (typeof spec.doc !== 'string' && (requireDoc || spec.doc !== undefined)) {\n add(`${at}.doc`, 'doc must be a string');\n }\n for (const text of ['label', 'help', 'labelId', 'helpId', 'placeholder', 'placeholderId'] as const) {\n if (spec[text] !== undefined && typeof spec[text] !== 'string') add(`${at}.${text}`, `${text} must be a string`);\n }\n // Any non-empty string: a control this host does not implement is a fallback to the\n // generic field, not a fault (see OptionSpecBase.control).\n if (spec.control !== undefined && (typeof spec.control !== 'string' || spec.control === '')) {\n add(`${at}.control`, `control must be a non-empty string, got ${JSON.stringify(spec.control)}`);\n }\n switch (spec.kind) {\n case 'string':\n case 'boolean':\n break;\n case 'number':\n for (const bound of ['min', 'max'] as const) {\n if (spec[bound] !== undefined && typeof spec[bound] !== 'number') add(`${at}.${bound}`, `${bound} must be a number`);\n }\n break;\n case 'enum':\n if (!Array.isArray(spec.values) || spec.values.length === 0 || !spec.values.every((v) => typeof v === 'string')) {\n add(`${at}.values`, 'an enum option needs a non-empty list of string values');\n sound = false;\n }\n for (const table of ['valueLabels', 'valueLabelIds'] as const) {\n const labels = spec[table];\n if (labels === undefined) continue;\n if (!isRecord(labels)) {\n add(`${at}.${table}`, `${table} must be an object mapping enum values to strings`);\n continue;\n }\n for (const [value, label] of Object.entries(labels)) {\n if (typeof label !== 'string') add(`${at}.${table}.${value}`, `${table} entries must be strings`);\n else if (Array.isArray(spec.values) && !spec.values.includes(value)) {\n add(`${at}.${table}.${value}`, `${JSON.stringify(value)} is not one of this option's values`);\n }\n }\n }\n break;\n case 'list': {\n if (spec.maxItems !== undefined && (typeof spec.maxItems !== 'number' || !Number.isInteger(spec.maxItems) || spec.maxItems < 1)) {\n add(`${at}.maxItems`, 'maxItems must be a whole number of at least 1');\n }\n // A non-boolean `ordered` (\"yes\", 1) would be read as truthy by one host and ignored by\n // another; refuse it so every host sees the same list.\n if (spec.ordered !== undefined && typeof spec.ordered !== 'boolean') {\n add(`${at}.ordered`, 'ordered must be true or false');\n }\n if (!isRecord(spec.item) || Object.keys(spec.item).length === 0) {\n add(`${at}.item`, 'a list option needs an item object declaring at least one field');\n sound = false;\n break;\n }\n // A field is a scalar spec checked with the same rules as a top-level option, but\n // nesting a list is not offered: the editor row is flat.\n const fields = spec.item as Record<string, unknown>;\n const flat = Object.fromEntries(Object.entries(fields).filter(([, f]) => !isRecord(f) || f.kind !== 'list'));\n for (const key of Object.keys(fields)) {\n if (!(key in flat)) add(`${at}.item.${key}`, 'a list item field cannot itself be a list');\n }\n if (Object.keys(flat).length !== Object.keys(fields).length) {\n sound = false;\n break;\n }\n if (!checkOptionsAt(flat, `${at}.item.`, requireDoc, add)) sound = false;\n break;\n }\n default:\n add(`${at}.kind`, `kind must be string, number, boolean, enum or list, got ${JSON.stringify(spec.kind)}`);\n sound = false;\n }\n }\n return sound;\n}\n\nfunction checkConstraints(\n constraints: unknown,\n specs: WidgetOptionSpecs | undefined,\n add: Add,\n): { kind: 'greater-than'; key: string; than: string }[] {\n if (constraints === undefined) return [];\n if (!Array.isArray(constraints)) {\n add('constraints', 'constraints must be a list');\n return [];\n }\n const valid: { kind: 'greater-than'; key: string; than: string }[] = [];\n constraints.forEach((c, i) => {\n const at = `constraints[${i}]`;\n if (!isRecord(c) || c.kind !== 'greater-than') {\n add(`${at}.kind`, `constraint kind must be \"greater-than\", got ${JSON.stringify(isRecord(c) ? c.kind : c)}`);\n return;\n }\n let ok = true;\n for (const field of ['key', 'than'] as const) {\n const name = c[field];\n if (typeof name !== 'string') {\n add(`${at}.${field}`, `${field} must name a declared option`);\n ok = false;\n } else if (specs && (!hasOwn(specs, name) || specs[name].kind !== 'number')) {\n add(`${at}.${field}`, `${field} ${JSON.stringify(name)} must name a declared number option`);\n ok = false;\n }\n }\n if (ok) valid.push({ kind: 'greater-than', key: c.key as string, than: c.than as string });\n });\n return valid;\n}\n\nfunction checkDefaults(\n defaults: unknown,\n specs: WidgetOptionSpecs,\n constraints: { kind: 'greater-than'; key: string; than: string }[],\n id: string,\n add: Add,\n): void {\n if (!isRecord(defaults)) {\n add('defaults', 'defaults must be an object');\n return;\n }\n // 'missing' is not a defaults fault: a fresh widget may be authoring-incomplete (an image\n // has no url until the author gives one), so `required` binds a SAVED widget, not the\n // starting point.\n const found: OptionIssue[] = validateOptions(specs, defaults, { constraints, label: id }).filter(\n (issue) => issue.code !== 'missing',\n );\n for (const issue of found) add(`defaults.${issue.key}`, issue.message);\n}\n\nfunction checkDatasource(datasource: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(datasource)) {\n add('datasource', 'datasource must be an object');\n return;\n }\n const mode = datasource.mode;\n if (mode !== 'none' && mode !== 'optional' && mode !== 'required') {\n add('datasource.mode', `mode must be none, optional or required, got ${JSON.stringify(mode)}`);\n return;\n }\n if ((channel === 'none' || channel === 'selection') && mode !== 'none') {\n add('datasource.mode', `a ${channel}-channel widget opens no data channel, so its datasource mode must be \"none\"`);\n }\n if (mode === 'none') {\n if (datasource.selectors !== undefined) add('datasource.selectors', 'a datasource mode of \"none\" takes no selectors');\n return;\n }\n const selectors = datasource.selectors;\n if (!Array.isArray(selectors) || selectors.length === 0) {\n add('datasource.selectors', `a datasource mode of \"${mode}\" needs a non-empty list of selector kinds`);\n return;\n }\n selectors.forEach((s, i) => {\n if (typeof s !== 'string' || !SELECTOR_KINDS.includes(s)) {\n add(`datasource.selectors[${i}]`, `selector must be one of ${SELECTOR_KINDS.join(', ')}, got ${JSON.stringify(s)}`);\n } else if (channel === 'control' && s !== 'device' && s !== 'slot') {\n add(`datasource.selectors[${i}]`, 'a command target must be a device, so a control widget accepts only device and slot selectors');\n }\n });\n}\n\nfunction checkBinding(binding: unknown, channel: WidgetChannel, specs: WidgetOptionSpecs | undefined, add: Add): void {\n if (!isRecord(binding)) {\n add('binding', 'binding must be an object');\n return;\n }\n const allowed = BINDING_KEYS[channel];\n for (const key of Object.keys(binding)) {\n if (!allowed.includes(key)) add(`binding.${key}`, `a ${channel}-channel widget has no binding policy \"${key}\"`);\n }\n if (allowed.includes('pageSize') && binding.pageSize === undefined) {\n add('binding.pageSize', `a ${channel}-channel widget must declare binding.pageSize`);\n }\n\n const ref = (key: string, accept: (spec: WidgetOptionSpecs[string]) => boolean, expect: string, allowConst: boolean): void => {\n const policy = binding[key];\n if (policy === undefined) return;\n const at = `binding.${key}`;\n if (!isRecord(policy)) {\n add(at, `${key} must be an object`);\n return;\n }\n const hasConst = policy.const !== undefined;\n const hasOption = policy.option !== undefined;\n if (hasConst) {\n if (!allowConst) add(`${at}.const`, `${key} takes an option reference, not a constant`);\n else if (typeof policy.const !== 'number' || !Number.isInteger(policy.const) || policy.const < 1) {\n add(`${at}.const`, `const must be a positive integer, got ${JSON.stringify(policy.const)}`);\n }\n if (hasOption) add(at, `${key} must be a constant or an option reference, not both`);\n return;\n }\n if (!hasOption) {\n add(at, `${key} must be a constant or an option reference`);\n return;\n }\n const name = policy.option;\n if (typeof name !== 'string') {\n add(`${at}.option`, 'option must name a declared option key');\n } else if (specs) {\n if (!hasOwn(specs, name)) add(`${at}.option`, `option ${JSON.stringify(name)} is not a declared option key`);\n else if (!accept(specs[name])) add(`${at}.option`, `option ${JSON.stringify(name)} must be ${expect}`);\n }\n if (policy.min !== undefined && typeof policy.min !== 'number') add(`${at}.min`, 'min must be a number');\n if (policy.default !== undefined) {\n const wantsNumber = key === 'window' || key === 'windowSeconds' || key === 'pageSize';\n if (typeof policy.default !== (wantsNumber ? 'number' : 'string')) {\n add(`${at}.default`, `default must be a ${wantsNumber ? 'number' : 'string'}`);\n }\n }\n };\n\n const isNumber = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'number';\n const isText = (s: WidgetOptionSpecs[string]): boolean => s.kind === 'string' || s.kind === 'enum';\n const isBoolEnum = (s: WidgetOptionSpecs[string]): boolean =>\n s.kind === 'enum' && s.values.every((v) => v === 'true' || v === 'false');\n\n ref('window', isNumber, 'a number option', false);\n ref('windowSeconds', isNumber, 'a number option', false);\n ref('pageSize', isNumber, 'a number option', true);\n ref('state', isText, 'a string or enum option', false);\n ref('severity', isText, 'a string or enum option', false);\n ref('deviceType', isText, 'a string or enum option', false);\n ref('acknowledged', isBoolEnum, 'an enum option whose values are only \"true\" and \"false\"', false);\n\n // A MODE binding turns a behaviour on: `members` makes a measurement widget a fleet widget\n // (`{ const: true }` always, or `{ option, equals }`), and `groupBy` makes an alarm widget group by\n // device type (`{ option, equals }` only). Either way a declared option holding `equals` is the switch.\n const modeBinding = (key: 'members' | 'groupBy'): void => {\n const mode = binding[key];\n if (mode === undefined) return;\n const m = isRecord(mode) ? mode : {};\n const at = `binding.${key}`;\n const allowConst = key === 'members';\n const isConst = m.const !== undefined;\n if (isConst ? !allowConst || m.option !== undefined || m.equals !== undefined : typeof m.option !== 'string' || typeof m.equals !== 'string') {\n add(at, `${key} must be ${allowConst ? '{ const: true } or ' : ''}{ option, equals } naming a declared option and a value of it`);\n } else if (isConst) {\n if (m.const !== true) add(`${at}.const`, `const must be true, got ${JSON.stringify(m.const)}`);\n } else if (specs) {\n const spec = hasOwn(specs, m.option as string) ? specs[m.option as string] : undefined;\n if (!spec) add(`${at}.option`, `option ${JSON.stringify(m.option)} is not a declared option key`);\n else if (!isText(spec)) add(`${at}.option`, `option ${JSON.stringify(m.option)} must be a string or enum option`);\n else if (spec.kind === 'enum' && !spec.values.includes(m.equals as string)) {\n add(`${at}.equals`, `${JSON.stringify(m.equals)} is not one of the option's values`);\n }\n }\n };\n // `presence` and `history` are constant switches and nothing else: `{ const: true }`. `presence` makes a\n // measurement widget read its devices' connectivity (online or offline, last seen) in place of their\n // readings; `history` makes it read one device's stored history in time buckets, not the live feed.\n for (const key of ['presence', 'history'] as const) {\n const flag = binding[key];\n if (flag !== undefined && !(isRecord(flag) && flag.const === true && Object.keys(flag).length === 1)) {\n add(`binding.${key}`, 'must be { const: true }');\n }\n }\n modeBinding('members');\n modeBinding('groupBy');\n // `countBySeverity` is a constant switch and nothing else: `{ const: true }`.\n const counts = binding.countBySeverity;\n if (counts !== undefined && !(isRecord(counts) && counts.const === true && Object.keys(counts).length === 1)) {\n add('binding.countBySeverity', 'must be { const: true }');\n }\n}\n\nfunction readSpan(value: unknown, path: string, add: Add): { cols: number; rows: number } | undefined {\n if (!isRecord(value)) {\n add(path, `${path} must be an object with cols and rows`);\n return undefined;\n }\n let ok = true;\n for (const axis of ['cols', 'rows'] as const) {\n const n = value[axis];\n if (typeof n !== 'number' || !Number.isInteger(n) || n < 1) {\n add(`${path}.${axis}`, `${axis} must be a positive integer, got ${JSON.stringify(n)}`);\n ok = false;\n }\n }\n for (const key of Object.keys(value)) {\n if (key !== 'cols' && key !== 'rows') add(`${path}.${key}`, `unknown span field \"${key}\"`);\n }\n return ok ? { cols: value.cols as number, rows: value.rows as number } : undefined;\n}\n\nfunction checkLayout(layout: unknown, add: Add): void {\n if (layout === undefined) return;\n if (!isRecord(layout)) {\n add('layout', 'layout must be an object');\n return;\n }\n for (const key of Object.keys(layout)) {\n if (!['defaultSpan', 'minSpan', 'maxSpan', 'resize'].includes(key)) add(`layout.${key}`, `unknown layout field \"${key}\"`);\n }\n let def: { cols: number; rows: number } | undefined;\n if (layout.defaultSpan === undefined) add('layout.defaultSpan', 'layout needs a defaultSpan');\n else def = readSpan(layout.defaultSpan, 'layout.defaultSpan', add);\n const min = layout.minSpan === undefined ? undefined : readSpan(layout.minSpan, 'layout.minSpan', add);\n const max = layout.maxSpan === undefined ? undefined : readSpan(layout.maxSpan, 'layout.maxSpan', add);\n\n const mode = layout.resize ?? 'both';\n if (mode !== 'both' && mode !== 'horizontal' && mode !== 'vertical' && mode !== 'none') {\n add('layout.resize', `resize must be both, horizontal, vertical or none, got ${JSON.stringify(layout.resize)}`);\n return;\n }\n\n // minSpan <= defaultSpan <= maxSpan, per axis. An absent minSpan is 1 x 1.\n const floor = min ?? { cols: 1, rows: 1 };\n for (const axis of ['cols', 'rows'] as const) {\n if (def && floor[axis] > def[axis]) add('layout.minSpan', `minSpan.${axis} (${floor[axis]}) is above defaultSpan.${axis} (${def[axis]})`);\n if (def && max && def[axis] > max[axis]) add('layout.maxSpan', `maxSpan.${axis} (${max[axis]}) is below defaultSpan.${axis} (${def[axis]})`);\n if (!def && min && max && min[axis] > max[axis]) add('layout.maxSpan', `maxSpan.${axis} is below minSpan.${axis}`);\n }\n\n // A restricted mode pins an axis at defaultSpan, so a declared bound must not contradict it.\n // The free axis stays as open as the bounds leave it: no maxSpan means no ceiling there.\n if (!def) return;\n for (const axis of pinnedAxes(mode as WidgetResizeMode)) {\n for (const [name, bound] of [['minSpan', min], ['maxSpan', max]] as const) {\n if (bound && bound[axis] !== def[axis]) {\n add('layout.resize', `resize \"${mode}\" pins ${axis} at ${def[axis]}, but ${name}.${axis} is ${bound[axis]}`);\n }\n }\n }\n}\n\nfunction checkInteractions(interactions: unknown, channel: WidgetChannel | undefined, add: Add): void {\n if (!isRecord(interactions)) {\n add('interactions', 'interactions must be an object');\n return;\n }\n const only: Record<string, WidgetChannel> = { alarmActions: 'alarm', commandSend: 'control' };\n for (const [key, value] of Object.entries(interactions)) {\n const at = `interactions.${key}`;\n if (key !== 'alarmActions' && key !== 'commandSend' && key !== 'selection' && key !== 'candidates') {\n add(at, `unknown interaction \"${key}\"`);\n } else if (value !== true) {\n add(at, `${key} must be true when declared (omit it otherwise)`);\n } else if (channel && only[key] && only[key] !== channel) {\n add(at, `${key} is only available to ${only[key]}-channel widgets, not ${channel}`);\n }\n }\n}\n\n// ---- Catalog ----------------------------------------------------------------\n\n// Freezes in place, recursively. Idempotent, so a table shared between registries (the\n// built-in option specs are) is simply frozen again. Descriptors hold JSON values only, so\n// this never meets a function or a cycle.\n//\n// It descends even into an object that is ALREADY frozen: Object.freeze is shallow, so a\n// caller's `Object.freeze(descriptor)` says nothing about `descriptor.defaults`, and\n// stopping at the first frozen object would leave those children mutable.\nfunction deepFreeze<T>(value: T): T {\n if (typeof value === 'object' && value !== null) {\n Object.freeze(value);\n for (const child of Object.values(value)) deepFreeze(child);\n }\n return value;\n}\n\n// createWidgetCatalog validates every descriptor and the set (duplicate ids), then throws\n// ONE WidgetCatalogError carrying every issue, or returns a sealed catalog.\nexport function createWidgetCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const issues = collectCatalogIssues(descriptors);\n if (issues.length > 0) throw new WidgetCatalogError(issues);\n return sealCatalog(descriptors);\n}\n\n// The issues for a set of descriptors: each descriptor's own, plus duplicate ids. A pure\n// report \u2014 it cannot produce a catalog. Exposed so the widget registry can report descriptor\n// and implementation faults together before it builds anything.\nexport function collectCatalogIssues(descriptors: readonly unknown[]): DescriptorIssue[] {\n if (!Array.isArray(descriptors)) {\n return [{ id: '#?', path: '', message: 'descriptors must be an array' }];\n }\n const issues: DescriptorIssue[] = [];\n const seen = new Map<string, number>();\n descriptors.forEach((d, index) => {\n const id = isRecord(d) && typeof d.id === 'string' && d.id !== '' ? d.id : `#${index}`;\n issues.push(...collectDescriptorIssues(d, id));\n if (id.startsWith('#')) return;\n const first = seen.get(id);\n if (first === undefined) seen.set(id, index);\n else issues.push({ id, path: 'id', message: `duplicate widget id (first registered at position ${first}, again at ${index})` });\n });\n return issues;\n}\n\n// Builds the sealed catalog from descriptors ALREADY validated by collectCatalogIssues.\n// Module-private on purpose: the only way to obtain a catalog is createWidgetCatalog, which\n// validates first, so the published contract has no unvalidated path to a catalog.\nfunction sealCatalog(descriptors: readonly WidgetDescriptor[]): WidgetCatalog {\n const byId = new Map<string, WidgetDescriptor>();\n for (const d of descriptors) byId.set(d.id, deepFreeze(d));\n const ordered: readonly WidgetDescriptor[] = Object.freeze([...byId.values()]);\n return Object.freeze({\n has: (id: string) => byId.has(id),\n get: (id: string) => byId.get(id),\n list: () => ordered,\n });\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Adaptive columns and reflow, as pure functions.\n//\n// Cells keep a roughly constant size: a board authored in N columns renders as authored while a\n// cell at N columns is at least minCellSize wide (the cells stretch to fill the width, so a wall\n// gets bigger cells and bigger text, not more widgets). Narrower than that the column count\n// drops to what fits, and the base layout is poured into it in reading order. A span in cells\n// therefore means a readable size at every width.\n//\n// The container's width is what is measured, never the window's: an embedded board answers to the\n// space it is actually given.\n\nimport {\n BASE_BREAKPOINT,\n PHONE_MIN_COLUMNS,\n activeBreakpoint,\n breakpointColumns,\n cellSizeAt,\n gridFootprintsOverlap,\n gutterPx,\n resolvePlacement,\n} from './grid';\nimport type { SpanLimits } from './layout-lint';\nimport type { CanvasGrid, DashboardDefinition, GridPlacement, WidgetInstance } from './types';\n\n// The column count a container width gets: the authored count while its cells would be at least\n// minCellSize (scaled by the density), otherwise as many columns as fit at minCellSize, no fewer\n// than PHONE_MIN_COLUMNS (a board authored in fewer keeps its own count) and never more than the\n// authored count.\nexport function liveColumns(grid: CanvasGrid, containerWidth: number, authored: number, scale = 1): number {\n const minCell = grid.minCellSize * scale;\n if (cellSizeAt(grid, containerWidth, authored, scale) >= minCell) return authored;\n const gx = gutterPx(grid, scale).x;\n // The inverse of cellSizeAt: the most columns whose cells are still minCell wide.\n const room = cellSizeAt(grid, containerWidth, 1, scale);\n const fit = Math.floor((room + gx) / (minCell + gx));\n return Math.min(authored, Math.max(PHONE_MIN_COLUMNS, fit));\n}\n\nexport interface ReflowItem {\n id: string;\n placement: GridPlacement;\n // The widget's declared limits; a span is clamped into them. Absent = unbounded.\n limits?: SpanLimits;\n}\n\nconst clampInto = (n: number, min: number | undefined, max: number | undefined) =>\n Math.min(max ?? Infinity, Math.max(min ?? 1, n));\n\n// The container width below which a reflowed board is drawn as a single column: the compact\n// window class phones fall in. Above it, narrow widgets pair up side by side; below it two of\n// them would each get half a phone, so every widget takes its own row at the full width.\nexport const STACK_BELOW_WIDTH = 600;\n\n// reflowGrid pours grid footprints into `columns` columns. Widgets go in reading order (row, then\n// column, then their order in the input), each keeping its span clamped into its declared limits\n// and then into the columns (a widget wider than the whole grid takes the full width, below its\n// minSpan if it has to), at the first free slot at or below the row the previous widget landed in.\n// The row never moves back up, so the order a viewer reads is the order authored. Nothing\n// overlaps. Deterministic: the same input gives the same output.\n//\n// With `stack` (a phone), each widget instead takes its own row under the previous one, as wide\n// as the grid and its maxSpan allow: a single column, still in reading order, heights kept.\nexport function reflowGrid(\n items: readonly ReflowItem[],\n columns: number,\n options: { stack?: boolean } = {},\n): Map<string, GridPlacement> {\n const order = items\n .map((item, index) => ({ item, index }))\n .sort(\n (a, b) =>\n a.item.placement.row - b.item.placement.row ||\n a.item.placement.col - b.item.placement.col ||\n a.index - b.index,\n );\n const placed: GridPlacement[] = [];\n const out = new Map<string, GridPlacement>();\n let floor = 0;\n let bottom = 0;\n for (const { item } of order) {\n const { placement: p, limits } = item;\n const want = options.stack ? columns : p.cols;\n const cols = Math.min(columns, clampInto(want, limits?.minSpan?.cols, limits?.maxSpan?.cols));\n const rows = clampInto(p.rows, limits?.minSpan?.rows, limits?.maxSpan?.rows);\n if (options.stack) {\n const spot: GridPlacement = { mode: 'grid', col: 0, row: bottom, cols, rows };\n out.set(item.id, spot);\n bottom += rows;\n continue;\n }\n let spot: GridPlacement | undefined;\n for (let row = floor; !spot; row++) {\n for (let col = 0; col + cols <= columns; col++) {\n const probe: GridPlacement = { mode: 'grid', col, row, cols, rows };\n if (!placed.some((q) => gridFootprintsOverlap(probe, q))) {\n spot = probe;\n break;\n }\n }\n }\n placed.push(spot);\n out.set(item.id, spot);\n floor = spot.row;\n }\n return out;\n}\n\n// The SHAPE of the layout at a width: everything about it a width decides. Two widths with the\n// same shape draw the same layout (the cells differ in size, which CSS follows on its own), so a\n// renderer redraws only when the shape changes, not on every pixel of a resize.\nexport interface LayoutShape {\n // The breakpoint whose placements are shown: chosen from the container's width.\n breakpoint: string;\n // The column count actually drawn.\n columns: number;\n // True when the board was poured into fewer columns than that breakpoint authored.\n reflowed: boolean;\n // True when the reflow is a single column (a container narrower than STACK_BELOW_WIDTH).\n stacked: boolean;\n}\n\n// layoutShape is the shape a container width gives a definition's canvas. A width of 0 or less\n// means \"not measured yet\": the authored base breakpoint at its own columns.\nexport function layoutShape(\n canvas: Pick<DashboardDefinition['canvas'], 'grid' | 'breakpoints'>,\n containerWidth: number,\n scale = 1,\n): LayoutShape {\n const measured = containerWidth > 0;\n const breakpoint = measured ? activeBreakpoint(canvas.breakpoints, containerWidth) : BASE_BREAKPOINT;\n const authored = breakpointColumns(canvas, breakpoint);\n const columns = measured ? liveColumns(canvas.grid, containerWidth, authored, scale) : authored;\n const reflowed = columns < authored;\n return { breakpoint, columns, reflowed, stacked: reflowed && containerWidth < STACK_BELOW_WIDTH };\n}\n\nexport interface EffectiveLayout extends LayoutShape {\n // The grid footprint of every grid widget as drawn (the authored one when not reflowed).\n grid: Map<string, GridPlacement>;\n}\n\n// effectiveLayout is what a renderer draws for a container width. A width of 0 or less means \"not\n// measured yet\" and draws the authored base layout. A breakpoint the author defined for this\n// width wins over the automatic reflow: it is drawn as authored while its cells fit, and is itself\n// poured into fewer columns only if even its own count does not fit. A reflow in a container\n// narrower than STACK_BELOW_WIDTH is a single column. Relative widgets are not in `grid`: they\n// follow their host.\nexport function effectiveLayout(\n def: DashboardDefinition,\n containerWidth: number,\n options: { scale?: number; limitsOf?: (w: WidgetInstance) => SpanLimits | undefined } = {},\n): EffectiveLayout {\n const shape = layoutShape(def.canvas, containerWidth, options.scale ?? 1);\n const items: ReflowItem[] = [];\n for (const w of def.widgets) {\n const p = resolvePlacement(w.layout, shape.breakpoint);\n if (p.mode === 'grid') items.push({ id: w.id, placement: p, limits: options.limitsOf?.(w) });\n }\n const grid = shape.reflowed\n ? reflowGrid(items, shape.columns, { stack: shape.stacked })\n : new Map(items.map((i) => [i.id, i.placement]));\n return { ...shape, grid };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["own", "old", "rows", "ownGet", "ownGet", "sameBinding", "window", "gql", "members", "gql", "window", "pages", "window", "num", "oneOf", "instant", "m", "own", "instant", "signature", "window", "x", "sameBinding", "slot", "gql", "isScopeUnsupportedError", "gql", "isScopeUnsupportedError", "gql", "window", "gql", "SEMVER", "key", "spot"]
|
|
7
7
|
}
|