@devicechain/dashboards 0.21.1-rc.1 → 0.22.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/README.md CHANGED
@@ -54,15 +54,23 @@ Ten widgets pointed at the same device open **one** upstream subscription, not
54
54
  ten. Each widget declares the measurement names it cares about (none means all),
55
55
  and the hub fans a single stream out to the subscribers that asked for each name.
56
56
 
57
- Four channels, each with its own `subscribe*` method and disposer:
57
+ Each channel has its own method, and each subscription returns its disposer:
58
58
 
59
- | channel | what it carries |
59
+ | method | what it carries |
60
60
  | --- | --- |
61
61
  | `subscribeWidget` | measurement samples for a datasource selector |
62
+ | `subscribeMembers` | a fleet widget's members, then each member's samples tagged with its device |
63
+ | `bucketMeasurements` | the time buckets (count, minimum, maximum, sum) a stat widget folds into one figure |
62
64
  | `subscribeAlarms` | live alarm rows, plus `acknowledgeAlarm` / `clearAlarm` |
63
- | `subscribeCommands` | command rows and their delivery status, plus dispatch |
65
+ | `subscribeCommands` | command rows and their delivery status, plus `sendCommand` |
64
66
  | `subscribeLocations` | position series for map widgets |
65
67
 
68
+ The widget layer depends on the `WidgetDataSource` interface, not on the hub
69
+ class, so a host can feed it from the hub, from `SyntheticDataSource` or from a
70
+ recorded run (`RecordedDataSource`). `subscribeMembers`, `bucketMeasurements` and
71
+ `readMemberSeeds` are optional on that interface: a source without them leaves
72
+ the widgets that need them saying so instead of showing a number.
73
+
66
74
  `disposeAll()` tears down every open channel — a host calls it when the board
67
75
  closes, which is what keeps a poll and a socket from outliving the page.
68
76
 
@@ -77,11 +85,49 @@ import { parseDashboardDefinition, serializeDefinition } from '@devicechain/dash
77
85
  const board = parseDashboardDefinition(json); // throws DashboardDefinitionError
78
86
  ```
79
87
 
88
+ A board saved by a newer release than this package is refused rather than read as
89
+ the current format: the `DashboardDefinitionError` carries `code: 'schemaTooNew'`
90
+ and the version it found in `found`. Test `code`, not `instanceof`, so the check
91
+ still holds when an application ends up with two copies of the package.
92
+
80
93
  Parsing is validating: a malformed board fails here, in your code, rather than
81
- half-rendering later. `isDirty` compares a working copy against its saved
82
- original, `resolveWidgetBox` and `activeBreakpoint` resolve responsive layout,
83
- and the `editor-model` transforms (`addWidget`, `setWidgetBox`, `deleteWidget`,
84
- `bringToFront`, …) are pure functions an authoring UI can wire to drag and resize.
94
+ half-rendering later. A board stored in an older format is converted as it is
95
+ parsed, and the result carries a `convertedFrom` mark until it is saved again
96
+ (`withoutConversionMark` drops it). `isDirty` compares a working copy against its
97
+ saved original.
98
+
99
+ ### Layout
100
+
101
+ A board lays out on a grid of square cells. Each widget has a `WidgetLayout`: a
102
+ `Placement` per breakpoint, with `base` required. A placement is either a grid
103
+ footprint (`GridPlacement`, measured in cells) or a position relative to another
104
+ widget (`RelativePlacement`). The column count follows the container, so the same
105
+ board reads on a phone and on a wall display.
106
+
107
+ | need | exports |
108
+ | --- | --- |
109
+ | the placement a widget shows at a width | `activeBreakpoint`, `resolvePlacement` |
110
+ | what a renderer draws for a container width | `effectiveLayout`, `layoutShape`, `liveColumns` |
111
+ | pixel boxes for every widget | `layoutRects`, `gridRect`, `relativeRect`, `cellSizeAt` |
112
+ | grid lengths and the widget frame | `gridLengthPx`, `gutterPx`, `paddingPx`, `resolveFrame` |
113
+ | placement against what each widget can show | `lintLayout`, `blockingLayoutFindings` |
114
+
115
+ The `editor-model` transforms are pure functions an authoring UI can wire to drag
116
+ and resize: `addWidget`, `moveWidget`, `resizeWidget`, `setWidgetPlacement`,
117
+ `placeOnGrid`, `placeRelative`, `deleteWidget`, `setTitle`, `setCanvasGrid`,
118
+ `setCanvasDensity` and `updateWidget`, with `gridPlacementToPx` and
119
+ `pxToGridPlacement` to convert between cells and pixels. Moving or resizing a
120
+ grid widget pushes whatever it now covers out of its way. Grid widgets never
121
+ overlap, so there is no stacking order to manage.
122
+
123
+ ### Time range and density
124
+
125
+ A board has a time range: `TIME_RANGE_PRESETS` (15 minutes to 7 days),
126
+ `initialTimeRange`, `pauseRange` / `resumeRange` and `rangeWindow` manage it, and
127
+ `timeRangeFromSearch` reads the `range` and `paused` URL parameters. Its density
128
+ (`CANVAS_DENSITIES`: `compact`, `comfortable`, `wall`) scales every text and
129
+ spacing size by `DENSITY_SCALE`; a host may override the board's own value at
130
+ view time.
85
131
 
86
132
  ## Slots
87
133
 
@@ -113,6 +159,41 @@ is still in flight but is no longer cancellable. The underlying sets are
113
159
  deliberately not exported: every surface asks the predicate, so a new status
114
160
  cannot be learned by some call sites and not others.
115
161
 
162
+ ## Upgrading from 0.21
163
+
164
+ 0.22 replaces the layout model, so code written against 0.21 needs changes:
165
+
166
+ | 0.21 | 0.22 |
167
+ | --- | --- |
168
+ | `WidgetBox` | `Placement` (`GridPlacement` or `RelativePlacement`) |
169
+ | `resolveWidgetBox` | `resolvePlacement` |
170
+ | `baseBox` | `basePlacement` |
171
+ | `setWidgetBox` | `setWidgetPlacement`, or `moveWidget` / `resizeWidget` |
172
+ | `gridBoxToPx` / `pxToGridBox` | `gridPlacementToPx` / `pxToGridPlacement` |
173
+ | `bringToFront` | removed: grid widgets cannot overlap, so there is no stacking order |
174
+
175
+ These keep their names but change type:
176
+
177
+ - `WidgetLayout` maps a breakpoint to a `Placement`, and `Breakpoints` maps a
178
+ name to a `BreakpointSpec`.
179
+ - `CanvasGrid` replaces `gap` and `rowHeight` with `minCellSize`, `gutter` and
180
+ `padding`; `GridGeometry` replaces its column and row sizes with `cell` and
181
+ `gutter`.
182
+ - `SYNTHETIC_GENERATORS` is a list of generator ids (`'sine'`, `'ramp'`,
183
+ `'random-walk'`) instead of `{ value, label }` pairs. Supply your own labels;
184
+ the stored ids are unchanged.
185
+ - `parseDashboardDefinition` refuses a definition whose grid widgets overlap. A
186
+ definition in an older format is converted before that check.
187
+
188
+ Some unions gained members, which breaks an exhaustive `switch` over them:
189
+ `WidgetChannel` has `'aggregate'` (the stat card), `OptionSpec` has
190
+ `ListOptionSpec`, and `WidgetType`, `WidgetId` and `BuiltinWidgetId` have the new
191
+ built-in widgets.
192
+
193
+ The release's upgrade guide covers what changes for stored boards and for the
194
+ other packages:
195
+ [If you build against the npm packages](https://docs.devicechain.io/deployment/releases-and-upgrades#v0220-packages).
196
+
116
197
  ## Compatibility
117
198
 
118
199
  ESM only, built for bundler resolution (Vite, webpack, Rollup, esbuild). The
@@ -3,6 +3,22 @@ export interface MeasurementStreamState {
3
3
  latest: Record<string, MeasurementSample>;
4
4
  samples: MeasurementSample[];
5
5
  error: unknown | null;
6
+ truncatedBefore?: string;
7
+ members?: FleetState;
8
+ }
9
+ export interface FleetMember {
10
+ token: string;
11
+ name: string | null;
12
+ latest: Record<string, MeasurementSample>;
13
+ seed: 'pending' | 'ready' | 'failed';
14
+ observed: string[];
15
+ unread?: string[];
16
+ }
17
+ export interface FleetState {
18
+ status: 'loading' | 'ready' | 'unbound' | 'forbidden' | 'unsupported';
19
+ members: FleetMember[];
20
+ total: number;
21
+ truncated: boolean;
6
22
  }
7
23
  export interface AlarmStreamState {
8
24
  alarms: AlarmRow[];
@@ -24,3 +40,30 @@ export interface LocationStreamState {
24
40
  loading: boolean;
25
41
  error: unknown | null;
26
42
  }
43
+ export interface MeasurementAggregate {
44
+ count: number;
45
+ min: number;
46
+ max: number;
47
+ sum: number;
48
+ avg: number;
49
+ }
50
+ export interface AggregateSpan {
51
+ startTime: string;
52
+ endTime: string;
53
+ spanSeconds: number;
54
+ }
55
+ export type AggregateResult = {
56
+ status: 'loading';
57
+ } | ({
58
+ status: 'ready';
59
+ byName: Record<string, MeasurementAggregate>;
60
+ } & AggregateSpan) | {
61
+ status: 'unavailable';
62
+ reason: 'unsupported' | 'failed';
63
+ message?: string;
64
+ };
65
+ export interface AggregateStreamState {
66
+ aggregates: AggregateResult;
67
+ latest: Record<string, MeasurementSample>;
68
+ error: unknown | null;
69
+ }
@@ -0,0 +1,27 @@
1
+ export type HubConnectionState = 'live' | 'reconnecting' | 'offline';
2
+ export declare const OFFLINE_AFTER_MS = 20000;
3
+ export declare const STABLE_AFTER_MS = 10000;
4
+ export interface ConnectionHandle {
5
+ connected(): void;
6
+ closed(): void;
7
+ release(): void;
8
+ }
9
+ export declare class ConnectionTracker {
10
+ private readonly offlineAfterMs;
11
+ private readonly stableAfterMs;
12
+ private readonly down;
13
+ private readonly listeners;
14
+ private escalated;
15
+ private escalate;
16
+ private recover;
17
+ private browserOffline;
18
+ private current;
19
+ private detach;
20
+ constructor(offlineAfterMs?: number, stableAfterMs?: number);
21
+ get state(): HubConnectionState;
22
+ register(): ConnectionHandle;
23
+ subscribe(listener: () => void): () => void;
24
+ private attach;
25
+ private release;
26
+ private settle;
27
+ }
@@ -1,13 +1,16 @@
1
- import { type Breakpoints, type DashboardDefinition, type ExtensionWidgetInstance, type KnownWidgetInstance, type SlotBinding, type SlotDefinition, type WidgetBox, type WidgetLayout } from './types';
1
+ import { type DashboardDefinition, type ExtensionWidgetInstance, type KnownWidgetInstance, type SlotBinding, type SlotDefinition } from './types';
2
+ import { BASE_BREAKPOINT } from './grid';
2
3
  import type { WidgetCatalog } from './widget-catalog';
3
- export declare const BASE_BREAKPOINT = "base";
4
+ export { BASE_BREAKPOINT };
4
5
  export declare class DashboardDefinitionError extends Error {
5
- constructor(message: string);
6
+ readonly code?: 'schemaTooNew' | undefined;
7
+ readonly found?: number | undefined;
8
+ constructor(message: string, code?: 'schemaTooNew' | undefined, found?: number | undefined);
6
9
  }
7
10
  export interface ParseDefinitionOptions {
8
11
  catalog?: WidgetCatalog;
9
12
  }
10
- export declare function parseDashboardDefinition(raw: unknown, options?: ParseDefinitionOptions): DashboardDefinition;
13
+ export declare function parseDashboardDefinition(input: unknown, options?: ParseDefinitionOptions): DashboardDefinition;
11
14
  export declare function canScopeSlot(slots: Record<string, SlotDefinition>, child: string, parentName: string): boolean;
12
15
  export declare function parseSlotBinding(raw: unknown): SlotBinding | undefined;
13
16
  export type StoredDashboardDefinition = Omit<DashboardDefinition, 'widgets'> & {
@@ -15,7 +18,6 @@ export type StoredDashboardDefinition = Omit<DashboardDefinition, 'widgets'> & {
15
18
  };
16
19
  export declare function storedDefinition(def: DashboardDefinition): StoredDashboardDefinition;
17
20
  export declare function serializeDefinition(def: DashboardDefinition): string;
21
+ export declare function withoutConversionMark(def: DashboardDefinition): DashboardDefinition;
18
22
  export declare function isDirty(a: DashboardDefinition, b: DashboardDefinition): boolean;
19
- export declare function resolveWidgetBox(layout: WidgetLayout, breakpoint: string): WidgetBox;
20
- export declare function activeBreakpoint(breakpoints: Breakpoints, viewportWidth: number): string;
21
23
  export declare function generateWidgetId(): string;
@@ -0,0 +1,42 @@
1
+ import { type ParseDefinitionOptions } from './definition';
2
+ import { type Canvas, type DashboardDefinition, type ResolvedWidgetInstance, type SlotDefinition } from './types';
3
+ export declare const WIDGET_CLIPBOARD_FORMAT = "devicechain/dashboard-widgets";
4
+ export declare const WIDGET_CLIPBOARD_VERSION = 1;
5
+ export interface ClipboardOrigin {
6
+ tenant: string;
7
+ board: string;
8
+ }
9
+ export interface WidgetClipboard {
10
+ tenant: string;
11
+ board: string;
12
+ canvas: Canvas;
13
+ widgets: ResolvedWidgetInstance[];
14
+ slots: Record<string, SlotDefinition>;
15
+ }
16
+ export interface RefusedWidget {
17
+ type: string;
18
+ reason: string;
19
+ }
20
+ export interface UnboundPaste {
21
+ widgetId: string;
22
+ slot: string;
23
+ from: string;
24
+ reason: 'missing' | 'different' | 'tenant';
25
+ }
26
+ export interface PasteResult {
27
+ definition: DashboardDefinition;
28
+ ids: string[];
29
+ unbound: UnboundPaste[];
30
+ }
31
+ export declare function copyWidget(def: DashboardDefinition, id: string, origin: ClipboardOrigin): WidgetClipboard | undefined;
32
+ export declare function serializeWidgetClipboard(clip: WidgetClipboard): string;
33
+ export declare function parseWidgetClipboard(text: string, options?: ParseDefinitionOptions): {
34
+ clipboard: WidgetClipboard;
35
+ refused: RefusedWidget[];
36
+ } | undefined;
37
+ export declare function pasteWidgets(def: DashboardDefinition, clip: WidgetClipboard, at: ClipboardOrigin): PasteResult;
38
+ export declare function duplicateWidget(def: DashboardDefinition, id: string): {
39
+ definition: DashboardDefinition;
40
+ id: string;
41
+ unbound: UnboundPaste[];
42
+ } | undefined;
@@ -0,0 +1,30 @@
1
+ export declare const DEFAULT_HISTORY_LIMIT = 100;
2
+ export interface HistoryEntry<T, L> {
3
+ state: T;
4
+ label: L;
5
+ }
6
+ export interface EditHistory<T, L> {
7
+ readonly past: readonly HistoryEntry<T, L>[];
8
+ readonly present: T;
9
+ readonly future: readonly HistoryEntry<T, L>[];
10
+ readonly open?: string;
11
+ readonly limit: number;
12
+ }
13
+ export declare function createEditHistory<T, L = string>(present: T, options?: {
14
+ limit?: number;
15
+ }): EditHistory<T, L>;
16
+ export interface CommitOptions<T> {
17
+ key?: string;
18
+ equals?: (a: T, b: T) => boolean;
19
+ }
20
+ export declare function commitEdit<T, L>(h: EditHistory<T, L>, next: T, label: L, options?: CommitOptions<T>): EditHistory<T, L>;
21
+ export declare function sealHistory<T, L>(h: EditHistory<T, L>): EditHistory<T, L>;
22
+ export interface Step<T, L> {
23
+ history: EditHistory<T, L>;
24
+ label?: L;
25
+ }
26
+ export declare function undoEdit<T, L>(h: EditHistory<T, L>): Step<T, L>;
27
+ export declare function redoEdit<T, L>(h: EditHistory<T, L>): Step<T, L>;
28
+ export declare const canUndo: (h: EditHistory<unknown, unknown>) => boolean;
29
+ export declare const canRedo: (h: EditHistory<unknown, unknown>) => boolean;
30
+ export declare function rebaseHistory<T, L>(h: EditHistory<T, L>, fn: (state: T) => T): EditHistory<T, L>;
@@ -1,14 +1,44 @@
1
- import type { CanvasGrid, CanvasSizing, DashboardDefinition, WidgetBox, WidgetInstance } from './types';
2
- import type { WidgetDescriptor } from './widget-descriptor';
3
- export declare function baseBox(widget: WidgetInstance): WidgetBox;
4
- export declare function setWidgetBox(def: DashboardDefinition, id: string, box: WidgetBox): DashboardDefinition;
1
+ import { type Gutter } from './grid';
2
+ import { type SpanLimits } from './layout-lint';
3
+ import type { CanvasDensity, CanvasGrid, CanvasSizing, DashboardDefinition, GridPlacement, Placement, PlacementAnchor, WidgetInstance } from './types';
4
+ import { type WidgetDescriptor } from './widget-descriptor';
5
+ export declare function basePlacement(widget: WidgetInstance): Placement;
6
+ export declare function resolveEverywhere(def: DashboardDefinition, first: readonly string[], aside?: boolean): DashboardDefinition;
7
+ export interface EditOptions {
8
+ nearest?: boolean;
9
+ }
10
+ export declare function setWidgetPlacement(def: DashboardDefinition, id: string, placement: Placement, options?: EditOptions): DashboardDefinition;
11
+ export declare function clampSpan(span: {
12
+ cols: number;
13
+ rows: number;
14
+ }, limits: SpanLimits | undefined, room: number): {
15
+ cols: number;
16
+ rows: number;
17
+ };
18
+ export declare function moveWidget(def: DashboardDefinition, id: string, to: {
19
+ col: number;
20
+ row: number;
21
+ }, columns?: number, options?: EditOptions): DashboardDefinition;
22
+ export declare function resizeWidget(def: DashboardDefinition, id: string, target: {
23
+ col: number;
24
+ row: number;
25
+ cols: number;
26
+ rows: number;
27
+ }, limits: SpanLimits | undefined, edges?: {
28
+ fromLeft?: boolean;
29
+ fromTop?: boolean;
30
+ }, columns?: number, options?: EditOptions): DashboardDefinition;
5
31
  export declare function deleteWidget(def: DashboardDefinition, id: string): DashboardDefinition;
6
- export declare function bringToFront(def: DashboardDefinition, id: string): DashboardDefinition;
32
+ export declare function hostSpan(p: Placement, def: DashboardDefinition, bp: string): {
33
+ cols: number;
34
+ rows: number;
35
+ };
7
36
  export declare function setTitle(def: DashboardDefinition, title: string): DashboardDefinition;
8
37
  export declare function setCanvasGrid(def: DashboardDefinition, patch: Partial<CanvasGrid>): DashboardDefinition;
9
38
  export declare function setCanvasSizing(def: DashboardDefinition, sizing: CanvasSizing): DashboardDefinition;
39
+ export declare function setCanvasDensity(def: DashboardDefinition, density: CanvasDensity): DashboardDefinition;
10
40
  export declare function updateWidget(def: DashboardDefinition, id: string, next: WidgetInstance): DashboardDefinition;
11
- export declare function addWidget(def: DashboardDefinition, descriptor: Pick<WidgetDescriptor, 'id' | 'defaults' | 'version' | 'optionsVersion' | 'sdkApiVersion'>): {
41
+ export declare function addWidget(def: DashboardDefinition, descriptor: Pick<WidgetDescriptor, 'id' | 'defaults' | 'version' | 'optionsVersion' | 'sdkApiVersion' | 'layout'>): {
12
42
  definition: DashboardDefinition;
13
43
  id: string;
14
44
  };
@@ -19,13 +49,32 @@ export interface PixelRect {
19
49
  h: number;
20
50
  }
21
51
  export interface GridGeometry {
22
- colWidth: number;
23
- colGap: number;
24
- rowHeight: number;
25
- rowGap: number;
52
+ cell: number;
53
+ gutter: Gutter;
54
+ }
55
+ export declare function gridPlacementToPx(p: GridPlacement, geom: GridGeometry): PixelRect;
56
+ export declare function pxToGridPlacement(px: PixelRect, geom: GridGeometry, columns?: number): GridPlacement;
57
+ export declare function formatSpan(span: {
58
+ cols: number;
59
+ rows: number;
60
+ }): string;
61
+ export declare const OFFSET_STEP = 1;
62
+ export declare const OFFSET_STEP_FINE = 0.25;
63
+ export declare function snapOffset(value: number, fine?: boolean): number;
64
+ export interface RelativeBounds {
65
+ cell: number;
66
+ gutter: Gutter;
67
+ columns: number;
26
68
  }
27
- export declare function gridBoxToPx(box: WidgetBox, geom: GridGeometry): PixelRect;
28
- export declare function pxToGridBox(px: PixelRect, geom: GridGeometry, z: number, offset?: {
69
+ export declare function setRelativeOffset(def: DashboardDefinition, id: string, offset: {
29
70
  x: number;
30
71
  y: number;
31
- }, columns?: number): WidgetBox;
72
+ }, options?: {
73
+ fine?: boolean;
74
+ bounds?: RelativeBounds;
75
+ }): DashboardDefinition;
76
+ export declare function canHostRelative(def: DashboardDefinition, id: string, hostId: string): boolean;
77
+ export declare function placeRelative(def: DashboardDefinition, id: string, hostId: string, anchor: PlacementAnchor): DashboardDefinition;
78
+ export declare function setRelativeAnchor(def: DashboardDefinition, id: string, anchor: PlacementAnchor): DashboardDefinition;
79
+ export declare function placeOnGrid(def: DashboardDefinition, id: string): DashboardDefinition;
80
+ export declare function fitToLimits(def: DashboardDefinition, spanLimitsOf: (widget: WidgetInstance) => SpanLimits | undefined): DashboardDefinition;
package/dist/grid.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { type Breakpoints, type BreakpointSpec, type Canvas, type CanvasGrid, type GridLength, type GridPlacement, type Placement, type PlacementAnchor, type RelativePlacement, type ResolvedFrame, type SpaceTokenName, type WidgetFrameSpec, type WidgetInstance, type WidgetLayout } from './types';
2
+ export declare const BASE_BREAKPOINT = "base";
3
+ export declare const DEFAULT_GRID: Readonly<CanvasGrid>;
4
+ export declare const PHONE_MIN_COLUMNS = 4;
5
+ export declare function isSpaceToken(v: unknown): v is SpaceTokenName;
6
+ export declare function gridLengthPx(len: GridLength, scale?: number): number;
7
+ export interface Gutter {
8
+ x: number;
9
+ y: number;
10
+ }
11
+ export interface Insets {
12
+ top: number;
13
+ right: number;
14
+ bottom: number;
15
+ left: number;
16
+ }
17
+ export declare function gutterPx(grid: CanvasGrid, scale?: number): Gutter;
18
+ export declare function paddingPx(grid: CanvasGrid, scale?: number): Insets;
19
+ export declare function cellSizeAt(grid: CanvasGrid, containerWidth: number, columns?: number, scale?: number): number;
20
+ export declare function containerWidthFor(grid: CanvasGrid, cell: number, columns?: number, scale?: number): number;
21
+ export declare function breakpointMinWidth(spec: BreakpointSpec): number;
22
+ export declare function breakpointColumns(canvas: Pick<Canvas, 'grid' | 'breakpoints'>, breakpoint: string): number;
23
+ export declare function activeBreakpoint(breakpoints: Breakpoints, width: number): string;
24
+ export declare function resolvePlacement(layout: WidgetLayout, breakpoint: string): Placement;
25
+ export declare function layoutBreakpoints(breakpoints: Breakpoints, widgets: readonly {
26
+ layout: WidgetLayout;
27
+ }[]): string[];
28
+ export declare function gridFootprintsOverlap(a: GridPlacement, b: GridPlacement): boolean;
29
+ export interface PlacedGrid {
30
+ id: string;
31
+ placement: GridPlacement;
32
+ }
33
+ export declare function pushDown(items: readonly PlacedGrid[], first?: readonly string[]): Map<string, GridPlacement>;
34
+ export declare function pushAside(items: readonly PlacedGrid[], first?: readonly string[]): Map<string, GridPlacement>;
35
+ export declare function firstFreeSlot(occupied: readonly GridPlacement[], cols: number, rows: number, columns: number): {
36
+ col: number;
37
+ row: number;
38
+ };
39
+ export interface Rect {
40
+ x: number;
41
+ y: number;
42
+ w: number;
43
+ h: number;
44
+ }
45
+ export declare function spanPx(n: number, cell: number, gutter: number): number;
46
+ export declare function gridRect(p: GridPlacement, cell: number, gutter: Gutter): Rect;
47
+ export declare function anchorFraction(anchor: PlacementAnchor): {
48
+ fx: number;
49
+ fy: number;
50
+ };
51
+ export declare function relativeRect(p: RelativePlacement, host: Rect, cell: number, gutter: Gutter): Rect;
52
+ export declare function layoutRects(widgets: readonly WidgetInstance[], breakpoint: string, cell: number, gutter: Gutter): Map<string, Rect>;
53
+ export declare function resolveFrame(canvasFrame: WidgetFrameSpec | undefined, widgetFrame: WidgetFrameSpec | undefined): ResolvedFrame;
package/dist/history.d.ts CHANGED
@@ -4,5 +4,6 @@ export interface HistoryWindow {
4
4
  endTime: string;
5
5
  intervalSeconds: number;
6
6
  }
7
+ export declare function historyWindowFor(spanSeconds: number, end: number): HistoryWindow;
7
8
  export declare function defaultHistoryWindow(): HistoryWindow;
8
9
  export declare function fetchWidgetHistory(widget: WidgetInstance, window: HistoryWindow): Promise<MeasurementSample[]>;
package/dist/hub.d.ts CHANGED
@@ -1,7 +1,15 @@
1
+ import { type HubConnectionState } from './connection';
2
+ import type { HistoryWindow } from './history';
3
+ import type { MemberRef, MemberSeed, MemberSeedPlan, MemberSink, SeedFailure } from './members';
4
+ import { type MeasurementBucket } from './queries';
1
5
  import type { AlarmRow, AnchorTarget, CommandRow, DatasourceSelector, LocationSample, MeasurementSample } from './types';
2
6
  export interface DeviceResolver {
3
7
  devicesForAnchor(anchor: AnchorTarget): Promise<string[]>;
4
8
  deviceExists(deviceToken: string): Promise<boolean>;
9
+ anchorMembers?(anchor: AnchorTarget): Promise<{
10
+ members: MemberRef[];
11
+ total: number;
12
+ }>;
5
13
  }
6
14
  export interface WidgetStreamSink {
7
15
  next: (sample: MeasurementSample) => void;
@@ -73,6 +81,10 @@ export interface WidgetDataSource {
73
81
  subscribeCommands(subscription: CommandSubscription, sink: CommandStreamSink): () => void;
74
82
  subscribeLocations(subscription: LocationSubscription, sink: LocationStreamSink): () => void;
75
83
  isDatasourceAvailable(datasource: DatasourceSelector | undefined): Promise<boolean>;
84
+ now?(): number;
85
+ bucketMeasurements?(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]>;
86
+ subscribeMembers?(datasource: DatasourceSelector, sink: MemberSink): () => void;
87
+ readMemberSeeds?(deviceTokens: readonly string[], plan: MemberSeedPlan, onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void, cancelled?: () => boolean): Promise<void>;
76
88
  }
77
89
  export declare class DashboardHub implements WidgetDataSource, WidgetActions {
78
90
  private readonly resolver;
@@ -83,8 +95,21 @@ export declare class DashboardHub implements WidgetDataSource, WidgetActions {
83
95
  private readonly alarmDisposers;
84
96
  private readonly commandDisposers;
85
97
  private readonly locationDisposers;
98
+ private readonly connection;
99
+ private readonly deadStreams;
86
100
  constructor(config: DashboardHubConfig);
101
+ get connectionState(): HubConnectionState;
102
+ onConnectionState(listener: () => void): () => void;
87
103
  subscribeWidget(datasource: DatasourceSelector, sink: WidgetStreamSink): () => void;
104
+ subscribeMembers(datasource: DatasourceSelector, sink: MemberSink): () => void;
105
+ private resolveRoster;
106
+ private readonly seedLimiter;
107
+ private readonly seedReads;
108
+ readMemberSeeds(deviceTokens: readonly string[], plan: MemberSeedPlan, onSeed: (deviceToken: string, seed: MemberSeed | null, failure?: SeedFailure) => void, cancelled?: () => boolean): Promise<void>;
109
+ private memberBuckets;
110
+ private readonly bucketReads;
111
+ bucketMeasurements(datasource: DatasourceSelector, window: HistoryWindow): Promise<MeasurementBucket[]>;
112
+ private readBuckets;
88
113
  subscribeAlarms(subscription: AlarmSubscription, sink: AlarmStreamSink): () => void;
89
114
  private resolveAlarmScope;
90
115
  private queryAlarms;
package/dist/index.d.ts CHANGED
@@ -1,21 +1,34 @@
1
- export type { DashboardDefinition, Canvas, CanvasBackground, CanvasGrid, CanvasSizing, Breakpoints, WidgetInstance, KnownWidgetInstance, ExtensionWidgetInstance, ExtensionWidgetType, ResolvedWidgetInstance, UnknownWidgetInstance, UnknownWidgetType, UnresolvedWidgetCode, UnresolvedWidgetReason, WidgetImplementationRef, WidgetType, WidgetLayout, WidgetBox, DatasourceSelector, DeviceSelector, AnchorSelector, AnchorTarget, AnchorAggregation, DevicesSelector, RelatedTraversalSelector, SlotSelector, UnboundSelector, SlotDefinition, SlotScope, SlotBinding, SelectionTarget, SelectionCandidate, EntityCandidateLister, EntityListKind, MeasurementSample, LocationSample, LocationSeries, LocationSelection, AlarmRow, CommandRow, CommandParameter, CommandParamDataType, } from './types';
1
+ export type { DashboardDefinition, BoardTimeRange, Canvas, CanvasBackground, CanvasGrid, CanvasSizing, Breakpoints, BreakpointSpec, GridLength, GridPlacement, RelativePlacement, RelativeSize, Placement, PlacementAnchor, PlacementMode, FrameBorder, FrameBorderWidth, FrameHeader, FrameRadius, FrameVariant, ResolvedFrame, SpaceTokenName, ThemeColorToken, WidgetFrameSpec, WidgetInstance, KnownWidgetInstance, ExtensionWidgetInstance, ExtensionWidgetType, ResolvedWidgetInstance, UnknownWidgetInstance, UnknownWidgetType, UnresolvedWidgetCode, UnresolvedWidgetReason, WidgetImplementationRef, WidgetType, WidgetLayout, DatasourceSelector, DeviceSelector, AnchorSelector, AnchorTarget, AnchorAggregation, DevicesSelector, RelatedTraversalSelector, SlotSelector, UnboundSelector, SlotDefinition, SlotScope, SlotBinding, SelectionTarget, SelectionCandidate, EntityCandidateLister, EntityListKind, MeasurementSample, LocationSample, LocationSeries, LocationSelection, AlarmRow, CommandRow, CommandParameter, CommandParamDataType, } from './types';
2
2
  export { COMMAND_STATUSES, isTerminalCommandStatus, isCancellableCommandStatus, type CommandStatus, } from './command-status';
3
3
  export { effectiveBindings, parseBindingManifest, resolveWidgetDatasource, stripDefaultBindings, type ParsedBindingManifest, } from './bindings';
4
4
  export { resolveContextBindings, hasScopedSlots, applySelection, bindingsWithoutScopedSlots, type MemberResolver, } from './context';
5
5
  export { migrateToSlots, bindWidgetSlot, clearWidgetDatasource, pruneSlots, widgetBinding, widgetSlotName, sameBinding, resolveConcrete, setSlotScope, anchorSlotNames, type ConcreteSelector, } from './slots';
6
6
  export { resolveSlotCandidates } from './candidates';
7
7
  export { createEntityLister } from './entity-lister';
8
- export { WIDGET_TYPES, UNKNOWN_WIDGET_TYPE } from './types';
8
+ export { WIDGET_TYPES, UNKNOWN_WIDGET_TYPE, CURRENT_SCHEMA_VERSION, PLACEMENT_ANCHORS, PLACEMENT_MODES, RESERVED_PLACEMENT_MODES, THEME_COLOR_TOKENS, SPACE_TOKEN_PX, FRAME_VARIANTS, FRAME_RADII, FRAME_BORDER_WIDTHS, FRAME_HEADERS, } from './types';
9
+ export { CANVAS_DENSITIES, DEFAULT_CANVAS_DENSITY, DENSITY_SCALE, isCanvasDensity, type CanvasDensity, } from './types';
10
+ export { sameLabel } from './same-label';
11
+ export { ConnectionTracker, OFFLINE_AFTER_MS, type ConnectionHandle, type HubConnectionState } from './connection';
9
12
  export { DashboardHub, type DashboardHubConfig, type DeviceResolver, type WidgetStreamSink, type WidgetDataSource, type AlarmSubscription, type AlarmSnapshot, type AlarmStreamSink, type CommandSubscription, type CommandSnapshot, type CommandStreamSink, type CommandDispatch, type LocationSubscription, type LocationSnapshot, type LocationStreamSink, type WidgetActions, } from './hub';
10
13
  export { SyntheticDataSource, SYNTHETIC_GENERATORS, type SyntheticGenerator, type SyntheticDataSourceConfig, } from './synthetic';
11
14
  export { RECORDING_FORMAT_VERSION, RECORDING_KIND, RecordingFormatError, parseBoardRecording, recordedWallTimeMs, recordingBounds, simulationScaleAt, type BoardRecording, type RecordedAlarm, type RecordedAlarmEvent, type RecordedAlarmSnapshot, type RecordedChannels, type RecordedChapter, type RecordedClockSegment, type RecordedDevice, type RecordedMeasurementColumns, } from './recording';
12
15
  export { PLAYBACK_RATES, RECORDED_HISTORY_WINDOW_MS, NotInRecordingError, RecordedClock, RecordedDataSource, createRecordedClock, createRecordedLister, createRecordedResolver, type ClockEvent, type ClockTicker, type PlaybackRate, type RecordedChannel, type RecordedSiteOptions, } from './recorded';
13
- export { parseDashboardDefinition, type ParseDefinitionOptions, parseSlotBinding, serializeDefinition, storedDefinition, isDirty, resolveWidgetBox, activeBreakpoint, generateWidgetId, canScopeSlot, DashboardDefinitionError, BASE_BREAKPOINT, } from './definition';
14
- export { baseBox, setWidgetBox, deleteWidget, bringToFront, setTitle, setCanvasGrid, setCanvasSizing, updateWidget, addWidget, gridBoxToPx, pxToGridBox, type PixelRect, type GridGeometry, } from './editor-model';
16
+ export { parseDashboardDefinition, type ParseDefinitionOptions, parseSlotBinding, serializeDefinition, storedDefinition, isDirty, withoutConversionMark, generateWidgetId, canScopeSlot, DashboardDefinitionError, BASE_BREAKPOINT, } from './definition';
17
+ export { basePlacement, clampSpan, moveWidget, resizeWidget, setWidgetPlacement, deleteWidget, setTitle, setCanvasGrid, setCanvasSizing, setCanvasDensity, updateWidget, addWidget, canHostRelative, fitToLimits, formatSpan, OFFSET_STEP, OFFSET_STEP_FINE, placeOnGrid, placeRelative, setRelativeAnchor, setRelativeOffset, snapOffset, gridPlacementToPx, pxToGridPlacement, type EditOptions, type PixelRect, type RelativeBounds, type GridGeometry, } from './editor-model';
18
+ export { DEFAULT_HISTORY_LIMIT, canRedo, canUndo, commitEdit, createEditHistory, rebaseHistory, redoEdit, sealHistory, undoEdit, type CommitOptions, type EditHistory, type HistoryEntry, type Step, } from './editor-history';
19
+ export { WIDGET_CLIPBOARD_FORMAT, WIDGET_CLIPBOARD_VERSION, copyWidget, duplicateWidget, parseWidgetClipboard, pasteWidgets, serializeWidgetClipboard, type ClipboardOrigin, type PasteResult, type RefusedWidget, type UnboundPaste, type WidgetClipboard, } from './editor-clipboard';
15
20
  export { createDeviceResolver } from './resolver';
16
- export { defaultHistoryWindow, fetchWidgetHistory, type HistoryWindow } from './history';
17
- export type { AlarmStreamState, CommandStreamState, LocationStreamState, MeasurementStreamState, } from './channel-snapshots';
18
- export { applyOptionPatch, clampNumberOption, enumValues, numberSpec, validateOptions, HOST_OPTION_CONTROLS, type HostOptionControl, type BooleanOptionSpec, type DefaultsOf, type EnumOptionSpec, type GreaterThanConstraint, type NumberOptionSpec, type OptionConstraint, type OptionIssue, type OptionIssueCode, type OptionKeysOfKind, type OptionSpec, type OptionsOf, type RequiredOptionKeys, type SpecValue, type StringOptionSpec, type ValidateOptionsContext, type WidgetOptionSpecs, } from './option-schema';
19
- export { SDK_API_VERSION, defineWidgetDescriptor, isBuiltinWidgetId, isNamespacedWidgetId, type AckOptionRef, type BuiltinWidgetId, type ChannelBindingPolicy, type ChannelSnapshot, type ConstBinding, type DatasourcePolicy, type DatasourcePolicyFor, type DatasourceSelectorKind, type InteractionsFor, type NamespacedWidgetId, type NumberOptionRef, type StringOptionRef, type WidgetChannel, type WidgetDescriptor, type WidgetDisplay, type WidgetId, type WidgetInteractionDeclaration, } from './widget-descriptor';
21
+ export type { MeasurementBucket } from './queries';
22
+ export { ANCHOR_PAGE_SIZE } from './resolver';
23
+ export type { MemberBucketCriteria, MemberBucketRead, MemberRef, MemberRoster, MemberSeed, MemberSeedPlan, MemberSink, SeedFailure, } from './members';
24
+ export { defaultHistoryWindow, fetchWidgetHistory, historyWindowFor, type HistoryWindow } from './history';
25
+ export { DEFAULT_TIME_RANGE_SECONDS, MAX_SEED_BUCKETS, MAX_TIME_RANGE_SECONDS, MIN_TIME_RANGE_SECONDS, TIME_RANGE_PRESETS, bucketSecondsFor, initialTimeRange, isValidSpanSeconds, parsePausedParam, parseRangeParam, pauseRange, presetIdOf, rangeEnd, rangeWindow, resumeRange, seedBucketCount, setBoardTimeRange, spanLabel, timeRangeFromSearch, withSpan, type RangeWindow, type TimeRangePresetId, type TimeRangeState, } from './time-range';
26
+ export type { AggregateResult, AggregateSpan, AggregateStreamState, AlarmStreamState, CommandStreamState, FleetMember, FleetState, LocationStreamState, MeasurementAggregate, MeasurementStreamState, } from './channel-snapshots';
27
+ export { applyOptionPatch, clampNumberOption, enumValues, numberSpec, validateOptions, HOST_OPTION_CONTROLS, type HostOptionControl, type BooleanOptionSpec, type DefaultsOf, type EnumOptionSpec, type ListItemFieldSpec, type ListOptionSpec, type GreaterThanConstraint, type NumberOptionSpec, type OptionConstraint, type OptionIssue, type OptionIssueCode, type OptionKeysOfKind, type OptionSpec, type OptionsOf, type RequiredOptionKeys, type SpecValue, type StringOptionSpec, type ValidateOptionsContext, type WidgetOptionSpecs, } from './option-schema';
28
+ export { SDK_API_VERSION, defineWidgetDescriptor, isBuiltinWidgetId, isNamespacedWidgetId, type AckOptionRef, type BuiltinWidgetId, type ChannelBindingPolicy, type ChannelSnapshot, type ConstBinding, type DatasourcePolicy, type DatasourcePolicyFor, type DatasourceSelectorKind, type InteractionsFor, type NamespacedWidgetId, type NumberOptionRef, type StringOptionRef, type WidgetChannel, type WidgetDescriptor, type WidgetDisplay, type WidgetId, type WidgetInteractionDeclaration, FALLBACK_DEFAULT_SPAN, pinnedAxes, type WidgetLayoutSpec, type WidgetResizeMode, type WidgetSpan, } from './widget-descriptor';
20
29
  export { WidgetCatalogError, collectCatalogIssues, createWidgetCatalog, describeIssues, validateWidgetDescriptor, type DescriptorIssue, type WidgetCatalog, } from './widget-catalog';
21
30
  export { isBuiltinWidget, isResolvedWidget, parseWidgetRef, checkWidgetCompatibility, widgetVersionNotice, unresolvedWidgets, type UnresolvedWidgetReport, } from './widget-compat';
31
+ export { DEFAULT_GRID, PHONE_MIN_COLUMNS, activeBreakpoint, anchorFraction, breakpointColumns, breakpointMinWidth, cellSizeAt, containerWidthFor, firstFreeSlot, gridFootprintsOverlap, gridLengthPx, gridRect, gutterPx, isSpaceToken, layoutBreakpoints, layoutRects, paddingPx, pushDown, pushAside, relativeRect, resolveFrame, resolvePlacement, spanPx, type Gutter, type Insets, type PlacedGrid, type Rect, } from './grid';
32
+ export { effectiveLayout, layoutShape, liveColumns, reflowGrid, STACK_BELOW_WIDTH, type EffectiveLayout, type LayoutShape, type ReflowItem, } from './reflow';
33
+ export { LEGACY_REFERENCE_WIDTH } from './legacy-grid';
34
+ export { blockingLayoutFindings, lintLayout, PUBLISH_BLOCKING_LAYOUT_CODES, spanLimitsOfLayout, type LayoutFinding, type LayoutLintCode, type LayoutLintOptions, type LayoutLintSeverity, type SpanLimits, } from './layout-lint';