@glasshome/sync-layer 0.1.8 → 0.1.9

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.
@@ -27,7 +27,7 @@ export declare function unloadDemoData(): void;
27
27
  * When a service call is made in demo mode, this updates the entity
28
28
  * state locally (no WebSocket message sent). State resets on page refresh.
29
29
  */
30
- export declare function applyDemoServiceCall(domain: string, service: string, _serviceData?: Record<string, any>, target?: {
30
+ export declare function applyDemoServiceCall(_domain: string, service: string, _serviceData?: Record<string, any>, target?: {
31
31
  entity_id?: string | string[];
32
32
  }): void;
33
33
  export { createDemoFixtures } from "./demo-data";
@@ -74,6 +74,202 @@ function makeEntity(entity_id, state$1, attributes = {}) {
74
74
  context: ctx()
75
75
  };
76
76
  }
77
+ /**
78
+ * One entity per weather scene the widget can render. Entity IDs use the
79
+ * `weather.demo_<slug>` form so a demo dashboard can reference them directly.
80
+ */
81
+ const WEATHER_FIXTURES = [
82
+ {
83
+ slug: "sunny",
84
+ state: "sunny",
85
+ temp: 28,
86
+ apparent: 31,
87
+ humidity: 35,
88
+ pressure: 1018,
89
+ wind: 8,
90
+ low: 18
91
+ },
92
+ {
93
+ slug: "clear_night",
94
+ state: "clear-night",
95
+ temp: 14,
96
+ apparent: 12,
97
+ humidity: 55,
98
+ pressure: 1016,
99
+ wind: 5,
100
+ low: 9
101
+ },
102
+ {
103
+ slug: "cloudy",
104
+ state: "cloudy",
105
+ temp: 17,
106
+ apparent: 16,
107
+ humidity: 72,
108
+ pressure: 1010,
109
+ wind: 14,
110
+ low: 11
111
+ },
112
+ {
113
+ slug: "partly_cloudy",
114
+ state: "partlycloudy",
115
+ temp: 23,
116
+ apparent: 24,
117
+ humidity: 50,
118
+ pressure: 1014,
119
+ wind: 12,
120
+ low: 15
121
+ },
122
+ {
123
+ slug: "rainy",
124
+ state: "rainy",
125
+ temp: 12,
126
+ apparent: 10,
127
+ humidity: 88,
128
+ pressure: 1004,
129
+ wind: 18,
130
+ low: 8
131
+ },
132
+ {
133
+ slug: "pouring",
134
+ state: "pouring",
135
+ temp: 11,
136
+ apparent: 8,
137
+ humidity: 95,
138
+ pressure: 998,
139
+ wind: 26,
140
+ low: 7
141
+ },
142
+ {
143
+ slug: "snowy",
144
+ state: "snowy",
145
+ temp: -2,
146
+ apparent: -6,
147
+ humidity: 80,
148
+ pressure: 1020,
149
+ wind: 10,
150
+ low: -7
151
+ },
152
+ {
153
+ slug: "snowy_rainy",
154
+ state: "snowy-rainy",
155
+ temp: 1,
156
+ apparent: -2,
157
+ humidity: 92,
158
+ pressure: 1006,
159
+ wind: 16,
160
+ low: -2
161
+ },
162
+ {
163
+ slug: "lightning",
164
+ state: "lightning",
165
+ temp: 22,
166
+ apparent: 24,
167
+ humidity: 78,
168
+ pressure: 1001,
169
+ wind: 22,
170
+ low: 17
171
+ },
172
+ {
173
+ slug: "lightning_rainy",
174
+ state: "lightning-rainy",
175
+ temp: 19,
176
+ apparent: 18,
177
+ humidity: 90,
178
+ pressure: 996,
179
+ wind: 28,
180
+ low: 14
181
+ },
182
+ {
183
+ slug: "fog",
184
+ state: "fog",
185
+ temp: 8,
186
+ apparent: 6,
187
+ humidity: 98,
188
+ pressure: 1015,
189
+ wind: 4,
190
+ low: 6
191
+ },
192
+ {
193
+ slug: "hail",
194
+ state: "hail",
195
+ temp: 6,
196
+ apparent: 3,
197
+ humidity: 84,
198
+ pressure: 1002,
199
+ wind: 20,
200
+ low: 1
201
+ },
202
+ {
203
+ slug: "windy",
204
+ state: "windy",
205
+ temp: 18,
206
+ apparent: 15,
207
+ humidity: 60,
208
+ pressure: 1009,
209
+ wind: 42,
210
+ low: 12
211
+ },
212
+ {
213
+ slug: "exceptional",
214
+ state: "exceptional",
215
+ temp: 38,
216
+ apparent: 44,
217
+ humidity: 22,
218
+ pressure: 1005,
219
+ wind: 30,
220
+ low: 28
221
+ }
222
+ ];
223
+ function makeWeatherEntity(w) {
224
+ const today = /* @__PURE__ */ new Date();
225
+ const day = (n) => new Date(today.getTime() + n * 864e5).toISOString();
226
+ return makeEntity(`weather.demo_${w.slug}`, w.state, {
227
+ temperature: w.temp,
228
+ temperature_unit: "°C",
229
+ apparent_temperature: w.apparent ?? w.temp,
230
+ humidity: w.humidity,
231
+ pressure: w.pressure,
232
+ pressure_unit: "hPa",
233
+ wind_speed: w.wind,
234
+ wind_speed_unit: "km/h",
235
+ wind_bearing: w.bearing ?? 220,
236
+ visibility: 10,
237
+ visibility_unit: "km",
238
+ uv_index: w.state === "sunny" ? 8 : 2,
239
+ forecast: [
240
+ {
241
+ datetime: day(0),
242
+ condition: w.state,
243
+ temperature: w.temp,
244
+ templow: w.low
245
+ },
246
+ {
247
+ datetime: day(1),
248
+ condition: "partlycloudy",
249
+ temperature: w.temp + 2,
250
+ templow: w.low
251
+ },
252
+ {
253
+ datetime: day(2),
254
+ condition: "cloudy",
255
+ temperature: w.temp + 1,
256
+ templow: w.low - 1
257
+ },
258
+ {
259
+ datetime: day(3),
260
+ condition: "sunny",
261
+ temperature: w.temp + 4,
262
+ templow: w.low + 1
263
+ },
264
+ {
265
+ datetime: day(4),
266
+ condition: "rainy",
267
+ temperature: w.temp - 2,
268
+ templow: w.low - 2
269
+ }
270
+ ]
271
+ });
272
+ }
77
273
  function friendlyName(entityId) {
78
274
  const objectId = entityId.split(".")[1] ?? entityId;
79
275
  return objectId.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
@@ -372,48 +568,7 @@ function createDemoFixtures() {
372
568
  area_id: "living_room",
373
569
  supported_features: 152461
374
570
  });
375
- add(makeEntity("weather.home_forecast", "partlycloudy", {
376
- temperature: 3,
377
- temperature_unit: "°C",
378
- humidity: 72,
379
- pressure: 1013,
380
- pressure_unit: "hPa",
381
- wind_speed: 12,
382
- wind_speed_unit: "km/h",
383
- wind_bearing: 220,
384
- forecast: [
385
- {
386
- datetime: "2026-03-07T00:00:00Z",
387
- condition: "cloudy",
388
- temperature: 4,
389
- templow: -1
390
- },
391
- {
392
- datetime: "2026-03-08T00:00:00Z",
393
- condition: "sunny",
394
- temperature: 7,
395
- templow: 1
396
- },
397
- {
398
- datetime: "2026-03-09T00:00:00Z",
399
- condition: "rainy",
400
- temperature: 5,
401
- templow: 2
402
- },
403
- {
404
- datetime: "2026-03-10T00:00:00Z",
405
- condition: "partlycloudy",
406
- temperature: 6,
407
- templow: 0
408
- },
409
- {
410
- datetime: "2026-03-11T00:00:00Z",
411
- condition: "snowy",
412
- temperature: 1,
413
- templow: -3
414
- }
415
- ]
416
- }), { platform: "met" });
571
+ for (const w of WEATHER_FIXTURES) add(makeWeatherEntity(w), { platform: "met" });
417
572
  add(makeEntity("camera.front_door_camera", "streaming", {
418
573
  entity_picture: "/api/camera_proxy/camera.front_door_camera",
419
574
  frontend_stream_type: "hls",
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createDemoFixtures, resetStore, setState, state } from "./demo-data-gELwUOS_.js";
1
+ import { createDemoFixtures, resetStore, setState, state } from "./demo-data-zwOczByr.js";
2
2
  import { forceResubscribe, setManagerConnection, setResubscribeHandler } from "./subscription-manager-gR0qpDYZ.js";
3
3
  import { callService as callService$1, createConnection, createLongLivedTokenAuth, getAuth } from "home-assistant-js-websocket";
4
4
  import { produce, reconcile } from "solid-js/store";
@@ -447,7 +447,7 @@ function unloadDemoData() {
447
447
  * When a service call is made in demo mode, this updates the entity
448
448
  * state locally (no WebSocket message sent). State resets on page refresh.
449
449
  */
450
- function applyDemoServiceCall(domain, service, _serviceData = {}, target = {}) {
450
+ function applyDemoServiceCall(_domain, service, _serviceData = {}, target = {}) {
451
451
  if (!_isDemoMode) return;
452
452
  const entityIds = Array.isArray(target.entity_id) ? target.entity_id : target.entity_id ? [target.entity_id] : [];
453
453
  for (const entityId of entityIds) {
@@ -1538,7 +1538,7 @@ function buildQuerySignature(query) {
1538
1538
  /**
1539
1539
  * Execute query (no caching -- SolidJS createMemo handles this in Plan 03)
1540
1540
  */
1541
- function memoizedExecute(signature, compute) {
1541
+ function memoizedExecute(_signature, compute) {
1542
1542
  return compute();
1543
1543
  }
1544
1544
 
@@ -2061,9 +2061,50 @@ async function sendWebRtcCandidate(entityId, sessionId, candidate) {
2061
2061
  //#endregion
2062
2062
  //#region src/weather/fetch.ts
2063
2063
  /**
2064
+ * In demo mode, derive forecasts from the entity's `forecast` attribute.
2065
+ * Hourly and daily are synthesised from the same source (daily entries
2066
+ * expanded into 12 hourly steps for the chart).
2067
+ */
2068
+ function buildDemoForecast(entityId, type) {
2069
+ const entity$1 = state.entities[entityId];
2070
+ const raw = entity$1?.attributes?.forecast ?? [];
2071
+ if (raw.length === 0) return [];
2072
+ if (type === "daily" || type === "twice_daily") return raw.map((d) => {
2073
+ const templow = d.templow;
2074
+ return {
2075
+ ...d,
2076
+ temp_high: d.temp_high ?? d.temperature,
2077
+ temp_low: d.temp_low ?? templow
2078
+ };
2079
+ });
2080
+ const start = /* @__PURE__ */ new Date();
2081
+ const today = raw[0];
2082
+ const tomorrow = raw[1] ?? today;
2083
+ const tHi = today?.temperature ?? 18;
2084
+ const tLo = today?.templow ?? tHi - 5;
2085
+ const tNext = tomorrow?.temperature ?? tHi;
2086
+ const out = [];
2087
+ for (let i = 0; i < 24; i++) {
2088
+ const dt = new Date(start.getTime() + i * 60 * 60 * 1e3);
2089
+ const hour = dt.getHours();
2090
+ const dayMix = i < 12 ? 0 : (i - 12) / 12;
2091
+ const baseHi = tHi * (1 - dayMix) + tNext * dayMix;
2092
+ const phase = (hour - 6) / 24 * Math.PI * 2;
2093
+ const swing = (baseHi - tLo) / 2;
2094
+ const mid = (baseHi + tLo) / 2;
2095
+ out.push({
2096
+ datetime: dt.toISOString(),
2097
+ temperature: Math.round((mid + Math.sin(phase) * swing) * 10) / 10,
2098
+ condition: today?.condition ?? entity$1?.state ?? "cloudy"
2099
+ });
2100
+ }
2101
+ return out;
2102
+ }
2103
+ /**
2064
2104
  * Fetch forecast for a single entity and type
2065
2105
  */
2066
2106
  async function fetchForecast(entityId, type) {
2107
+ if (isDemoMode()) return buildDemoForecast(entityId, type);
2067
2108
  const conn = state.conn;
2068
2109
  if (!conn) throw new Error("Not connected");
2069
2110
  return new Promise((resolve, reject) => {
@@ -11,7 +11,7 @@ export declare function buildQuerySignature(query: QueryState): string;
11
11
  /**
12
12
  * Execute query (no caching -- SolidJS createMemo handles this in Plan 03)
13
13
  */
14
- export declare function memoizedExecute<T>(signature: string, compute: () => T): T;
14
+ export declare function memoizedExecute<T>(_signature: string, compute: () => T): T;
15
15
  /**
16
16
  * Check if cache should be invalidated
17
17
  */
@@ -107,7 +107,12 @@ function useAreas() {
107
107
  }).filter((v) => v !== void 0);
108
108
  }, void 0, { equals: (a, b) => {
109
109
  if (a.length !== b.length) return false;
110
- for (let i = 0; i < a.length; i++) if (a[i].id !== b[i].id || a[i].modifiedAt !== b[i].modifiedAt || a[i].entities.length !== b[i].entities.length) return false;
110
+ for (let i = 0; i < a.length; i++) {
111
+ const aItem = a[i];
112
+ const bItem = b[i];
113
+ if (!aItem || !bItem) return false;
114
+ if (aItem.id !== bItem.id || aItem.modifiedAt !== bItem.modifiedAt || aItem.entities.length !== bItem.entities.length) return false;
115
+ }
111
116
  return true;
112
117
  } });
113
118
  }
@@ -1,4 +1,4 @@
1
- import { createDemoFixtures, state } from "../demo-data-gELwUOS_.js";
1
+ import { createDemoFixtures, state } from "../demo-data-zwOczByr.js";
2
2
 
3
3
  //#region src/testing/mock-behaviors/base.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glasshome/sync-layer",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "Type-safe, reactive state synchronization layer for Home Assistant",
5
5
  "repository": {
6
6
  "type": "git",
@@ -44,7 +44,7 @@
44
44
  "dist"
45
45
  ],
46
46
  "scripts": {
47
- "build": "tsdown",
47
+ "build": "tsdown && tsc --noEmit",
48
48
  "dev": "tsdown --watch",
49
49
  "test": "bun test",
50
50
  "lint": "tsc --noEmit"
@@ -53,7 +53,9 @@
53
53
  "home-assistant-js-websocket": "^9.6.0"
54
54
  },
55
55
  "devDependencies": {
56
+ "@glasshome/ha-types": "0.1.0",
56
57
  "@types/bun": "latest",
58
+ "solid-js": "^1.9.0",
57
59
  "tsdown": "^0.2.0",
58
60
  "typescript": "^5"
59
61
  },