@camstack/types 1.2.446 → 1.2.448

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/addon.js +3 -2
  3. package/dist/addon.mjs +3 -2
  4. package/dist/automation/action.d.ts +2 -0
  5. package/dist/automation/index.d.ts +3 -1
  6. package/dist/automation/normalize-units.d.ts +59 -0
  7. package/dist/automation/recipe.d.ts +15 -1
  8. package/dist/capabilities/action-source.cap.d.ts +1 -0
  9. package/dist/capabilities/actions.cap.d.ts +32 -1
  10. package/dist/capabilities/core-blocks.cap.d.ts +17 -0
  11. package/dist/capabilities/index.d.ts +3 -1
  12. package/dist/capabilities/remote-layout-components.cap.d.ts +81 -0
  13. package/dist/capabilities/remote.cap.d.ts +2 -0
  14. package/dist/client.js +1 -2
  15. package/dist/client.mjs +1 -2
  16. package/dist/device/cap-vocabulary/types.d.ts +9 -1
  17. package/dist/generated/addon-api.d.ts +15 -0
  18. package/dist/generated/capability-router-map.d.ts +5 -2
  19. package/dist/generated/collection-array-methods.d.ts +1 -1
  20. package/dist/generated/method-access-map.d.ts +1 -1
  21. package/dist/generated/system-proxy.d.ts +2 -0
  22. package/dist/index.d.ts +6 -1
  23. package/dist/index.js +1786 -733
  24. package/dist/index.mjs +1750 -734
  25. package/dist/interfaces/event-bus.d.ts +9 -0
  26. package/dist/remote/remote-builtin-components.d.ts +15 -0
  27. package/dist/remote/remote-components.d.ts +109 -0
  28. package/dist/remote/remote-config.d.ts +121 -0
  29. package/dist/remote/remote-layout-ops.d.ts +68 -0
  30. package/dist/{sleep-6eG9C8Xl.mjs → sleep-63gM_1JR.mjs} +1 -0
  31. package/dist/{sleep-UXDHn-oJ.js → sleep-BSPhInRm.js} +1 -0
  32. package/dist/units/index.d.ts +3 -0
  33. package/dist/units/quantity-input.d.ts +58 -0
  34. package/dist/units/quantity-units.d.ts +8 -0
  35. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_sleep = require("./sleep-UXDHn-oJ.js");
2
+ const require_sleep = require("./sleep-BSPhInRm.js");
3
3
  const require_event_category = require("./event-category-E17zaHjN.js");
4
4
  const require_composition = require("./composition-CO6Sak-s.js");
5
5
  const require_node_color = require("./node-color.js");
@@ -8060,6 +8060,791 @@ var ExpressionSourceSchema = zod.z.object({
8060
8060
  });
8061
8061
  });
8062
8062
  //#endregion
8063
+ //#region src/device/schema-fields.ts
8064
+ /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
8065
+ * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
8066
+ function unwrapSchema(schema) {
8067
+ let s = schema;
8068
+ while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
8069
+ return s;
8070
+ }
8071
+ function isMetaBag(value) {
8072
+ return typeof value === "object" && value !== null;
8073
+ }
8074
+ function metaQuantity(schema) {
8075
+ const bag = zod.z.globalRegistry.get(schema);
8076
+ if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
8077
+ }
8078
+ /** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
8079
+ function quantityOfField(schema) {
8080
+ return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
8081
+ }
8082
+ function leafKind(schema) {
8083
+ const s = unwrapSchema(schema);
8084
+ if (s instanceof zod.z.ZodEnum) return {
8085
+ kind: "enum",
8086
+ enumValues: s.options
8087
+ };
8088
+ if (s instanceof zod.z.ZodNumber) return { kind: "number" };
8089
+ if (s instanceof zod.z.ZodString) return { kind: "string" };
8090
+ if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
8091
+ return null;
8092
+ }
8093
+ /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
8094
+ * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
8095
+ * so fields with null live values are still offered. Arrays / unknown shapes
8096
+ * are skipped — never throws. */
8097
+ function enumerateSchemaFields(schema, prefix = "") {
8098
+ const root = unwrapSchema(schema);
8099
+ if (!(root instanceof zod.z.ZodObject)) return [];
8100
+ const out = [];
8101
+ const shape = root.shape;
8102
+ for (const [key, field] of Object.entries(shape)) {
8103
+ const path = prefix ? `${prefix}.${key}` : key;
8104
+ const inner = unwrapSchema(field);
8105
+ if (inner instanceof zod.z.ZodObject) {
8106
+ out.push(...enumerateSchemaFields(inner, path));
8107
+ continue;
8108
+ }
8109
+ const leaf = leafKind(field);
8110
+ if (leaf) {
8111
+ const quantity = quantityOfField(field);
8112
+ out.push({
8113
+ path,
8114
+ kind: leaf.kind,
8115
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
8116
+ ...quantity !== void 0 ? { quantity } : {}
8117
+ });
8118
+ }
8119
+ }
8120
+ return out;
8121
+ }
8122
+ /** Enumerate the per-item wireable fields of an item-array cap (see
8123
+ * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
8124
+ * `keyField` (the key comes from the link's `itemKey`, never from a wired
8125
+ * source), each tagged `item: true` so the authoring UI collects an
8126
+ * `itemKey` alongside the field. Never throws. */
8127
+ function enumerateItemArrayFields(itemArray) {
8128
+ return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
8129
+ ...f,
8130
+ item: true
8131
+ }));
8132
+ }
8133
+ //#endregion
8134
+ //#region src/device/source-info.ts
8135
+ /**
8136
+ * Identity envelope for a device's upstream-system metadata.
8137
+ *
8138
+ * Two jobs:
8139
+ *
8140
+ * 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
8141
+ * uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
8142
+ * WS-Discovery endpoint UUID, …). Provider code keeps a
8143
+ * `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
8144
+ * without round-tripping through `stableId` / `id` mappings.
8145
+ *
8146
+ * 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
8147
+ * one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
8148
+ * for vendors where MAC is the natural key). For systems where the
8149
+ * live dispatch key can be renamed (HA `entity_id` can be edited),
8150
+ * providers detect renames by matching `uniqueId` and patch `id`
8151
+ * in-place so dispatch keeps working.
8152
+ *
8153
+ * **Display unit and precision are NOT stored here.** They are a read-time
8154
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
8155
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
8156
+ * neither is repeated on each sample. The `device_class` tag is an
8157
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
8158
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
8159
+ *
8160
+ * The `raw` escape hatch carries provider-specific attributes that don't
8161
+ * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
8162
+ * details, …). Providers control what lands here.
8163
+ */
8164
+ var SourceInfoSchema = zod.z.object({
8165
+ /** Live dispatch key — mutable when the source system allows rename. */
8166
+ id: zod.z.string(),
8167
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
8168
+ system: zod.z.string(),
8169
+ /** Immutable upstream identifier when available (HA `unique_id`, … ).
8170
+ * Used to detect rename when `id` changes. */
8171
+ uniqueId: zod.z.string().optional(),
8172
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
8173
+ quantity: require_sleep.ReadingQuantitySchema.optional(),
8174
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
8175
+ dateTimeShape: zod.z.enum([
8176
+ "date",
8177
+ "time",
8178
+ "datetime"
8179
+ ]).optional(),
8180
+ /** Provider-specific extras that don't fit the structured slots.
8181
+ * Whitelist what goes here per provider — do NOT dump entire HA
8182
+ * attribute blobs (would explode the meta JSON column). */
8183
+ raw: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
8184
+ });
8185
+ /**
8186
+ * Build the synthetic SourceInfo every existing provider falls back to
8187
+ * when no upstream value has been persisted. Keeps non-migrated providers
8188
+ * (Reolink / Hikvision / ONVIF / RTSP) functional without code
8189
+ * changes — `id` reuses the CamStack stableId, `system` reuses the addon
8190
+ * id. Real upstream identity replaces this once a provider migrates and
8191
+ * calls `updateSourceInfo()` with concrete values.
8192
+ */
8193
+ function synthesizeSourceInfo(input) {
8194
+ return {
8195
+ id: input.stableId,
8196
+ system: input.addonId
8197
+ };
8198
+ }
8199
+ /**
8200
+ * Shallow-merge a partial patch over a prior SourceInfo. `undefined`
8201
+ * patch values are ignored (no field clobber); to clear a field
8202
+ * explicitly, providers omit it from the patch and rebuild from scratch
8203
+ * via `updateSourceInfo({})` cycles — uncommon enough that we don't
8204
+ * bother with a `null`-means-delete sentinel.
8205
+ *
8206
+ * The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
8207
+ * merges over existing `raw` keys instead of replacing the whole bag.
8208
+ */
8209
+ function mergeSourceInfo(prev, patch) {
8210
+ const next = { ...prev };
8211
+ for (const [k, v] of Object.entries(patch)) {
8212
+ if (v === void 0) continue;
8213
+ if (k === "raw" && typeof v === "object" && v !== null) {
8214
+ next.raw = {
8215
+ ...prev.raw,
8216
+ ...v
8217
+ };
8218
+ continue;
8219
+ }
8220
+ next[k] = v;
8221
+ }
8222
+ return next;
8223
+ }
8224
+ /**
8225
+ * Coerce common unit-of-measurement string variants to a canonical form
8226
+ * so the UI's display templates can rely on stable strings. Pure
8227
+ * passthrough for unknown units — kept conservative on purpose, the goal
8228
+ * is to normalise the long tail of HA `unit_of_measurement` values that
8229
+ * have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
8230
+ *
8231
+ * Extend the map as we encounter real-world data. Returning the raw
8232
+ * input for unknown values is intentional: providers should never
8233
+ * lose information silently here.
8234
+ */
8235
+ function normalizeUnit(raw) {
8236
+ if (raw == null) return void 0;
8237
+ const trimmed = raw.trim();
8238
+ if (trimmed === "") return void 0;
8239
+ const lower = trimmed.toLowerCase();
8240
+ if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
8241
+ if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
8242
+ if (lower === "k" || lower === "kelvin") return "K";
8243
+ if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
8244
+ if (lower === "lx" || lower === "lux") return "lx";
8245
+ if (lower === "hpa" || lower === "hectopascal") return "hPa";
8246
+ if (lower === "kpa") return "kPa";
8247
+ if (lower === "pa") return "Pa";
8248
+ if (lower === "mbar" || lower === "millibar") return "mbar";
8249
+ if (lower === "w" || lower === "watt") return "W";
8250
+ if (lower === "kw" || lower === "kilowatt") return "kW";
8251
+ if (lower === "wh" || lower === "watt-hour") return "Wh";
8252
+ if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
8253
+ if (lower === "v" || lower === "volt") return "V";
8254
+ if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
8255
+ if (lower === "ma" || lower === "milliampere") return "mA";
8256
+ if (lower === "va") return "VA";
8257
+ if (lower === "ppm") return "ppm";
8258
+ if (lower === "ppb") return "ppb";
8259
+ if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
8260
+ if (lower === "km/h" || lower === "kph") return "km/h";
8261
+ if (lower === "mph") return "mph";
8262
+ if (lower === "m/s") return "m/s";
8263
+ if (lower === "m") return "m";
8264
+ if (lower === "m²" || lower === "m2") return "m²";
8265
+ if (lower === "mm") return "mm";
8266
+ if (lower === "cm") return "cm";
8267
+ if (lower === "km") return "km";
8268
+ if (lower === "in" || lower === "inch" || lower === "inches") return "in";
8269
+ if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
8270
+ if (lower === "kg") return "kg";
8271
+ if (lower === "g") return "g";
8272
+ if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
8273
+ if (lower === "ml") return "mL";
8274
+ if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
8275
+ if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
8276
+ if (lower === "ms") return "ms";
8277
+ if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
8278
+ if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
8279
+ if (lower === "d" || lower === "day" || lower === "days") return "d";
8280
+ if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
8281
+ if (lower === "kib") return "KiB";
8282
+ if (lower === "mib") return "MiB";
8283
+ if (lower === "gib") return "GiB";
8284
+ if (lower === "tib") return "TiB";
8285
+ if (lower === "kb") return "kB";
8286
+ if (lower === "mb") return "MB";
8287
+ if (lower === "gb") return "GB";
8288
+ if (lower === "dbm") return "dBm";
8289
+ if (lower === "dbfs") return "dBFS";
8290
+ if (lower === "inhg") return "inHg";
8291
+ if (lower === "mm/h") return "mm/h";
8292
+ if (lower === "w/m²" || lower === "w/m2") return "W/m²";
8293
+ if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
8294
+ if (lower === "kbps") return "kbps";
8295
+ if (lower === "fps") return "fps";
8296
+ return trimmed;
8297
+ }
8298
+ /**
8299
+ * Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
8300
+ * blob is a free-form `Record<string, unknown>` shared with hardware-
8301
+ * identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
8302
+ * lives under a single nested key to keep it composable with those.
8303
+ */
8304
+ var SOURCE_INFO_METADATA_KEY = "sourceInfo";
8305
+ /**
8306
+ * Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
8307
+ * with Zod. Returns `null` when the blob is missing or the nested
8308
+ * `sourceInfo` key fails validation — callers fall back to the
8309
+ * synthetic default in that case (see `synthesizeSourceInfo`).
8310
+ */
8311
+ function extractSourceInfoFromMetadata(metadata) {
8312
+ if (!metadata) return null;
8313
+ const raw = metadata[SOURCE_INFO_METADATA_KEY];
8314
+ if (!raw) return null;
8315
+ const parsed = SourceInfoSchema.safeParse(raw);
8316
+ if (!parsed.success) return null;
8317
+ return parsed.data;
8318
+ }
8319
+ //#endregion
8320
+ //#region src/units/unit-table.ts
8321
+ /**
8322
+ * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
8323
+ * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
8324
+ * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
8325
+ * decimals). K derives from `°C = K − 273.15`.
8326
+ */
8327
+ var UNIT_TABLE = {
8328
+ "°C": {
8329
+ dimension: "temperature",
8330
+ factor: 1,
8331
+ offset: 0
8332
+ },
8333
+ "°F": {
8334
+ dimension: "temperature",
8335
+ factor: 5 / 9,
8336
+ offset: -160 / 9
8337
+ },
8338
+ K: {
8339
+ dimension: "temperature",
8340
+ factor: 1,
8341
+ offset: -273.15
8342
+ },
8343
+ hPa: {
8344
+ dimension: "pressure",
8345
+ factor: 1,
8346
+ offset: 0
8347
+ },
8348
+ kPa: {
8349
+ dimension: "pressure",
8350
+ factor: 10,
8351
+ offset: 0
8352
+ },
8353
+ Pa: {
8354
+ dimension: "pressure",
8355
+ factor: .01,
8356
+ offset: 0
8357
+ },
8358
+ mbar: {
8359
+ dimension: "pressure",
8360
+ factor: 1,
8361
+ offset: 0
8362
+ },
8363
+ bar: {
8364
+ dimension: "pressure",
8365
+ factor: 1e3,
8366
+ offset: 0
8367
+ },
8368
+ inHg: {
8369
+ dimension: "pressure",
8370
+ factor: 33.8639,
8371
+ offset: 0
8372
+ },
8373
+ mmHg: {
8374
+ dimension: "pressure",
8375
+ factor: 1.33322,
8376
+ offset: 0
8377
+ },
8378
+ psi: {
8379
+ dimension: "pressure",
8380
+ factor: 68.9476,
8381
+ offset: 0
8382
+ },
8383
+ "m/s": {
8384
+ dimension: "speed",
8385
+ factor: 1,
8386
+ offset: 0
8387
+ },
8388
+ "km/h": {
8389
+ dimension: "speed",
8390
+ factor: 1 / 3.6,
8391
+ offset: 0
8392
+ },
8393
+ mph: {
8394
+ dimension: "speed",
8395
+ factor: .44704,
8396
+ offset: 0
8397
+ },
8398
+ kn: {
8399
+ dimension: "speed",
8400
+ factor: .514444,
8401
+ offset: 0
8402
+ },
8403
+ "mm/h": {
8404
+ dimension: "precipitation-rate",
8405
+ factor: 1,
8406
+ offset: 0
8407
+ },
8408
+ "in/h": {
8409
+ dimension: "precipitation-rate",
8410
+ factor: 25.4,
8411
+ offset: 0
8412
+ },
8413
+ m: {
8414
+ dimension: "length",
8415
+ factor: 1,
8416
+ offset: 0
8417
+ },
8418
+ mm: {
8419
+ dimension: "length",
8420
+ factor: .001,
8421
+ offset: 0
8422
+ },
8423
+ cm: {
8424
+ dimension: "length",
8425
+ factor: .01,
8426
+ offset: 0
8427
+ },
8428
+ km: {
8429
+ dimension: "length",
8430
+ factor: 1e3,
8431
+ offset: 0
8432
+ },
8433
+ in: {
8434
+ dimension: "length",
8435
+ factor: .0254,
8436
+ offset: 0
8437
+ },
8438
+ ft: {
8439
+ dimension: "length",
8440
+ factor: .3048,
8441
+ offset: 0
8442
+ },
8443
+ mi: {
8444
+ dimension: "length",
8445
+ factor: 1609.344,
8446
+ offset: 0
8447
+ },
8448
+ "m²": {
8449
+ dimension: "area",
8450
+ factor: 1,
8451
+ offset: 0
8452
+ },
8453
+ lx: {
8454
+ dimension: "illuminance",
8455
+ factor: 1,
8456
+ offset: 0
8457
+ },
8458
+ "W/m²": {
8459
+ dimension: "irradiance",
8460
+ factor: 1,
8461
+ offset: 0
8462
+ },
8463
+ W: {
8464
+ dimension: "power",
8465
+ factor: 1,
8466
+ offset: 0
8467
+ },
8468
+ kW: {
8469
+ dimension: "power",
8470
+ factor: 1e3,
8471
+ offset: 0
8472
+ },
8473
+ VA: {
8474
+ dimension: "apparent-power",
8475
+ factor: 1,
8476
+ offset: 0
8477
+ },
8478
+ Wh: {
8479
+ dimension: "energy",
8480
+ factor: 1,
8481
+ offset: 0
8482
+ },
8483
+ kWh: {
8484
+ dimension: "energy",
8485
+ factor: 1e3,
8486
+ offset: 0
8487
+ },
8488
+ MWh: {
8489
+ dimension: "energy",
8490
+ factor: 1e6,
8491
+ offset: 0
8492
+ },
8493
+ V: {
8494
+ dimension: "voltage",
8495
+ factor: 1,
8496
+ offset: 0
8497
+ },
8498
+ mV: {
8499
+ dimension: "voltage",
8500
+ factor: .001,
8501
+ offset: 0
8502
+ },
8503
+ A: {
8504
+ dimension: "current",
8505
+ factor: 1,
8506
+ offset: 0
8507
+ },
8508
+ mA: {
8509
+ dimension: "current",
8510
+ factor: .001,
8511
+ offset: 0
8512
+ },
8513
+ ppm: {
8514
+ dimension: "concentration-volume",
8515
+ factor: 1,
8516
+ offset: 0
8517
+ },
8518
+ ppb: {
8519
+ dimension: "concentration-volume",
8520
+ factor: .001,
8521
+ offset: 0
8522
+ },
8523
+ "µg/m³": {
8524
+ dimension: "concentration-mass",
8525
+ factor: 1,
8526
+ offset: 0
8527
+ },
8528
+ "mg/m³": {
8529
+ dimension: "concentration-mass",
8530
+ factor: 1e3,
8531
+ offset: 0
8532
+ },
8533
+ kg: {
8534
+ dimension: "mass",
8535
+ factor: 1,
8536
+ offset: 0
8537
+ },
8538
+ g: {
8539
+ dimension: "mass",
8540
+ factor: .001,
8541
+ offset: 0
8542
+ },
8543
+ lb: {
8544
+ dimension: "mass",
8545
+ factor: .453592,
8546
+ offset: 0
8547
+ },
8548
+ oz: {
8549
+ dimension: "mass",
8550
+ factor: .0283495,
8551
+ offset: 0
8552
+ },
8553
+ "%": {
8554
+ dimension: "percentage",
8555
+ factor: 1,
8556
+ offset: 0
8557
+ },
8558
+ L: {
8559
+ dimension: "volume",
8560
+ factor: 1,
8561
+ offset: 0
8562
+ },
8563
+ mL: {
8564
+ dimension: "volume",
8565
+ factor: .001,
8566
+ offset: 0
8567
+ },
8568
+ gal: {
8569
+ dimension: "volume",
8570
+ factor: 3.785411784,
8571
+ offset: 0
8572
+ },
8573
+ s: {
8574
+ dimension: "duration",
8575
+ factor: 1,
8576
+ offset: 0
8577
+ },
8578
+ ms: {
8579
+ dimension: "duration",
8580
+ factor: .001,
8581
+ offset: 0
8582
+ },
8583
+ min: {
8584
+ dimension: "duration",
8585
+ factor: 60,
8586
+ offset: 0
8587
+ },
8588
+ h: {
8589
+ dimension: "duration",
8590
+ factor: 3600,
8591
+ offset: 0
8592
+ },
8593
+ d: {
8594
+ dimension: "duration",
8595
+ factor: 86400,
8596
+ offset: 0
8597
+ },
8598
+ B: {
8599
+ dimension: "information",
8600
+ factor: 1,
8601
+ offset: 0
8602
+ },
8603
+ KiB: {
8604
+ dimension: "information",
8605
+ factor: 1024,
8606
+ offset: 0
8607
+ },
8608
+ MiB: {
8609
+ dimension: "information",
8610
+ factor: 1024 ** 2,
8611
+ offset: 0
8612
+ },
8613
+ GiB: {
8614
+ dimension: "information",
8615
+ factor: 1024 ** 3,
8616
+ offset: 0
8617
+ },
8618
+ TiB: {
8619
+ dimension: "information",
8620
+ factor: 1024 ** 4,
8621
+ offset: 0
8622
+ },
8623
+ kB: {
8624
+ dimension: "information",
8625
+ factor: 1e3,
8626
+ offset: 0
8627
+ },
8628
+ MB: {
8629
+ dimension: "information",
8630
+ factor: 1e6,
8631
+ offset: 0
8632
+ },
8633
+ GB: {
8634
+ dimension: "information",
8635
+ factor: 1e9,
8636
+ offset: 0
8637
+ },
8638
+ "°": {
8639
+ dimension: "angle",
8640
+ factor: 1,
8641
+ offset: 0
8642
+ },
8643
+ dBFS: {
8644
+ dimension: "sound-level",
8645
+ factor: 1,
8646
+ offset: 0
8647
+ },
8648
+ dBm: {
8649
+ dimension: "signal-level",
8650
+ factor: 1,
8651
+ offset: 0
8652
+ },
8653
+ mired: {
8654
+ dimension: "colour-temperature",
8655
+ factor: 1,
8656
+ offset: 0
8657
+ },
8658
+ kbps: {
8659
+ dimension: "bitrate",
8660
+ factor: 1,
8661
+ offset: 0
8662
+ },
8663
+ Mbps: {
8664
+ dimension: "bitrate",
8665
+ factor: 1e3,
8666
+ offset: 0
8667
+ },
8668
+ fps: {
8669
+ dimension: "rate",
8670
+ factor: 1,
8671
+ offset: 0
8672
+ }
8673
+ };
8674
+ //#endregion
8675
+ //#region src/units/convert.ts
8676
+ /**
8677
+ * Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
8678
+ *
8679
+ * Conversion is refused (throws `UnitConversionError`, or returns `undefined`
8680
+ * from the `try*` variant) whenever a unit is unknown or the two units belong
8681
+ * to different dimensions — cross-dimension conversion is deliberately
8682
+ * impossible so a render override can never make the value and its unit label
8683
+ * disagree.
8684
+ *
8685
+ * Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
8686
+ * calling here — the table keys only canonical spellings.
8687
+ */
8688
+ /** Thrown by `convertUnit` when a conversion is refused. Carries the offending
8689
+ * units + a machine-readable `reason` for callers that branch on the cause. */
8690
+ var UnitConversionError = class extends Error {
8691
+ from;
8692
+ to;
8693
+ reason;
8694
+ constructor(from, to, reason) {
8695
+ super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
8696
+ this.name = "UnitConversionError";
8697
+ this.from = from;
8698
+ this.to = to;
8699
+ this.reason = reason;
8700
+ }
8701
+ };
8702
+ /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
8703
+ function specFor(unit) {
8704
+ return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
8705
+ }
8706
+ /** The dimension a unit belongs to, or `undefined` for an unknown unit. */
8707
+ function unitDimension(unit) {
8708
+ return specFor(unit)?.dimension;
8709
+ }
8710
+ /** Value in `spec`'s unit → its dimension's canonical unit. */
8711
+ function toCanonical(value, spec) {
8712
+ return value * spec.factor + spec.offset;
8713
+ }
8714
+ /** Canonical-unit value → the unit described by `spec`. */
8715
+ function fromCanonical(canonical, spec) {
8716
+ return (canonical - spec.offset) / spec.factor;
8717
+ }
8718
+ /**
8719
+ * Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
8720
+ * Throws `UnitConversionError` when either unit is unknown or the units belong
8721
+ * to different dimensions.
8722
+ */
8723
+ function convertUnit(value, from, to) {
8724
+ if (from === to) return value;
8725
+ const fromSpec = specFor(from);
8726
+ if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
8727
+ const toSpec = specFor(to);
8728
+ if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
8729
+ if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
8730
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
8731
+ }
8732
+ /**
8733
+ * Graceful variant for the render path: returns the converted value, or
8734
+ * `undefined` when the conversion is refused (unknown unit / cross-dimension).
8735
+ * Never throws — a render caller falls back to the raw value + source unit.
8736
+ */
8737
+ function tryConvertUnit(value, from, to) {
8738
+ if (from === to) return value;
8739
+ const fromSpec = specFor(from);
8740
+ if (!fromSpec) return void 0;
8741
+ const toSpec = specFor(to);
8742
+ if (!toSpec) return void 0;
8743
+ if (fromSpec.dimension !== toSpec.dimension) return void 0;
8744
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
8745
+ }
8746
+ /**
8747
+ * Whether `value` can be converted between `from` and `to` (same dimension,
8748
+ * both known). Cheap boolean probe for the UI's same-dimension unit picker and
8749
+ * the resolver's convertibility branch — compute ONCE so a unit label and its
8750
+ * value math can never disagree.
8751
+ */
8752
+ function canConvertUnit(from, to) {
8753
+ if (from === to) return true;
8754
+ const fromDim = unitDimension(from);
8755
+ if (fromDim === void 0) return false;
8756
+ return fromDim === unitDimension(to);
8757
+ }
8758
+ /**
8759
+ * Every known unit spelling in a dimension (table insertion order — canonical
8760
+ * unit first). Powers the UI's same-dimension unit picker.
8761
+ */
8762
+ /**
8763
+ * Convert a difference, not a reading. Affine units (°F) shift an absolute
8764
+ * value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
8765
+ */
8766
+ function tryConvertDelta(value, from, to) {
8767
+ if (from === to) return value;
8768
+ const origin = tryConvertUnit(0, from, to);
8769
+ const shifted = tryConvertUnit(value, from, to);
8770
+ if (origin === void 0 || shifted === void 0) return void 0;
8771
+ return shifted - origin;
8772
+ }
8773
+ function unitsForDimension(dimension) {
8774
+ const out = [];
8775
+ for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
8776
+ return out;
8777
+ }
8778
+ //#endregion
8779
+ //#region src/units/quantity-input.ts
8780
+ /**
8781
+ * A number a person types for a physical quantity — the input side of the
8782
+ * value + unit contract (D972).
8783
+ *
8784
+ * Readings are stored in the base unit of their quantity (D944); a cap method
8785
+ * param is stored in the unit its input schema DECLARES (`paramMeta`). The
8786
+ * wire pair {@link QuantityValue} says "this number is expressed in this
8787
+ * unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
8788
+ * with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
8789
+ */
8790
+ /** A unit that cannot be applied to the number it was given with. */
8791
+ var QuantityUnitError = class extends Error {
8792
+ code;
8793
+ constructor(code, message) {
8794
+ super(`[${code}] ${message}`);
8795
+ this.name = "QuantityUnitError";
8796
+ this.code = code;
8797
+ }
8798
+ };
8799
+ /** The wire pair: `value` is expressed in `unit`. */
8800
+ var QuantityValueSchema = zod.z.object({
8801
+ value: zod.z.number(),
8802
+ unit: zod.z.string().min(1)
8803
+ });
8804
+ function isQuantityValue(candidate) {
8805
+ return QuantityValueSchema.safeParse(candidate).success;
8806
+ }
8807
+ /**
8808
+ * `value` expressed in `unit`, brought to `target`. Throws
8809
+ * {@link QuantityUnitError} for a non-finite value, an unknown unit or a unit
8810
+ * of another dimension — never guesses.
8811
+ */
8812
+ function convertQuantityInput(input) {
8813
+ if (!Number.isFinite(input.value)) throw new QuantityUnitError("non-finite", `${String(input.value)} is not a finite number`);
8814
+ const unit = normalizeUnit(input.unit);
8815
+ if (unit === void 0 || unitDimension(unit) === void 0) throw new QuantityUnitError("unknown-unit", `unknown unit "${input.unit}"`);
8816
+ const converted = tryConvertUnit(input.value, unit, input.target);
8817
+ if (converted === void 0) throw new QuantityUnitError("cross-dimension", `"${input.unit}" is not a unit of ${input.target}'s quantity`);
8818
+ return Number(converted.toPrecision(12));
8819
+ }
8820
+ /**
8821
+ * Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
8822
+ * input-side twin of `readingMeta`. The value is carried in `unit` (a gram
8823
+ * portion stays in grams); the unit must belong to the quantity, checked once
8824
+ * when the cap is declared.
8825
+ */
8826
+ function paramMeta(declared) {
8827
+ const base = require_sleep.baseUnitOf(declared.quantity);
8828
+ if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
8829
+ if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
8830
+ return {
8831
+ quantity: declared.quantity,
8832
+ unit: declared.unit
8833
+ };
8834
+ }
8835
+ var ParamMetaBagSchema = zod.z.object({
8836
+ quantity: require_sleep.ReadingQuantitySchema,
8837
+ unit: zod.z.string().min(1)
8838
+ });
8839
+ function metaOf(schema) {
8840
+ const parsed = ParamMetaBagSchema.safeParse(zod.z.globalRegistry.get(schema));
8841
+ return parsed.success ? parsed.data : void 0;
8842
+ }
8843
+ /** The `paramMeta` on a method input field (or its unwrapped leaf), if any. */
8844
+ function paramMetaOf(field) {
8845
+ return metaOf(field) ?? metaOf(unwrapSchema(field));
8846
+ }
8847
+ //#endregion
8063
8848
  //#region src/automation/recipe.ts
8064
8849
  /**
8065
8850
  * The automation RECIPE — what a visually-authored automation is, before it
@@ -8121,8 +8906,10 @@ var AutomationConditionLeafSchema = zod.z.object({
8121
8906
  zod.z.string(),
8122
8907
  zod.z.number(),
8123
8908
  zod.z.boolean(),
8124
- zod.z.array(zod.z.union([zod.z.string(), zod.z.number()]))
8125
- ])
8909
+ zod.z.array(zod.z.union([zod.z.string(), zod.z.number()])),
8910
+ QuantityValueSchema
8911
+ ]),
8912
+ unit: zod.z.string().min(1).optional()
8126
8913
  });
8127
8914
  var AutomationConditionStationaryPresentSchema = zod.z.object({
8128
8915
  kind: zod.z.literal("stationary-present"),
@@ -8275,7 +9062,13 @@ var AutomationCapStepSchema = zod.z.object({
8275
9062
  /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
8276
9063
  * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
8277
9064
  * the expression leaf. */
8278
- args: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
9065
+ args: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
9066
+ /**
9067
+ * The unit the person picked per arg (display only). `args` always hold the
9068
+ * value in the unit the method's input DECLARES (`paramMeta`); on a write an
9069
+ * arg may be a {@link QuantityValue}, which the save boundary converts.
9070
+ */
9071
+ argUnits: zod.z.record(zod.z.string(), zod.z.string().min(1)).optional()
8279
9072
  });
8280
9073
  /**
8281
9074
  * The steps an internal Action may contain: cap and wait, and nothing that can
@@ -9855,6 +10648,14 @@ function evalNode(cond, path, reader, now, out) {
9855
10648
  expected: toTraceValue(cond.value),
9856
10649
  observed: toTraceValue(observed)
9857
10650
  };
10651
+ if (isQuantityValue(cond.value)) {
10652
+ out.push({
10653
+ ...base,
10654
+ outcome: "fail",
10655
+ reason: `${cond.fieldPath} expected value's unit was never converted → fail`
10656
+ });
10657
+ return false;
10658
+ }
9858
10659
  if (observed === void 0) {
9859
10660
  out.push({
9860
10661
  ...base,
@@ -10705,6 +11506,242 @@ function buildActionSteps(steps, vars, deps) {
10705
11506
  });
10706
11507
  }
10707
11508
  //#endregion
11509
+ //#region src/units/quantity-units.ts
11510
+ /**
11511
+ * Which units a person may pick for a number of a given quantity. The
11512
+ * quantity's stored base unit comes first (D944), then every other unit of the
11513
+ * same dimension in table order. A quantity with no unit (`none`, `count`,
11514
+ * `index`, `ratio`, `device`) offers nothing: the number is just a number.
11515
+ */
11516
+ /** The unit a reading of `quantity` is stored in, or `undefined` when it has none. */
11517
+ function baseUnitForQuantity(quantity) {
11518
+ return require_sleep.baseUnitOf(quantity);
11519
+ }
11520
+ /**
11521
+ * The units a person can enter a number of `quantity` in — base unit first.
11522
+ * Empty when the quantity carries no unit.
11523
+ */
11524
+ function unitsForQuantity(quantity) {
11525
+ const base = baseUnitForQuantity(quantity);
11526
+ if (base === void 0) return [];
11527
+ const dimension = unitDimension(base);
11528
+ if (dimension === void 0) return [base];
11529
+ return [base, ...unitsForDimension(dimension).filter((unit) => unit !== base)];
11530
+ }
11531
+ //#endregion
11532
+ //#region src/automation/normalize-units.ts
11533
+ /** A bare number accepted as a base unit — the save succeeded, say so. */
11534
+ var UnitWarningSchema = zod.z.object({
11535
+ /** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
11536
+ path: zod.z.string(),
11537
+ deviceId: zod.z.number().int().optional(),
11538
+ field: zod.z.string(),
11539
+ value: zod.z.number(),
11540
+ assumedUnit: zod.z.string(),
11541
+ message: zod.z.string()
11542
+ });
11543
+ function objectInput(schema) {
11544
+ const inner = unwrapSchema(schema);
11545
+ return inner instanceof zod.z.ZodObject ? inner : void 0;
11546
+ }
11547
+ /** A resolver over capability definitions (the generated registry, or a fake). */
11548
+ function capUnitResolver(defs) {
11549
+ const byName = new Map(defs.map((def) => [def.name, def]));
11550
+ return {
11551
+ fieldQuantity(cap, fieldPath) {
11552
+ const status = byName.get(cap)?.status;
11553
+ if (status === void 0) return void 0;
11554
+ return [...enumerateSchemaFields(status.schema), ...status.itemArray === void 0 ? [] : enumerateItemArrayFields(status.itemArray)].find((f) => f.path === fieldPath)?.quantity;
11555
+ },
11556
+ paramMeta(cap, method, arg) {
11557
+ const input = byName.get(cap)?.methods[method]?.input;
11558
+ if (input === void 0) return void 0;
11559
+ const field = objectInput(input)?.shape[arg];
11560
+ return field === void 0 ? void 0 : paramMetaOf(field);
11561
+ }
11562
+ };
11563
+ }
11564
+ function pickedUnit(raw) {
11565
+ return normalizeUnit(raw) ?? raw;
11566
+ }
11567
+ function hasUnitChoice(quantity) {
11568
+ return unitsForQuantity(quantity).length > 1;
11569
+ }
11570
+ function bareWarning(input) {
11571
+ return {
11572
+ ...input,
11573
+ message: `${input.field}: the bare number ${input.value} was taken as ${input.assumedUnit} — send { value, unit } to state the unit`
11574
+ };
11575
+ }
11576
+ function checkPickedUnit(unit, target) {
11577
+ convertQuantityInput({
11578
+ value: 1,
11579
+ unit,
11580
+ target
11581
+ });
11582
+ }
11583
+ function normalizeLeaf(leaf, resolver, path) {
11584
+ const quantity = resolver.fieldQuantity(leaf.cap, leaf.fieldPath);
11585
+ const base = quantity === void 0 ? void 0 : baseUnitForQuantity(quantity);
11586
+ const wantsUnit = isQuantityValue(leaf.value) || leaf.unit !== void 0;
11587
+ if (base === void 0 || quantity === void 0) {
11588
+ if (wantsUnit) throw new QuantityUnitError("no-quantity", `${leaf.cap}.${leaf.fieldPath} is not a quantity — it takes no unit`);
11589
+ return {
11590
+ leaf,
11591
+ warnings: []
11592
+ };
11593
+ }
11594
+ if (isQuantityValue(leaf.value)) {
11595
+ const converted = convertQuantityInput({
11596
+ value: leaf.value.value,
11597
+ unit: leaf.value.unit,
11598
+ target: base
11599
+ });
11600
+ return {
11601
+ leaf: {
11602
+ ...leaf,
11603
+ value: converted,
11604
+ unit: pickedUnit(leaf.value.unit)
11605
+ },
11606
+ warnings: []
11607
+ };
11608
+ }
11609
+ if (typeof leaf.value !== "number") return {
11610
+ leaf,
11611
+ warnings: []
11612
+ };
11613
+ if (leaf.unit !== void 0) {
11614
+ checkPickedUnit(leaf.unit, base);
11615
+ return {
11616
+ leaf,
11617
+ warnings: []
11618
+ };
11619
+ }
11620
+ if (!hasUnitChoice(quantity)) return {
11621
+ leaf,
11622
+ warnings: []
11623
+ };
11624
+ return {
11625
+ leaf,
11626
+ warnings: [bareWarning({
11627
+ path,
11628
+ deviceId: leaf.deviceId,
11629
+ field: leaf.fieldPath,
11630
+ value: leaf.value,
11631
+ assumedUnit: base
11632
+ })]
11633
+ };
11634
+ }
11635
+ /** Normalise every leaf of a condition tree. Throws {@link QuantityUnitError}. */
11636
+ function normalizeConditionUnits(condition, resolver, path = "conditions") {
11637
+ switch (condition.kind) {
11638
+ case "condition": {
11639
+ const { leaf, warnings } = normalizeLeaf(condition, resolver, path);
11640
+ return {
11641
+ value: leaf,
11642
+ warnings
11643
+ };
11644
+ }
11645
+ case "not": {
11646
+ const inner = normalizeConditionUnits(condition.child, resolver, `${path}.child`);
11647
+ return {
11648
+ value: {
11649
+ ...condition,
11650
+ child: inner.value
11651
+ },
11652
+ warnings: inner.warnings
11653
+ };
11654
+ }
11655
+ case "all":
11656
+ case "any": {
11657
+ const children = condition.children.map((child, i) => normalizeConditionUnits(child, resolver, `${path}.children.${i}`));
11658
+ return {
11659
+ value: {
11660
+ ...condition,
11661
+ children: children.map((c) => c.value)
11662
+ },
11663
+ warnings: children.flatMap((c) => c.warnings)
11664
+ };
11665
+ }
11666
+ default: return {
11667
+ value: condition,
11668
+ warnings: []
11669
+ };
11670
+ }
11671
+ }
11672
+ function normalizeCapStep(step, resolver, path) {
11673
+ const deviceId = typeof step.deviceId === "number" ? step.deviceId : void 0;
11674
+ const args = { ...step.args };
11675
+ const argUnits = { ...step.argUnits };
11676
+ const warnings = [];
11677
+ for (const [arg, picked] of Object.entries(step.argUnits ?? {})) {
11678
+ const meta = resolver.paramMeta(step.cap, step.method, arg);
11679
+ if (meta === void 0) throw new QuantityUnitError("no-quantity", `${step.cap}.${step.method}(${arg}) is not a quantity — it takes no unit`);
11680
+ checkPickedUnit(picked, meta.unit);
11681
+ }
11682
+ for (const [arg, given] of Object.entries(step.args ?? {})) {
11683
+ const meta = resolver.paramMeta(step.cap, step.method, arg);
11684
+ if (meta === void 0) continue;
11685
+ if (isQuantityValue(given)) {
11686
+ args[arg] = convertQuantityInput({
11687
+ value: given.value,
11688
+ unit: given.unit,
11689
+ target: meta.unit
11690
+ });
11691
+ argUnits[arg] = pickedUnit(given.unit);
11692
+ } else if (typeof given === "number" && argUnits[arg] === void 0 && hasUnitChoice(meta.quantity)) warnings.push(bareWarning({
11693
+ path: `${path}.args.${arg}`,
11694
+ ...deviceId === void 0 ? {} : { deviceId },
11695
+ field: arg,
11696
+ value: given,
11697
+ assumedUnit: meta.unit
11698
+ }));
11699
+ }
11700
+ const { argUnits: _dropped, ...rest } = step;
11701
+ return {
11702
+ value: {
11703
+ ...rest,
11704
+ ...step.args === void 0 ? {} : { args },
11705
+ ...Object.keys(argUnits).length > 0 ? { argUnits } : {}
11706
+ },
11707
+ warnings
11708
+ };
11709
+ }
11710
+ /**
11711
+ * Normalise the cap steps of an action list (an internal Action's steps, or a
11712
+ * recipe's actions). Other step kinds pass through. Throws {@link QuantityUnitError}.
11713
+ */
11714
+ function normalizeActionUnits(actions, resolver, path = "steps") {
11715
+ const out = [];
11716
+ const warnings = [];
11717
+ actions.forEach((action, i) => {
11718
+ if (action.kind !== "cap") {
11719
+ out.push(action);
11720
+ return;
11721
+ }
11722
+ const normalized = normalizeCapStep(action, resolver, `${path}.${i}`);
11723
+ out.push(normalized.value);
11724
+ warnings.push(...normalized.warnings);
11725
+ });
11726
+ return {
11727
+ value: out,
11728
+ warnings
11729
+ };
11730
+ }
11731
+ /** Normalise a whole recipe: its condition tree and its actions. */
11732
+ function normalizeRecipeUnits(recipe, resolver) {
11733
+ const conditions = recipe.conditions === void 0 ? void 0 : normalizeConditionUnits(recipe.conditions, resolver, "conditions");
11734
+ const actions = normalizeActionUnits(recipe.actions, resolver, "actions");
11735
+ return {
11736
+ value: {
11737
+ ...recipe,
11738
+ ...conditions === void 0 ? {} : { conditions: conditions.value },
11739
+ actions: actions.value
11740
+ },
11741
+ warnings: [...conditions?.warnings ?? [], ...actions.warnings]
11742
+ };
11743
+ }
11744
+ //#endregion
10708
11745
  //#region src/capabilities/notification-output.cap.ts
10709
11746
  /**
10710
11747
  * notification-output — canonical, capability-gated notification delivery.
@@ -15199,77 +16236,6 @@ function kebabToCamel(s) {
15199
16236
  return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
15200
16237
  }
15201
16238
  //#endregion
15202
- //#region src/device/schema-fields.ts
15203
- /** Unwrap ZodNullable / ZodOptional / ZodDefault wrappers to reach the inner type.
15204
- * Zod v4 exposes `.unwrap()` on all three wrapper classes. */
15205
- function unwrapSchema(schema) {
15206
- let s = schema;
15207
- while (s instanceof zod.z.ZodNullable || s instanceof zod.z.ZodOptional || s instanceof zod.z.ZodDefault) s = s.unwrap();
15208
- return s;
15209
- }
15210
- function isMetaBag(value) {
15211
- return typeof value === "object" && value !== null;
15212
- }
15213
- function metaQuantity(schema) {
15214
- const bag = zod.z.globalRegistry.get(schema);
15215
- if (isMetaBag(bag) && require_sleep.isReadingQuantity(bag.quantity)) return bag.quantity;
15216
- }
15217
- /** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
15218
- function quantityOfField(schema) {
15219
- return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
15220
- }
15221
- function leafKind(schema) {
15222
- const s = unwrapSchema(schema);
15223
- if (s instanceof zod.z.ZodEnum) return {
15224
- kind: "enum",
15225
- enumValues: s.options
15226
- };
15227
- if (s instanceof zod.z.ZodNumber) return { kind: "number" };
15228
- if (s instanceof zod.z.ZodString) return { kind: "string" };
15229
- if (s instanceof zod.z.ZodBoolean) return { kind: "boolean" };
15230
- return null;
15231
- }
15232
- /** Walk a cap status schema into flat wireable leaf fields (dotted paths).
15233
- * Recurses into nested ZodObject (unwrapping nullable/optional/default first),
15234
- * so fields with null live values are still offered. Arrays / unknown shapes
15235
- * are skipped — never throws. */
15236
- function enumerateSchemaFields(schema, prefix = "") {
15237
- const root = unwrapSchema(schema);
15238
- if (!(root instanceof zod.z.ZodObject)) return [];
15239
- const out = [];
15240
- const shape = root.shape;
15241
- for (const [key, field] of Object.entries(shape)) {
15242
- const path = prefix ? `${prefix}.${key}` : key;
15243
- const inner = unwrapSchema(field);
15244
- if (inner instanceof zod.z.ZodObject) {
15245
- out.push(...enumerateSchemaFields(inner, path));
15246
- continue;
15247
- }
15248
- const leaf = leafKind(field);
15249
- if (leaf) {
15250
- const quantity = quantityOfField(field);
15251
- out.push({
15252
- path,
15253
- kind: leaf.kind,
15254
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
15255
- ...quantity !== void 0 ? { quantity } : {}
15256
- });
15257
- }
15258
- }
15259
- return out;
15260
- }
15261
- /** Enumerate the per-item wireable fields of an item-array cap (see
15262
- * `CapabilityStatusItemArray`): the item schema's leaf fields, minus the
15263
- * `keyField` (the key comes from the link's `itemKey`, never from a wired
15264
- * source), each tagged `item: true` so the authoring UI collects an
15265
- * `itemKey` alongside the field. Never throws. */
15266
- function enumerateItemArrayFields(itemArray) {
15267
- return enumerateSchemaFields(itemArray.itemSchema).filter((f) => f.path !== itemArray.keyField).map((f) => ({
15268
- ...f,
15269
- item: true
15270
- }));
15271
- }
15272
- //#endregion
15273
16239
  //#region src/composition/state-fields.ts
15274
16240
  /**
15275
16241
  * What a capability's state field IS: kind, nullability, whether required. The
@@ -21163,192 +22129,6 @@ function parseAdminBase(baseUrl) {
21163
22129
  };
21164
22130
  }
21165
22131
  //#endregion
21166
- //#region src/device/source-info.ts
21167
- /**
21168
- * Identity envelope for a device's upstream-system metadata.
21169
- *
21170
- * Two jobs:
21171
- *
21172
- * 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
21173
- * uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
21174
- * WS-Discovery endpoint UUID, …). Provider code keeps a
21175
- * `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
21176
- * without round-tripping through `stableId` / `id` mappings.
21177
- *
21178
- * 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
21179
- * one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
21180
- * for vendors where MAC is the natural key). For systems where the
21181
- * live dispatch key can be renamed (HA `entity_id` can be edited),
21182
- * providers detect renames by matching `uniqueId` and patch `id`
21183
- * in-place so dispatch keeps working.
21184
- *
21185
- * **Display unit and precision are NOT stored here.** They are a read-time
21186
- * preference (D944). A polymorphic reading stores its `quantity` here once,
21187
- * at adopt, and a date/time value stores `dateTimeShape` the same way —
21188
- * neither is repeated on each sample. The `device_class` tag is an
21189
- * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21190
- * to derive the CamStack `DeviceRole`; it is never stored after that point.
21191
- *
21192
- * The `raw` escape hatch carries provider-specific attributes that don't
21193
- * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
21194
- * details, …). Providers control what lands here.
21195
- */
21196
- var SourceInfoSchema = zod.z.object({
21197
- /** Live dispatch key — mutable when the source system allows rename. */
21198
- id: zod.z.string(),
21199
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
21200
- system: zod.z.string(),
21201
- /** Immutable upstream identifier when available (HA `unique_id`, … ).
21202
- * Used to detect rename when `id` changes. */
21203
- uniqueId: zod.z.string().optional(),
21204
- /** Quantity of a polymorphic reading, written once at adopt (D944). */
21205
- quantity: require_sleep.ReadingQuantitySchema.optional(),
21206
- /** Date / time / datetime shape of a string reading, written once at adopt. */
21207
- dateTimeShape: zod.z.enum([
21208
- "date",
21209
- "time",
21210
- "datetime"
21211
- ]).optional(),
21212
- /** Provider-specific extras that don't fit the structured slots.
21213
- * Whitelist what goes here per provider — do NOT dump entire HA
21214
- * attribute blobs (would explode the meta JSON column). */
21215
- raw: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
21216
- });
21217
- /**
21218
- * Build the synthetic SourceInfo every existing provider falls back to
21219
- * when no upstream value has been persisted. Keeps non-migrated providers
21220
- * (Reolink / Hikvision / ONVIF / RTSP) functional without code
21221
- * changes — `id` reuses the CamStack stableId, `system` reuses the addon
21222
- * id. Real upstream identity replaces this once a provider migrates and
21223
- * calls `updateSourceInfo()` with concrete values.
21224
- */
21225
- function synthesizeSourceInfo(input) {
21226
- return {
21227
- id: input.stableId,
21228
- system: input.addonId
21229
- };
21230
- }
21231
- /**
21232
- * Shallow-merge a partial patch over a prior SourceInfo. `undefined`
21233
- * patch values are ignored (no field clobber); to clear a field
21234
- * explicitly, providers omit it from the patch and rebuild from scratch
21235
- * via `updateSourceInfo({})` cycles — uncommon enough that we don't
21236
- * bother with a `null`-means-delete sentinel.
21237
- *
21238
- * The `raw` field is also shallow-merged: passing `raw: { foo: 1 }`
21239
- * merges over existing `raw` keys instead of replacing the whole bag.
21240
- */
21241
- function mergeSourceInfo(prev, patch) {
21242
- const next = { ...prev };
21243
- for (const [k, v] of Object.entries(patch)) {
21244
- if (v === void 0) continue;
21245
- if (k === "raw" && typeof v === "object" && v !== null) {
21246
- next.raw = {
21247
- ...prev.raw,
21248
- ...v
21249
- };
21250
- continue;
21251
- }
21252
- next[k] = v;
21253
- }
21254
- return next;
21255
- }
21256
- /**
21257
- * Coerce common unit-of-measurement string variants to a canonical form
21258
- * so the UI's display templates can rely on stable strings. Pure
21259
- * passthrough for unknown units — kept conservative on purpose, the goal
21260
- * is to normalise the long tail of HA `unit_of_measurement` values that
21261
- * have multiple historical spellings (`'°C'` vs `'C'` vs `'celsius'`).
21262
- *
21263
- * Extend the map as we encounter real-world data. Returning the raw
21264
- * input for unknown values is intentional: providers should never
21265
- * lose information silently here.
21266
- */
21267
- function normalizeUnit(raw) {
21268
- if (raw == null) return void 0;
21269
- const trimmed = raw.trim();
21270
- if (trimmed === "") return void 0;
21271
- const lower = trimmed.toLowerCase();
21272
- if (lower === "°c" || lower === "c" || lower === "celsius") return "°C";
21273
- if (lower === "°f" || lower === "f" || lower === "fahrenheit") return "°F";
21274
- if (lower === "k" || lower === "kelvin") return "K";
21275
- if (lower === "%" || lower === "percent" || lower === "percentage") return "%";
21276
- if (lower === "lx" || lower === "lux") return "lx";
21277
- if (lower === "hpa" || lower === "hectopascal") return "hPa";
21278
- if (lower === "kpa") return "kPa";
21279
- if (lower === "pa") return "Pa";
21280
- if (lower === "mbar" || lower === "millibar") return "mbar";
21281
- if (lower === "w" || lower === "watt") return "W";
21282
- if (lower === "kw" || lower === "kilowatt") return "kW";
21283
- if (lower === "wh" || lower === "watt-hour") return "Wh";
21284
- if (lower === "kwh" || lower === "kilowatt-hour") return "kWh";
21285
- if (lower === "v" || lower === "volt") return "V";
21286
- if (lower === "a" || lower === "amp" || lower === "ampere") return "A";
21287
- if (lower === "ma" || lower === "milliampere") return "mA";
21288
- if (lower === "va") return "VA";
21289
- if (lower === "ppm") return "ppm";
21290
- if (lower === "ppb") return "ppb";
21291
- if (lower === "µg/m³" || lower === "ug/m3" || lower === "µg/m3") return "µg/m³";
21292
- if (lower === "km/h" || lower === "kph") return "km/h";
21293
- if (lower === "mph") return "mph";
21294
- if (lower === "m/s") return "m/s";
21295
- if (lower === "m") return "m";
21296
- if (lower === "m²" || lower === "m2") return "m²";
21297
- if (lower === "mm") return "mm";
21298
- if (lower === "cm") return "cm";
21299
- if (lower === "km") return "km";
21300
- if (lower === "in" || lower === "inch" || lower === "inches") return "in";
21301
- if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
21302
- if (lower === "kg") return "kg";
21303
- if (lower === "g") return "g";
21304
- if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
21305
- if (lower === "ml") return "mL";
21306
- if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
21307
- if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
21308
- if (lower === "ms") return "ms";
21309
- if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
21310
- if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
21311
- if (lower === "d" || lower === "day" || lower === "days") return "d";
21312
- if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
21313
- if (lower === "kib") return "KiB";
21314
- if (lower === "mib") return "MiB";
21315
- if (lower === "gib") return "GiB";
21316
- if (lower === "tib") return "TiB";
21317
- if (lower === "kb") return "kB";
21318
- if (lower === "mb") return "MB";
21319
- if (lower === "gb") return "GB";
21320
- if (lower === "dbm") return "dBm";
21321
- if (lower === "dbfs") return "dBFS";
21322
- if (lower === "inhg") return "inHg";
21323
- if (lower === "mm/h") return "mm/h";
21324
- if (lower === "w/m²" || lower === "w/m2") return "W/m²";
21325
- if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
21326
- if (lower === "kbps") return "kbps";
21327
- if (lower === "fps") return "fps";
21328
- return trimmed;
21329
- }
21330
- /**
21331
- * Key under which `SourceInfo` lives inside `DeviceMeta.metadata`. The
21332
- * blob is a free-form `Record<string, unknown>` shared with hardware-
21333
- * identity fields (`manufacturer`, `model`, `firmware`, …); SourceInfo
21334
- * lives under a single nested key to keep it composable with those.
21335
- */
21336
- var SOURCE_INFO_METADATA_KEY = "sourceInfo";
21337
- /**
21338
- * Extract a SourceInfo from a `DeviceMeta.metadata` blob, validating
21339
- * with Zod. Returns `null` when the blob is missing or the nested
21340
- * `sourceInfo` key fails validation — callers fall back to the
21341
- * synthetic default in that case (see `synthesizeSourceInfo`).
21342
- */
21343
- function extractSourceInfoFromMetadata(metadata) {
21344
- if (!metadata) return null;
21345
- const raw = metadata[SOURCE_INFO_METADATA_KEY];
21346
- if (!raw) return null;
21347
- const parsed = SourceInfoSchema.safeParse(raw);
21348
- if (!parsed.success) return null;
21349
- return parsed.data;
21350
- }
21351
- //#endregion
21352
22132
  //#region src/capabilities/device-discovery.cap.ts
21353
22133
  /**
21354
22134
  * device-discovery — device-scoped capability for parents that host /
@@ -65158,15 +65938,29 @@ var actionsCapability = {
65158
65938
  auth: "admin"
65159
65939
  }),
65160
65940
  getTraces: require_sleep.method(ActionIdInput, zod.z.object({ traces: zod.z.array(AutomationRunTraceSchema) }), { auth: "admin" }),
65161
- /** Internal actions only. A read-only (addon) entry is refused with `[read-only]`. */
65162
- create: require_sleep.method(zod.z.object({ action: ActionInputSchema }), zod.z.object({ action: ActionEntrySchema }), {
65941
+ /**
65942
+ * Internal actions only; a read-only (addon) entry is refused with `[read-only]`.
65943
+ * Create an internal action. A cap step arg that is a physical quantity may
65944
+ * be sent as `{ value, unit }` ("value expressed in unit"); the server
65945
+ * converts it to the unit the method's input declares, stores the bare
65946
+ * number plus the picked unit in `argUnits`, and refuses a unit of another
65947
+ * dimension (`[invalid-steps]`). A bare number is taken as the declared
65948
+ * unit and answered as a `warnings` entry (D972).
65949
+ */
65950
+ create: require_sleep.method(zod.z.object({ action: ActionInputSchema }), zod.z.object({
65951
+ action: ActionEntrySchema,
65952
+ warnings: zod.z.array(UnitWarningSchema).optional()
65953
+ }), {
65163
65954
  kind: "mutation",
65164
65955
  auth: "admin"
65165
65956
  }),
65166
65957
  update: require_sleep.method(zod.z.object({
65167
65958
  actionId: zod.z.string().min(1),
65168
65959
  action: ActionInputSchema.partial()
65169
- }), zod.z.object({ action: ActionEntrySchema }), {
65960
+ }), zod.z.object({
65961
+ action: ActionEntrySchema,
65962
+ warnings: zod.z.array(UnitWarningSchema).optional()
65963
+ }), {
65170
65964
  kind: "mutation",
65171
65965
  auth: "admin"
65172
65966
  }),
@@ -65813,6 +66607,39 @@ var EMPTY_REMOTE_ASSUMED_STATE = {
65813
66607
  muted: null
65814
66608
  };
65815
66609
  var MAX_LAYOUT_SPAN = 24;
66610
+ var MAX_LAYOUT_GROUPS = 50;
66611
+ /**
66612
+ * Closed vocabularies the UI knows how to DRAW (D973). A layout names a shape,
66613
+ * a tone or a frame; it never carries a style. An addon that contributes a
66614
+ * component picks from these lists — it ships no UI code.
66615
+ */
66616
+ var REMOTE_BUTTON_SHAPES = [
66617
+ "rect",
66618
+ "rounded",
66619
+ "round",
66620
+ "pill"
66621
+ ];
66622
+ var RemoteButtonShapeSchema = zod.z.enum(REMOTE_BUTTON_SHAPES);
66623
+ var REMOTE_BUTTON_TONES = [
66624
+ "neutral",
66625
+ "accent",
66626
+ "danger",
66627
+ "red",
66628
+ "green",
66629
+ "yellow",
66630
+ "blue"
66631
+ ];
66632
+ var RemoteButtonToneSchema = zod.z.enum(REMOTE_BUTTON_TONES);
66633
+ var REMOTE_GROUP_FRAMES = [
66634
+ "none",
66635
+ "panel",
66636
+ "divider",
66637
+ "dpad",
66638
+ "rocker-v",
66639
+ "rocker-h",
66640
+ "keypad"
66641
+ ];
66642
+ var RemoteGroupFrameSchema = zod.z.enum(REMOTE_GROUP_FRAMES);
65816
66643
  var RemoteLayoutButtonSchema = zod.z.object({
65817
66644
  id: zod.z.string().min(1).max(40),
65818
66645
  /** 0-based grid position. */
@@ -65822,12 +66649,94 @@ var RemoteLayoutButtonSchema = zod.z.object({
65822
66649
  colSpan: zod.z.number().int().min(1).max(24),
65823
66650
  icon: zod.z.string().min(1).max(60).optional(),
65824
66651
  label: zod.z.string().min(1).max(40).optional(),
65825
- role: RemoteRoleSchema
66652
+ role: RemoteRoleSchema,
66653
+ /** Absent = the plain rendering every layout had before shapes existed. */
66654
+ shape: RemoteButtonShapeSchema.optional(),
66655
+ tone: RemoteButtonToneSchema.optional(),
66656
+ /** The group this button belongs to; it must lie inside that group's rectangle. */
66657
+ groupId: zod.z.string().min(1).max(40).optional()
65826
66658
  });
66659
+ /**
66660
+ * A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
66661
+ * titled panel, a section divider, a D-pad ring. `componentId` names the palette
66662
+ * entry it came from (informational: the layout is self-contained).
66663
+ */
66664
+ var RemoteLayoutGroupSchema = zod.z.object({
66665
+ id: zod.z.string().min(1).max(40),
66666
+ componentId: zod.z.string().min(1).max(80),
66667
+ frame: RemoteGroupFrameSchema,
66668
+ row: zod.z.number().int().min(0).max(24),
66669
+ col: zod.z.number().int().min(0).max(24),
66670
+ rowSpan: zod.z.number().int().min(1).max(24),
66671
+ colSpan: zod.z.number().int().min(1).max(24),
66672
+ title: zod.z.string().min(1).max(40).optional()
66673
+ });
66674
+ function rectsOverlap(a, b) {
66675
+ return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
66676
+ }
66677
+ function rectContains(outer, inner) {
66678
+ return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
66679
+ }
66680
+ /**
66681
+ * The group rules of a layout: inside the grid, no two groups overlap, a
66682
+ * member lies inside its group, a divider holds nothing, and a button that is
66683
+ * not a member never sits inside someone else's rectangle (a group moves as
66684
+ * one thing, so a stray key under it would be left behind).
66685
+ */
66686
+ function refineGroups(grid, ctx) {
66687
+ const groups = grid.groups ?? [];
66688
+ const byId = /* @__PURE__ */ new Map();
66689
+ const fail = (message, path) => {
66690
+ ctx.addIssue({
66691
+ code: "custom",
66692
+ message,
66693
+ path
66694
+ });
66695
+ };
66696
+ groups.forEach((group, index) => {
66697
+ if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
66698
+ "groups",
66699
+ index,
66700
+ "id"
66701
+ ]);
66702
+ byId.set(group.id, group);
66703
+ if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
66704
+ for (let other = 0; other < index; other += 1) {
66705
+ const earlier = groups[other];
66706
+ if (earlier !== void 0 && rectsOverlap(earlier, group)) {
66707
+ fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
66708
+ return;
66709
+ }
66710
+ }
66711
+ });
66712
+ grid.buttons.forEach((button, index) => {
66713
+ if (button.groupId !== void 0) {
66714
+ const group = byId.get(button.groupId);
66715
+ if (group === void 0) {
66716
+ fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
66717
+ "buttons",
66718
+ index,
66719
+ "groupId"
66720
+ ]);
66721
+ return;
66722
+ }
66723
+ if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
66724
+ "buttons",
66725
+ index,
66726
+ "groupId"
66727
+ ]);
66728
+ if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
66729
+ return;
66730
+ }
66731
+ const foreign = groups.find((group) => rectsOverlap(group, button));
66732
+ if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
66733
+ });
66734
+ }
65827
66735
  var RemoteLayoutPayloadSchema = zod.z.object({
65828
66736
  columns: zod.z.number().int().min(1).max(24),
65829
66737
  rows: zod.z.number().int().min(1).max(24),
65830
- buttons: zod.z.array(RemoteLayoutButtonSchema).max(200)
66738
+ buttons: zod.z.array(RemoteLayoutButtonSchema).max(200),
66739
+ groups: zod.z.array(RemoteLayoutGroupSchema).max(50).optional()
65831
66740
  }).superRefine((grid, ctx) => {
65832
66741
  const ids = /* @__PURE__ */ new Set();
65833
66742
  const taken = /* @__PURE__ */ new Map();
@@ -65864,6 +66773,7 @@ var RemoteLayoutPayloadSchema = zod.z.object({
65864
66773
  taken.set(cell, button.id);
65865
66774
  }
65866
66775
  });
66776
+ refineGroups(grid, ctx);
65867
66777
  });
65868
66778
  //#endregion
65869
66779
  //#region src/capabilities/remote.cap.ts
@@ -66093,6 +67003,121 @@ var templateCapability = {
66093
67003
  }
66094
67004
  };
66095
67005
  //#endregion
67006
+ //#region src/remote/remote-components.ts
67007
+ /**
67008
+ * A remote layout COMPONENT (D973): a reusable, named chunk of a remote — a
67009
+ * D-pad, a keypad, a section divider — offered in the template editor's
67010
+ * "Add component" palette. Pure data: the buttons are placed RELATIVE to the
67011
+ * component, and the frame, shape and tone are names from the closed
67012
+ * vocabularies the UI draws. An addon contributes components through the
67013
+ * `remote-layout-components` cap and ships no UI code.
67014
+ */
67015
+ var REMOTE_COMPONENT_CATEGORIES = [
67016
+ "power",
67017
+ "navigation",
67018
+ "volume",
67019
+ "numbers",
67020
+ "media",
67021
+ "colour",
67022
+ "apps",
67023
+ "layout"
67024
+ ];
67025
+ var RemoteComponentCategorySchema = zod.z.enum(REMOTE_COMPONENT_CATEGORIES);
67026
+ var MAX_COMPONENT_BUTTONS = 40;
67027
+ /** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
67028
+ var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
67029
+ var RemoteComponentButtonSchema = zod.z.object({
67030
+ /** Joined to the instance id when the component is inserted. */
67031
+ idSuffix: zod.z.string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
67032
+ row: zod.z.number().int().min(0).max(24),
67033
+ col: zod.z.number().int().min(0).max(24),
67034
+ rowSpan: zod.z.number().int().min(1).max(24),
67035
+ colSpan: zod.z.number().int().min(1).max(24),
67036
+ role: RemoteRoleSchema,
67037
+ icon: zod.z.string().min(1).max(60).optional(),
67038
+ label: zod.z.string().min(1).max(40).optional(),
67039
+ shape: RemoteButtonShapeSchema.optional(),
67040
+ tone: RemoteButtonToneSchema.optional()
67041
+ });
67042
+ var RemoteLayoutComponentSchema = zod.z.object({
67043
+ id: zod.z.string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
67044
+ name: zod.z.string().min(1).max(60),
67045
+ nameI18n: zod.z.object({
67046
+ en: zod.z.string().min(1).max(60),
67047
+ it: zod.z.string().min(1).max(60)
67048
+ }).optional(),
67049
+ /** A display icon key, resolved by the UI like a button icon. */
67050
+ icon: zod.z.string().min(1).max(60),
67051
+ category: RemoteComponentCategorySchema,
67052
+ size: zod.z.object({
67053
+ rows: zod.z.number().int().min(1).max(24),
67054
+ cols: zod.z.number().int().min(1).max(24)
67055
+ }),
67056
+ /** The frame drawn around the whole component, or null for loose buttons. */
67057
+ group: zod.z.object({
67058
+ frame: RemoteGroupFrameSchema,
67059
+ title: zod.z.string().min(1).max(40).optional()
67060
+ }).nullable(),
67061
+ buttons: zod.z.array(RemoteComponentButtonSchema).max(40)
67062
+ }).superRefine((component, ctx) => {
67063
+ const suffixes = /* @__PURE__ */ new Set();
67064
+ component.buttons.forEach((button, index) => {
67065
+ if (suffixes.has(button.idSuffix)) ctx.addIssue({
67066
+ code: "custom",
67067
+ message: `button "${button.idSuffix}" is listed twice`,
67068
+ path: [
67069
+ "buttons",
67070
+ index,
67071
+ "idSuffix"
67072
+ ]
67073
+ });
67074
+ suffixes.add(button.idSuffix);
67075
+ if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
67076
+ ctx.addIssue({
67077
+ code: "custom",
67078
+ message: `button "${button.idSuffix}" leaves the component`,
67079
+ path: ["buttons", index]
67080
+ });
67081
+ return;
67082
+ }
67083
+ for (let other = 0; other < index; other += 1) {
67084
+ const earlier = component.buttons[other];
67085
+ if (earlier !== void 0 && rectsOverlap(earlier, button)) {
67086
+ ctx.addIssue({
67087
+ code: "custom",
67088
+ message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
67089
+ path: ["buttons", index]
67090
+ });
67091
+ return;
67092
+ }
67093
+ }
67094
+ });
67095
+ if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
67096
+ code: "custom",
67097
+ message: "a divider component holds no buttons",
67098
+ path: ["buttons"]
67099
+ });
67100
+ });
67101
+ //#endregion
67102
+ //#region src/capabilities/remote-layout-components.cap.ts
67103
+ /**
67104
+ * remote-layout-components — the palette of the remote template editor's "Add
67105
+ * component" (D973). A provider DECLARES components as data (buttons placed
67106
+ * relative to the component, a frame, shapes and tones from closed
67107
+ * vocabularies the UI draws); it ships no UI code.
67108
+ *
67109
+ * Collection: `listComponents` returns an array, so the generated mount
67110
+ * concat-fans it across every enabled provider. The core-blocks builtin
67111
+ * provides the built-in set (`core:*`); an addon adds its own under
67112
+ * `<addonId>:<name>`. The caller dedupes by id, built-ins first.
67113
+ */
67114
+ var remoteLayoutComponentsCapability = {
67115
+ name: "remote-layout-components",
67116
+ scope: "system",
67117
+ mode: "collection",
67118
+ methods: { listComponents: require_sleep.method(zod.z.object({}), zod.z.array(RemoteLayoutComponentSchema), { auth: "protected" }) }
67119
+ };
67120
+ //#endregion
66096
67121
  //#region src/capabilities/button.cap.ts
66097
67122
  /**
66098
67123
  * button — device-scoped capability for HA `button.*` / `input_button.*`
@@ -73343,6 +74368,14 @@ var PetFeederDeviceStatusSchema = zod.z.enum([
73343
74368
  "on_batteries"
73344
74369
  ]);
73345
74370
  var gramsPortion = zod.z.number().int().min(4).max(200);
74371
+ /** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
74372
+ * hardware's own unit). The bounds are grams; a caller entering another unit
74373
+ * converts to grams first (`paramMeta`, D972). The STATUS fields above stay
74374
+ * plain grams — they are not readings in the kilogram base. */
74375
+ var gramsParam = gramsPortion.meta(paramMeta({
74376
+ quantity: "mass",
74377
+ unit: "g"
74378
+ }));
73346
74379
  var PetFeederStatusSchema = zod.z.object({
73347
74380
  /** Food in the TANK (hopper), as the feeder's own sensor reports it
73348
74381
  * (`state.food`). A coarse level code, not grams — `0` is empty. The
@@ -73431,9 +74464,9 @@ var petFeederCapability = {
73431
74464
  */
73432
74465
  feed: require_sleep.method(zod.z.object({
73433
74466
  deviceId: zod.z.number().int().nonnegative(),
73434
- grams: gramsPortion.optional(),
73435
- hopper1: gramsPortion.optional(),
73436
- hopper2: gramsPortion.optional()
74467
+ grams: gramsParam.optional(),
74468
+ hopper1: gramsParam.optional(),
74469
+ hopper2: gramsParam.optional()
73437
74470
  }), zod.z.void(), {
73438
74471
  kind: "mutation",
73439
74472
  auth: "protected"
@@ -82154,6 +83187,430 @@ var PIPELINE_OWNER_CAPABILITY_NAMES = [
82154
83187
  "pipeline-runner"
82155
83188
  ];
82156
83189
  //#endregion
83190
+ //#region src/remote/remote-layout-ops.ts
83191
+ var LAYOUT_EDIT_REFUSALS = [
83192
+ "outside",
83193
+ "overlap",
83194
+ "unknown-group",
83195
+ "too-many"
83196
+ ];
83197
+ var refuse = (reason) => ({
83198
+ ok: false,
83199
+ reason
83200
+ });
83201
+ function withGroups(layout, groups) {
83202
+ const { groups: _previous, ...rest } = layout;
83203
+ return groups.length === 0 ? rest : {
83204
+ ...rest,
83205
+ groups: [...groups]
83206
+ };
83207
+ }
83208
+ function fitsGrid(layout, rect) {
83209
+ return rect.row >= 0 && rect.col >= 0 && rect.row + rect.rowSpan <= layout.rows && rect.col + rect.colSpan <= layout.columns;
83210
+ }
83211
+ /** The columns a component must be scaled by to span the whole grid width, or null when it does not divide. */
83212
+ function fillWidthScale(layout, component) {
83213
+ const factor = layout.columns / component.size.cols;
83214
+ return Number.isInteger(factor) && factor >= 1 ? factor : null;
83215
+ }
83216
+ function titleOf(title) {
83217
+ return title === void 0 ? {} : { title };
83218
+ }
83219
+ function slug$1(value) {
83220
+ return (value.includes(":") ? value.slice(value.indexOf(":") + 1) : value).replace(/[^a-z0-9_-]/g, "-").slice(0, 16);
83221
+ }
83222
+ function uniqueId(base, taken) {
83223
+ if (!taken.has(base)) return base;
83224
+ for (let n = 2;; n += 1) {
83225
+ const candidate = `${base}-${n}`;
83226
+ if (!taken.has(candidate)) return candidate;
83227
+ }
83228
+ }
83229
+ function freePrefix(base, placed, taken) {
83230
+ for (let n = 1;; n += 1) {
83231
+ const candidate = n === 1 ? base : `${base}-${n}`;
83232
+ if (!placed.some((p) => taken.has(`${candidate}-${p.spec.idSuffix}`))) return candidate;
83233
+ }
83234
+ }
83235
+ /**
83236
+ * Put `component` on the layout with its top-left cell at `at`. A component
83237
+ * with a group becomes one group whose rectangle is the whole component and
83238
+ * whose members are its buttons; one without is a set of loose buttons. The
83239
+ * grid does not grow: a component that does not fit is refused `outside`, one
83240
+ * that would sit on an existing key or group `overlap`.
83241
+ */
83242
+ function insertComponent(layout, component, at, options = {}) {
83243
+ const rowScale = options.scale?.rows ?? 1;
83244
+ const colScale = options.scale?.cols ?? 1;
83245
+ const bounds = {
83246
+ row: at.row,
83247
+ col: at.col,
83248
+ rowSpan: component.size.rows * rowScale,
83249
+ colSpan: component.size.cols * colScale
83250
+ };
83251
+ if (!fitsGrid(layout, bounds)) return refuse("outside");
83252
+ const specs = [...component.buttons, ...options.extras ?? []];
83253
+ const placed = specs.map((spec) => ({
83254
+ spec,
83255
+ rect: {
83256
+ row: at.row + spec.row * rowScale,
83257
+ col: at.col + spec.col * colScale,
83258
+ rowSpan: spec.rowSpan * rowScale,
83259
+ colSpan: spec.colSpan * colScale
83260
+ }
83261
+ }));
83262
+ const groups = layout.groups ?? [];
83263
+ if ((component.group === null ? placed.map((p) => p.rect) : [bounds]).some((rect) => layout.buttons.some((b) => rectsOverlap(b, rect)) || groups.some((g) => rectsOverlap(g, rect)))) return refuse("overlap");
83264
+ if (layout.buttons.length + specs.length > 200 || groups.length + (component.group === null ? 0 : 1) > 50) return refuse("too-many");
83265
+ const base = slug$1(component.id);
83266
+ const takenButtons = new Set(layout.buttons.map((b) => b.id));
83267
+ const takenGroups = new Set(groups.map((g) => g.id));
83268
+ const groupId = component.group === null ? null : uniqueId(base, takenGroups);
83269
+ const prefix = groupId ?? freePrefix(base, placed, takenButtons);
83270
+ const buttons = placed.map(({ spec, rect }) => ({
83271
+ id: `${prefix}-${spec.idSuffix}`,
83272
+ ...rect,
83273
+ role: spec.role,
83274
+ ...spec.icon === void 0 ? {} : { icon: spec.icon },
83275
+ ...spec.label === void 0 ? {} : { label: spec.label },
83276
+ ...spec.shape === void 0 ? {} : { shape: spec.shape },
83277
+ ...spec.tone === void 0 ? {} : { tone: spec.tone },
83278
+ ...groupId === null ? {} : { groupId }
83279
+ }));
83280
+ const group = component.group === null || groupId === null ? [] : [{
83281
+ id: groupId,
83282
+ componentId: component.id,
83283
+ frame: component.group.frame,
83284
+ ...bounds,
83285
+ ...titleOf(options.title ?? component.group.title)
83286
+ }];
83287
+ return {
83288
+ ok: true,
83289
+ layout: withGroups({
83290
+ ...layout,
83291
+ buttons: [...layout.buttons, ...buttons]
83292
+ }, [...groups, ...group]),
83293
+ groupId,
83294
+ buttonIds: buttons.map((b) => b.id)
83295
+ };
83296
+ }
83297
+ /** Move a group and every member by the same offset, atomically. */
83298
+ function moveGroup(layout, groupId, to) {
83299
+ const groups = layout.groups ?? [];
83300
+ const group = groups.find((g) => g.id === groupId);
83301
+ if (group === void 0) return refuse("unknown-group");
83302
+ const moved = {
83303
+ ...group,
83304
+ row: to.row,
83305
+ col: to.col
83306
+ };
83307
+ if (!fitsGrid(layout, moved)) return refuse("outside");
83308
+ const dRow = to.row - group.row;
83309
+ const dCol = to.col - group.col;
83310
+ const isMember = (b) => b.groupId === groupId;
83311
+ if (groups.some((g) => g.id !== groupId && rectsOverlap(g, moved)) || layout.buttons.some((b) => !isMember(b) && rectsOverlap(b, moved))) return refuse("overlap");
83312
+ return {
83313
+ ok: true,
83314
+ layout: {
83315
+ ...layout,
83316
+ buttons: layout.buttons.map((b) => isMember(b) ? {
83317
+ ...b,
83318
+ row: b.row + dRow,
83319
+ col: b.col + dCol
83320
+ } : b),
83321
+ groups: groups.map((g) => g.id === groupId ? moved : g)
83322
+ }
83323
+ };
83324
+ }
83325
+ /**
83326
+ * Delete a group. With `keepButtons` its members stay where they are as loose
83327
+ * keys; without, they go with it.
83328
+ */
83329
+ function removeGroup(layout, groupId, options) {
83330
+ const groups = layout.groups ?? [];
83331
+ if (!groups.some((g) => g.id === groupId)) return refuse("unknown-group");
83332
+ const buttons = options.keepButtons ? layout.buttons.map((b) => {
83333
+ if (b.groupId !== groupId) return b;
83334
+ const { groupId: _gone, ...rest } = b;
83335
+ return rest;
83336
+ }) : layout.buttons.filter((b) => b.groupId !== groupId);
83337
+ return {
83338
+ ok: true,
83339
+ layout: withGroups({
83340
+ ...layout,
83341
+ buttons
83342
+ }, groups.filter((g) => g.id !== groupId))
83343
+ };
83344
+ }
83345
+ /** Dissolve a group, keeping its buttons in place. */
83346
+ function ungroup(layout, groupId) {
83347
+ return removeGroup(layout, groupId, { keepButtons: true });
83348
+ }
83349
+ //#endregion
83350
+ //#region src/remote/remote-builtin-components.ts
83351
+ var btn = (idSuffix, row, col, init) => ({
83352
+ idSuffix,
83353
+ row,
83354
+ col,
83355
+ rowSpan: 1,
83356
+ colSpan: 1,
83357
+ ...init
83358
+ });
83359
+ function rocker(id, name, it, frame, title, buttons, size) {
83360
+ return {
83361
+ id,
83362
+ name,
83363
+ nameI18n: {
83364
+ en: name,
83365
+ it
83366
+ },
83367
+ icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
83368
+ category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
83369
+ size,
83370
+ group: title === null ? { frame } : {
83371
+ frame,
83372
+ title
83373
+ },
83374
+ buttons
83375
+ };
83376
+ }
83377
+ var BUILTIN_REMOTE_COMPONENTS = [
83378
+ {
83379
+ id: "core:power",
83380
+ name: "Power",
83381
+ nameI18n: {
83382
+ en: "Power",
83383
+ it: "Accensione"
83384
+ },
83385
+ icon: "power",
83386
+ category: "power",
83387
+ size: {
83388
+ rows: 1,
83389
+ cols: 1
83390
+ },
83391
+ group: null,
83392
+ buttons: [btn("power", 0, 0, {
83393
+ role: "power",
83394
+ shape: "round",
83395
+ tone: "danger"
83396
+ })]
83397
+ },
83398
+ {
83399
+ id: "core:dpad",
83400
+ name: "D-pad with OK",
83401
+ nameI18n: {
83402
+ en: "D-pad with OK",
83403
+ it: "Croce direzionale con OK"
83404
+ },
83405
+ icon: "gamepad-2",
83406
+ category: "navigation",
83407
+ size: {
83408
+ rows: 3,
83409
+ cols: 3
83410
+ },
83411
+ group: { frame: "dpad" },
83412
+ buttons: [
83413
+ btn("up", 0, 1, { role: "up" }),
83414
+ btn("left", 1, 0, { role: "left" }),
83415
+ btn("ok", 1, 1, {
83416
+ role: "ok",
83417
+ shape: "round",
83418
+ tone: "accent"
83419
+ }),
83420
+ btn("right", 1, 2, { role: "right" }),
83421
+ btn("down", 2, 1, { role: "down" })
83422
+ ]
83423
+ },
83424
+ rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
83425
+ role: "volume_up",
83426
+ shape: "pill"
83427
+ }), btn("down", 1, 0, {
83428
+ role: "volume_down",
83429
+ shape: "pill"
83430
+ })], {
83431
+ rows: 2,
83432
+ cols: 1
83433
+ }),
83434
+ rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
83435
+ role: "channel_up",
83436
+ shape: "pill"
83437
+ }), btn("down", 1, 0, {
83438
+ role: "channel_down",
83439
+ shape: "pill"
83440
+ })], {
83441
+ rows: 2,
83442
+ cols: 1
83443
+ }),
83444
+ rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
83445
+ role: "custom:minus",
83446
+ label: "−",
83447
+ shape: "pill"
83448
+ }), btn("plus", 0, 1, {
83449
+ role: "custom:plus",
83450
+ label: "+",
83451
+ shape: "pill"
83452
+ })], {
83453
+ rows: 1,
83454
+ cols: 2
83455
+ }),
83456
+ {
83457
+ id: "core:keypad",
83458
+ name: "Numeric keypad",
83459
+ nameI18n: {
83460
+ en: "Numeric keypad",
83461
+ it: "Tastierino numerico"
83462
+ },
83463
+ icon: "grid-3x3",
83464
+ category: "numbers",
83465
+ size: {
83466
+ rows: 4,
83467
+ cols: 3
83468
+ },
83469
+ group: { frame: "keypad" },
83470
+ buttons: [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })]
83471
+ },
83472
+ {
83473
+ id: "core:colour-keys",
83474
+ name: "Colour keys",
83475
+ nameI18n: {
83476
+ en: "Colour keys",
83477
+ it: "Tasti colorati"
83478
+ },
83479
+ icon: "palette",
83480
+ category: "colour",
83481
+ size: {
83482
+ rows: 1,
83483
+ cols: 4
83484
+ },
83485
+ group: null,
83486
+ buttons: [
83487
+ btn("red", 0, 0, {
83488
+ role: "custom:color_red",
83489
+ tone: "red"
83490
+ }),
83491
+ btn("green", 0, 1, {
83492
+ role: "custom:color_green",
83493
+ tone: "green"
83494
+ }),
83495
+ btn("yellow", 0, 2, {
83496
+ role: "custom:color_yellow",
83497
+ tone: "yellow"
83498
+ }),
83499
+ btn("blue", 0, 3, {
83500
+ role: "custom:color_blue",
83501
+ tone: "blue"
83502
+ })
83503
+ ]
83504
+ },
83505
+ {
83506
+ id: "core:media-transport",
83507
+ name: "Media transport",
83508
+ nameI18n: {
83509
+ en: "Media transport",
83510
+ it: "Controlli multimediali"
83511
+ },
83512
+ icon: "play",
83513
+ category: "media",
83514
+ size: {
83515
+ rows: 1,
83516
+ cols: 5
83517
+ },
83518
+ group: null,
83519
+ buttons: [
83520
+ btn("rewind", 0, 0, { role: "rewind" }),
83521
+ btn("play", 0, 1, { role: "play" }),
83522
+ btn("pause", 0, 2, { role: "pause" }),
83523
+ btn("forward", 0, 3, { role: "fast_forward" }),
83524
+ btn("stop", 0, 4, { role: "stop" })
83525
+ ]
83526
+ },
83527
+ {
83528
+ id: "core:apps-row",
83529
+ name: "App row",
83530
+ nameI18n: {
83531
+ en: "App row",
83532
+ it: "Riga di app"
83533
+ },
83534
+ icon: "app-window",
83535
+ category: "apps",
83536
+ size: {
83537
+ rows: 1,
83538
+ cols: 4
83539
+ },
83540
+ group: null,
83541
+ buttons: [
83542
+ 1,
83543
+ 2,
83544
+ 3,
83545
+ 4
83546
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` }))
83547
+ },
83548
+ {
83549
+ id: "core:back-home-menu",
83550
+ name: "Back, Home, Menu",
83551
+ nameI18n: {
83552
+ en: "Back, Home, Menu",
83553
+ it: "Indietro, Home, Menu"
83554
+ },
83555
+ icon: "house",
83556
+ category: "navigation",
83557
+ size: {
83558
+ rows: 1,
83559
+ cols: 3
83560
+ },
83561
+ group: null,
83562
+ buttons: [
83563
+ btn("back", 0, 0, { role: "back" }),
83564
+ btn("home", 0, 1, { role: "home" }),
83565
+ btn("menu", 0, 2, { role: "menu" })
83566
+ ]
83567
+ },
83568
+ {
83569
+ id: "core:section-divider",
83570
+ name: "Section divider",
83571
+ nameI18n: {
83572
+ en: "Section divider",
83573
+ it: "Divisore di sezione"
83574
+ },
83575
+ icon: "separator-horizontal",
83576
+ category: "layout",
83577
+ size: {
83578
+ rows: 1,
83579
+ cols: 1
83580
+ },
83581
+ group: {
83582
+ frame: "divider",
83583
+ title: "Section"
83584
+ },
83585
+ buttons: []
83586
+ },
83587
+ {
83588
+ id: "core:panel",
83589
+ name: "Titled panel",
83590
+ nameI18n: {
83591
+ en: "Titled panel",
83592
+ it: "Pannello con titolo"
83593
+ },
83594
+ icon: "panel-top",
83595
+ category: "layout",
83596
+ size: {
83597
+ rows: 2,
83598
+ cols: 3
83599
+ },
83600
+ group: {
83601
+ frame: "panel",
83602
+ title: "Panel"
83603
+ },
83604
+ buttons: []
83605
+ }
83606
+ ];
83607
+ /** A built-in component by id (the presets compose from these). */
83608
+ function builtinRemoteComponent(id) {
83609
+ const found = BUILTIN_REMOTE_COMPONENTS.find((component) => component.id === id);
83610
+ if (found === void 0) throw new Error(`no built-in remote component "${id}"`);
83611
+ return found;
83612
+ }
83613
+ //#endregion
82157
83614
  //#region src/ir/ir-error.ts
82158
83615
  var IrCodecError = class extends Error {
82159
83616
  code;
@@ -82891,465 +84348,6 @@ function numericAccept() {
82891
84348
  return historyAccept("statistic").filter((field) => field.valueKind === "number");
82892
84349
  }
82893
84350
  //#endregion
82894
- //#region src/units/unit-table.ts
82895
- /**
82896
- * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
82897
- * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
82898
- * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
82899
- * decimals). K derives from `°C = K − 273.15`.
82900
- */
82901
- var UNIT_TABLE = {
82902
- "°C": {
82903
- dimension: "temperature",
82904
- factor: 1,
82905
- offset: 0
82906
- },
82907
- "°F": {
82908
- dimension: "temperature",
82909
- factor: 5 / 9,
82910
- offset: -160 / 9
82911
- },
82912
- K: {
82913
- dimension: "temperature",
82914
- factor: 1,
82915
- offset: -273.15
82916
- },
82917
- hPa: {
82918
- dimension: "pressure",
82919
- factor: 1,
82920
- offset: 0
82921
- },
82922
- kPa: {
82923
- dimension: "pressure",
82924
- factor: 10,
82925
- offset: 0
82926
- },
82927
- Pa: {
82928
- dimension: "pressure",
82929
- factor: .01,
82930
- offset: 0
82931
- },
82932
- mbar: {
82933
- dimension: "pressure",
82934
- factor: 1,
82935
- offset: 0
82936
- },
82937
- bar: {
82938
- dimension: "pressure",
82939
- factor: 1e3,
82940
- offset: 0
82941
- },
82942
- inHg: {
82943
- dimension: "pressure",
82944
- factor: 33.8639,
82945
- offset: 0
82946
- },
82947
- mmHg: {
82948
- dimension: "pressure",
82949
- factor: 1.33322,
82950
- offset: 0
82951
- },
82952
- psi: {
82953
- dimension: "pressure",
82954
- factor: 68.9476,
82955
- offset: 0
82956
- },
82957
- "m/s": {
82958
- dimension: "speed",
82959
- factor: 1,
82960
- offset: 0
82961
- },
82962
- "km/h": {
82963
- dimension: "speed",
82964
- factor: 1 / 3.6,
82965
- offset: 0
82966
- },
82967
- mph: {
82968
- dimension: "speed",
82969
- factor: .44704,
82970
- offset: 0
82971
- },
82972
- kn: {
82973
- dimension: "speed",
82974
- factor: .514444,
82975
- offset: 0
82976
- },
82977
- "mm/h": {
82978
- dimension: "precipitation-rate",
82979
- factor: 1,
82980
- offset: 0
82981
- },
82982
- "in/h": {
82983
- dimension: "precipitation-rate",
82984
- factor: 25.4,
82985
- offset: 0
82986
- },
82987
- m: {
82988
- dimension: "length",
82989
- factor: 1,
82990
- offset: 0
82991
- },
82992
- mm: {
82993
- dimension: "length",
82994
- factor: .001,
82995
- offset: 0
82996
- },
82997
- cm: {
82998
- dimension: "length",
82999
- factor: .01,
83000
- offset: 0
83001
- },
83002
- km: {
83003
- dimension: "length",
83004
- factor: 1e3,
83005
- offset: 0
83006
- },
83007
- in: {
83008
- dimension: "length",
83009
- factor: .0254,
83010
- offset: 0
83011
- },
83012
- ft: {
83013
- dimension: "length",
83014
- factor: .3048,
83015
- offset: 0
83016
- },
83017
- mi: {
83018
- dimension: "length",
83019
- factor: 1609.344,
83020
- offset: 0
83021
- },
83022
- "m²": {
83023
- dimension: "area",
83024
- factor: 1,
83025
- offset: 0
83026
- },
83027
- lx: {
83028
- dimension: "illuminance",
83029
- factor: 1,
83030
- offset: 0
83031
- },
83032
- "W/m²": {
83033
- dimension: "irradiance",
83034
- factor: 1,
83035
- offset: 0
83036
- },
83037
- W: {
83038
- dimension: "power",
83039
- factor: 1,
83040
- offset: 0
83041
- },
83042
- kW: {
83043
- dimension: "power",
83044
- factor: 1e3,
83045
- offset: 0
83046
- },
83047
- VA: {
83048
- dimension: "apparent-power",
83049
- factor: 1,
83050
- offset: 0
83051
- },
83052
- Wh: {
83053
- dimension: "energy",
83054
- factor: 1,
83055
- offset: 0
83056
- },
83057
- kWh: {
83058
- dimension: "energy",
83059
- factor: 1e3,
83060
- offset: 0
83061
- },
83062
- MWh: {
83063
- dimension: "energy",
83064
- factor: 1e6,
83065
- offset: 0
83066
- },
83067
- V: {
83068
- dimension: "voltage",
83069
- factor: 1,
83070
- offset: 0
83071
- },
83072
- mV: {
83073
- dimension: "voltage",
83074
- factor: .001,
83075
- offset: 0
83076
- },
83077
- A: {
83078
- dimension: "current",
83079
- factor: 1,
83080
- offset: 0
83081
- },
83082
- mA: {
83083
- dimension: "current",
83084
- factor: .001,
83085
- offset: 0
83086
- },
83087
- ppm: {
83088
- dimension: "concentration-volume",
83089
- factor: 1,
83090
- offset: 0
83091
- },
83092
- ppb: {
83093
- dimension: "concentration-volume",
83094
- factor: .001,
83095
- offset: 0
83096
- },
83097
- "µg/m³": {
83098
- dimension: "concentration-mass",
83099
- factor: 1,
83100
- offset: 0
83101
- },
83102
- "mg/m³": {
83103
- dimension: "concentration-mass",
83104
- factor: 1e3,
83105
- offset: 0
83106
- },
83107
- kg: {
83108
- dimension: "mass",
83109
- factor: 1,
83110
- offset: 0
83111
- },
83112
- g: {
83113
- dimension: "mass",
83114
- factor: .001,
83115
- offset: 0
83116
- },
83117
- lb: {
83118
- dimension: "mass",
83119
- factor: .453592,
83120
- offset: 0
83121
- },
83122
- oz: {
83123
- dimension: "mass",
83124
- factor: .0283495,
83125
- offset: 0
83126
- },
83127
- "%": {
83128
- dimension: "percentage",
83129
- factor: 1,
83130
- offset: 0
83131
- },
83132
- L: {
83133
- dimension: "volume",
83134
- factor: 1,
83135
- offset: 0
83136
- },
83137
- mL: {
83138
- dimension: "volume",
83139
- factor: .001,
83140
- offset: 0
83141
- },
83142
- gal: {
83143
- dimension: "volume",
83144
- factor: 3.785411784,
83145
- offset: 0
83146
- },
83147
- s: {
83148
- dimension: "duration",
83149
- factor: 1,
83150
- offset: 0
83151
- },
83152
- ms: {
83153
- dimension: "duration",
83154
- factor: .001,
83155
- offset: 0
83156
- },
83157
- min: {
83158
- dimension: "duration",
83159
- factor: 60,
83160
- offset: 0
83161
- },
83162
- h: {
83163
- dimension: "duration",
83164
- factor: 3600,
83165
- offset: 0
83166
- },
83167
- d: {
83168
- dimension: "duration",
83169
- factor: 86400,
83170
- offset: 0
83171
- },
83172
- B: {
83173
- dimension: "information",
83174
- factor: 1,
83175
- offset: 0
83176
- },
83177
- KiB: {
83178
- dimension: "information",
83179
- factor: 1024,
83180
- offset: 0
83181
- },
83182
- MiB: {
83183
- dimension: "information",
83184
- factor: 1024 ** 2,
83185
- offset: 0
83186
- },
83187
- GiB: {
83188
- dimension: "information",
83189
- factor: 1024 ** 3,
83190
- offset: 0
83191
- },
83192
- TiB: {
83193
- dimension: "information",
83194
- factor: 1024 ** 4,
83195
- offset: 0
83196
- },
83197
- kB: {
83198
- dimension: "information",
83199
- factor: 1e3,
83200
- offset: 0
83201
- },
83202
- MB: {
83203
- dimension: "information",
83204
- factor: 1e6,
83205
- offset: 0
83206
- },
83207
- GB: {
83208
- dimension: "information",
83209
- factor: 1e9,
83210
- offset: 0
83211
- },
83212
- "°": {
83213
- dimension: "angle",
83214
- factor: 1,
83215
- offset: 0
83216
- },
83217
- dBFS: {
83218
- dimension: "sound-level",
83219
- factor: 1,
83220
- offset: 0
83221
- },
83222
- dBm: {
83223
- dimension: "signal-level",
83224
- factor: 1,
83225
- offset: 0
83226
- },
83227
- mired: {
83228
- dimension: "colour-temperature",
83229
- factor: 1,
83230
- offset: 0
83231
- },
83232
- kbps: {
83233
- dimension: "bitrate",
83234
- factor: 1,
83235
- offset: 0
83236
- },
83237
- Mbps: {
83238
- dimension: "bitrate",
83239
- factor: 1e3,
83240
- offset: 0
83241
- },
83242
- fps: {
83243
- dimension: "rate",
83244
- factor: 1,
83245
- offset: 0
83246
- }
83247
- };
83248
- //#endregion
83249
- //#region src/units/convert.ts
83250
- /**
83251
- * Pure unit-conversion API over `UNIT_TABLE`. No dependencies, no side effects.
83252
- *
83253
- * Conversion is refused (throws `UnitConversionError`, or returns `undefined`
83254
- * from the `try*` variant) whenever a unit is unknown or the two units belong
83255
- * to different dimensions — cross-dimension conversion is deliberately
83256
- * impossible so a render override can never make the value and its unit label
83257
- * disagree.
83258
- *
83259
- * Callers pass units through `normalizeUnit` (`../device/source-info.ts`) BEFORE
83260
- * calling here — the table keys only canonical spellings.
83261
- */
83262
- /** Thrown by `convertUnit` when a conversion is refused. Carries the offending
83263
- * units + a machine-readable `reason` for callers that branch on the cause. */
83264
- var UnitConversionError = class extends Error {
83265
- from;
83266
- to;
83267
- reason;
83268
- constructor(from, to, reason) {
83269
- super(`[units] cannot convert '${from}' → '${to}': ${reason}`);
83270
- this.name = "UnitConversionError";
83271
- this.from = from;
83272
- this.to = to;
83273
- this.reason = reason;
83274
- }
83275
- };
83276
- /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
83277
- function specFor(unit) {
83278
- return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
83279
- }
83280
- /** The dimension a unit belongs to, or `undefined` for an unknown unit. */
83281
- function unitDimension(unit) {
83282
- return specFor(unit)?.dimension;
83283
- }
83284
- /** Value in `spec`'s unit → its dimension's canonical unit. */
83285
- function toCanonical(value, spec) {
83286
- return value * spec.factor + spec.offset;
83287
- }
83288
- /** Canonical-unit value → the unit described by `spec`. */
83289
- function fromCanonical(canonical, spec) {
83290
- return (canonical - spec.offset) / spec.factor;
83291
- }
83292
- /**
83293
- * Convert `value` from unit `from` to unit `to`. Identity when `from === to`.
83294
- * Throws `UnitConversionError` when either unit is unknown or the units belong
83295
- * to different dimensions.
83296
- */
83297
- function convertUnit(value, from, to) {
83298
- if (from === to) return value;
83299
- const fromSpec = specFor(from);
83300
- if (!fromSpec) throw new UnitConversionError(from, to, "unknown-from");
83301
- const toSpec = specFor(to);
83302
- if (!toSpec) throw new UnitConversionError(from, to, "unknown-to");
83303
- if (fromSpec.dimension !== toSpec.dimension) throw new UnitConversionError(from, to, "cross-dimension");
83304
- return fromCanonical(toCanonical(value, fromSpec), toSpec);
83305
- }
83306
- /**
83307
- * Graceful variant for the render path: returns the converted value, or
83308
- * `undefined` when the conversion is refused (unknown unit / cross-dimension).
83309
- * Never throws — a render caller falls back to the raw value + source unit.
83310
- */
83311
- function tryConvertUnit(value, from, to) {
83312
- if (from === to) return value;
83313
- const fromSpec = specFor(from);
83314
- if (!fromSpec) return void 0;
83315
- const toSpec = specFor(to);
83316
- if (!toSpec) return void 0;
83317
- if (fromSpec.dimension !== toSpec.dimension) return void 0;
83318
- return fromCanonical(toCanonical(value, fromSpec), toSpec);
83319
- }
83320
- /**
83321
- * Whether `value` can be converted between `from` and `to` (same dimension,
83322
- * both known). Cheap boolean probe for the UI's same-dimension unit picker and
83323
- * the resolver's convertibility branch — compute ONCE so a unit label and its
83324
- * value math can never disagree.
83325
- */
83326
- function canConvertUnit(from, to) {
83327
- if (from === to) return true;
83328
- const fromDim = unitDimension(from);
83329
- if (fromDim === void 0) return false;
83330
- return fromDim === unitDimension(to);
83331
- }
83332
- /**
83333
- * Every known unit spelling in a dimension (table insertion order — canonical
83334
- * unit first). Powers the UI's same-dimension unit picker.
83335
- */
83336
- /**
83337
- * Convert a difference, not a reading. Affine units (°F) shift an absolute
83338
- * value by their offset; a step of 1°F is 5/9°C, so the offset is cancelled.
83339
- */
83340
- function tryConvertDelta(value, from, to) {
83341
- if (from === to) return value;
83342
- const origin = tryConvertUnit(0, from, to);
83343
- const shifted = tryConvertUnit(value, from, to);
83344
- if (origin === void 0 || shifted === void 0) return void 0;
83345
- return shifted - origin;
83346
- }
83347
- function unitsForDimension(dimension) {
83348
- const out = [];
83349
- for (const [unit, spec] of Object.entries(UNIT_TABLE)) if (spec.dimension === dimension) out.push(unit);
83350
- return out;
83351
- }
83352
- //#endregion
83353
84351
  //#region src/composition/evaluate-field.ts
83354
84352
  /**
83355
84353
  * One composed field, evaluated from what its sources say NOW (D659).
@@ -96616,6 +97614,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
96616
97614
  access: "create",
96617
97615
  role: "admin"
96618
97616
  },
97617
+ "remoteLayoutComponents.listComponents": {
97618
+ capName: "remote-layout-components",
97619
+ capScope: "system",
97620
+ addonId: null,
97621
+ access: "view",
97622
+ role: "admin"
97623
+ },
96619
97624
  "runningState.getStatus": {
96620
97625
  capName: "running-state",
96621
97626
  capScope: "device",
@@ -98979,6 +99984,7 @@ var KNOWN_CAP_NAMES = [
98979
99984
  "recording-signal",
98980
99985
  "records",
98981
99986
  "remote",
99987
+ "remote-layout-components",
98982
99988
  "running-state",
98983
99989
  "scene-monitor",
98984
99990
  "script-runner",
@@ -99199,6 +100205,7 @@ var SYSTEM_CAP_NAMES = [
99199
100205
  "recording-export",
99200
100206
  "records",
99201
100207
  "remote",
100208
+ "remote-layout-components",
99202
100209
  "server-management",
99203
100210
  "settings-store",
99204
100211
  "smtp-provider",
@@ -101058,7 +102065,8 @@ var APPLIANCE_CAP_VOCABULARY = {
101058
102065
  min: 4,
101059
102066
  max: 200,
101060
102067
  step: 1,
101061
- unit: "g"
102068
+ unit: "g",
102069
+ quantity: "mass"
101062
102070
  },
101063
102071
  optional: true
101064
102072
  }]
@@ -101077,7 +102085,8 @@ var APPLIANCE_CAP_VOCABULARY = {
101077
102085
  min: 4,
101078
102086
  max: 200,
101079
102087
  step: 1,
101080
- unit: "g"
102088
+ unit: "g",
102089
+ quantity: "mass"
101081
102090
  },
101082
102091
  optional: true
101083
102092
  }, {
@@ -101088,7 +102097,8 @@ var APPLIANCE_CAP_VOCABULARY = {
101088
102097
  min: 4,
101089
102098
  max: 200,
101090
102099
  step: 1,
101091
- unit: "g"
102100
+ unit: "g",
102101
+ quantity: "mass"
101092
102102
  },
101093
102103
  optional: true
101094
102104
  }]
@@ -103194,6 +104204,7 @@ var SYSTEM_CAP_VOCABULARY = {
103194
104204
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
103195
104205
  records: system(INTERNAL),
103196
104206
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
104207
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
103197
104208
  "server-management": system(CLUSTER),
103198
104209
  "settings-store": system(INTERNAL),
103199
104210
  "smtp-provider": system(INTERNAL),
@@ -105181,6 +106192,7 @@ var CAPABILITY_NAMES = {
105181
106192
  recordingSignal: "recording-signal",
105182
106193
  records: "records",
105183
106194
  remote: "remote",
106195
+ remoteLayoutComponents: "remote-layout-components",
105184
106196
  runningState: "running-state",
105185
106197
  sceneMonitor: "scene-monitor",
105186
106198
  scriptRunner: "script-runner",
@@ -105820,6 +106832,10 @@ var CAPABILITY_ROUTER_KEYS = [
105820
106832
  key: "remote",
105821
106833
  name: "remote"
105822
106834
  },
106835
+ {
106836
+ key: "remoteLayoutComponents",
106837
+ name: "remote-layout-components"
106838
+ },
105823
106839
  {
105824
106840
  key: "runningState",
105825
106841
  name: "running-state"
@@ -106155,6 +107171,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
106155
107171
  recordingSignalCapability,
106156
107172
  recordsCapability,
106157
107173
  remoteCapability,
107174
+ remoteLayoutComponentsCapability,
106158
107175
  runningStateCapability,
106159
107176
  sceneMonitorCapability,
106160
107177
  scriptRunnerCapability,
@@ -110945,6 +111962,7 @@ function createSystemProxy(api) {
110945
111962
  sendCommand: (input) => dispatch("remote", "sendCommand", "mutation", input),
110946
111963
  sendMediaCommand: (input) => dispatch("remote", "sendMediaCommand", "mutation", input)
110947
111964
  },
111965
+ remoteLayoutComponents: { listComponents: (input) => dispatch("remoteLayoutComponents", "listComponents", "query", input) },
110948
111966
  serverManagement: {
110949
111967
  getServerPackageStatus: (input) => dispatch("serverManagement", "getServerPackageStatus", "query", input),
110950
111968
  checkServerUpdate: (input) => dispatch("serverManagement", "checkServerUpdate", "mutation", input),
@@ -115180,8 +116198,6 @@ var NC_TEMPLATE_VARS = [
115180
116198
  * delete it from here then, and the served catalog names it again.
115181
116199
  */
115182
116200
  var NC_TEMPLATE_HELD_BACK_SYSTEM_EVENT_KINDS = [
115183
- "node-restarting",
115184
- "node-updating",
115185
116201
  "alert-critical",
115186
116202
  "alert-error",
115187
116203
  "alert-critical-resolved",
@@ -122025,6 +123041,7 @@ exports.BENCH_ROOT_STEP_ID = BENCH_ROOT_STEP_ID;
122025
123041
  exports.BINARY_AGGREGATES = require_composition.BINARY_AGGREGATES;
122026
123042
  exports.BIRTH_DECISION_DEFAULT_LIMIT = BIRTH_DECISION_DEFAULT_LIMIT;
122027
123043
  exports.BOOT_RECOVERY_BACKOFF_MS = require_sleep.BOOT_RECOVERY_BACKOFF_MS;
123044
+ exports.BUILTIN_REMOTE_COMPONENTS = BUILTIN_REMOTE_COMPONENTS;
122028
123045
  exports.BUILTIN_RESULT_KINDS = BUILTIN_RESULT_KINDS;
122029
123046
  exports.BackgroundRunRegistry = BackgroundRunRegistry;
122030
123047
  exports.BacklightModeSchema = BacklightModeSchema;
@@ -122753,6 +123770,7 @@ exports.KeychainWriteRefusedError = require_sleep.KeychainWriteRefusedError;
122753
123770
  exports.LABEL_SUPPLY_MACRO = LABEL_SUPPLY_MACRO;
122754
123771
  exports.LABEL_SUPPLY_STEP_IDS = LABEL_SUPPLY_STEP_IDS;
122755
123772
  exports.LABEL_SUPPLY_VOCABULARIES = LABEL_SUPPLY_VOCABULARIES;
123773
+ exports.LAYOUT_EDIT_REFUSALS = LAYOUT_EDIT_REFUSALS;
122756
123774
  exports.LEGACY_READ_ONLY_CONFIG_KEY = LEGACY_READ_ONLY_CONFIG_KEY;
122757
123775
  exports.LIGHT_SHARED_CAPS = LIGHT_SHARED_CAPS;
122758
123776
  exports.LINKED_DEVICES_SLOT = LINKED_DEVICES_SLOT;
@@ -122859,6 +123877,7 @@ exports.MAX_BIRTH_LATENCY_PROXY_MS = MAX_BIRTH_LATENCY_PROXY_MS;
122859
123877
  exports.MAX_CAMERA_SENSORS = MAX_CAMERA_SENSORS;
122860
123878
  exports.MAX_CLIP_EVENT_IDS = MAX_CLIP_EVENT_IDS;
122861
123879
  exports.MAX_CLIP_LABELS = MAX_CLIP_LABELS;
123880
+ exports.MAX_COMPONENT_BUTTONS = MAX_COMPONENT_BUTTONS;
122862
123881
  exports.MAX_COMPOSITION_CODE_LENGTH = require_composition.MAX_COMPOSITION_CODE_LENGTH;
122863
123882
  exports.MAX_COMPOSITION_FEATURES = require_composition.MAX_COMPOSITION_FEATURES;
122864
123883
  exports.MAX_COMPOSITION_FIELDS_PER_FEATURE = require_composition.MAX_COMPOSITION_FIELDS_PER_FEATURE;
@@ -122872,6 +123891,7 @@ exports.MAX_EXPRESSION_EVAL_STEPS = require_composition.MAX_EXPRESSION_EVAL_STEP
122872
123891
  exports.MAX_EXPRESSION_SOURCE_LENGTH = require_composition.MAX_EXPRESSION_SOURCE_LENGTH;
122873
123892
  exports.MAX_HELPER_MEMBERS = require_composition.MAX_HELPER_MEMBERS;
122874
123893
  exports.MAX_KEYS = MAX_KEYS;
123894
+ exports.MAX_LAYOUT_GROUPS = MAX_LAYOUT_GROUPS;
122875
123895
  exports.MAX_LAYOUT_SPAN = MAX_LAYOUT_SPAN;
122876
123896
  exports.MAX_REASONS_PER_KEY = MAX_REASONS_PER_KEY;
122877
123897
  exports.MAX_REMOTE_BINDINGS = MAX_REMOTE_BINDINGS;
@@ -123395,6 +124415,8 @@ exports.PtzPresetFunctionSchema = PtzPresetFunctionSchema;
123395
124415
  exports.PtzPresetSchema = PtzPresetSchema;
123396
124416
  exports.PtzStatusSchema = PtzStatusSchema;
123397
124417
  exports.QUANTITY_BASE = require_sleep.QUANTITY_BASE;
124418
+ exports.QuantityUnitError = QuantityUnitError;
124419
+ exports.QuantityValueSchema = QuantityValueSchema;
123398
124420
  exports.QueryFilterSchema = QueryFilterSchema;
123399
124421
  exports.RATE_CONTROL_RELAXED = RATE_CONTROL_RELAXED;
123400
124422
  exports.RATE_CONTROL_TIGHT = RATE_CONTROL_TIGHT;
@@ -123438,8 +124460,12 @@ exports.RECOVERY_VIEW_PX = RECOVERY_VIEW_PX;
123438
124460
  exports.REDACTED_SECRET = require_sleep.REDACTED_SECRET;
123439
124461
  exports.REFERENCE_COMPUTE = REFERENCE_COMPUTE;
123440
124462
  exports.REFERENCE_MATCH_FRACTION = REFERENCE_MATCH_FRACTION;
124463
+ exports.REMOTE_BUTTON_SHAPES = REMOTE_BUTTON_SHAPES;
124464
+ exports.REMOTE_BUTTON_TONES = REMOTE_BUTTON_TONES;
124465
+ exports.REMOTE_COMPONENT_CATEGORIES = REMOTE_COMPONENT_CATEGORIES;
123441
124466
  exports.REMOTE_CONFIG_REFUSALS = REMOTE_CONFIG_REFUSALS;
123442
124467
  exports.REMOTE_CUSTOM_ROLE_PREFIX = REMOTE_CUSTOM_ROLE_PREFIX;
124468
+ exports.REMOTE_GROUP_FRAMES = REMOTE_GROUP_FRAMES;
123443
124469
  exports.REMOTE_KNOWN_ROLES = REMOTE_KNOWN_ROLES;
123444
124470
  exports.REMOTE_MAX_REPEAT = REMOTE_MAX_REPEAT;
123445
124471
  exports.REMOTE_MEDIA_COMMANDS = REMOTE_MEDIA_COMMANDS;
@@ -123558,10 +124584,17 @@ exports.RemoteAssumedStateSchema = RemoteAssumedStateSchema;
123558
124584
  exports.RemoteBindingActionSchema = RemoteBindingActionSchema;
123559
124585
  exports.RemoteBindingSchema = RemoteBindingSchema;
123560
124586
  exports.RemoteBoundCommandSchema = RemoteBoundCommandSchema;
124587
+ exports.RemoteButtonShapeSchema = RemoteButtonShapeSchema;
124588
+ exports.RemoteButtonToneSchema = RemoteButtonToneSchema;
123561
124589
  exports.RemoteCommandListSchema = RemoteCommandListSchema;
124590
+ exports.RemoteComponentButtonSchema = RemoteComponentButtonSchema;
124591
+ exports.RemoteComponentCategorySchema = RemoteComponentCategorySchema;
123562
124592
  exports.RemoteConfigSchema = RemoteConfigSchema;
123563
124593
  exports.RemoteCustomButtonSchema = RemoteCustomButtonSchema;
124594
+ exports.RemoteGroupFrameSchema = RemoteGroupFrameSchema;
123564
124595
  exports.RemoteLayoutButtonSchema = RemoteLayoutButtonSchema;
124596
+ exports.RemoteLayoutComponentSchema = RemoteLayoutComponentSchema;
124597
+ exports.RemoteLayoutGroupSchema = RemoteLayoutGroupSchema;
123565
124598
  exports.RemoteLayoutPayloadSchema = RemoteLayoutPayloadSchema;
123566
124599
  exports.RemoteMediaCommandResultSchema = RemoteMediaCommandResultSchema;
123567
124600
  exports.RemoteMediaCommandSchema = RemoteMediaCommandSchema;
@@ -123937,6 +124970,7 @@ exports.UninstallNodeOutcomeSchema = UninstallNodeOutcomeSchema;
123937
124970
  exports.UninstallNodeResultSchema = UninstallNodeResultSchema;
123938
124971
  exports.UninstallPackageResultSchema = UninstallPackageResultSchema;
123939
124972
  exports.UnitConversionError = UnitConversionError;
124973
+ exports.UnitWarningSchema = UnitWarningSchema;
123940
124974
  exports.UnknownRecordKindError = require_sleep.UnknownRecordKindError;
123941
124975
  exports.UnreleasedClaimSchema = UnreleasedClaimSchema;
123942
124976
  exports.UnstampedEventMediaCountSchema = UnstampedEventMediaCountSchema;
@@ -124100,6 +125134,7 @@ exports.autoAssignProfiles = autoAssignProfiles;
124100
125134
  exports.automationControlCapability = automationControlCapability;
124101
125135
  exports.backupCapability = backupCapability;
124102
125136
  exports.bareAddonId = bareAddonId;
125137
+ exports.baseUnitForQuantity = baseUnitForQuantity;
124103
125138
  exports.baseUnitOf = require_sleep.baseUnitOf;
124104
125139
  exports.batteryCapability = batteryCapability;
124105
125140
  exports.bestLocationMatch = bestLocationMatch;
@@ -124131,6 +125166,7 @@ exports.buildTriggerTrace = buildTriggerTrace;
124131
125166
  exports.buildVideoArgs = require_composition.buildVideoArgs;
124132
125167
  exports.buildViewerLink = buildViewerLink;
124133
125168
  exports.builtCapabilityBlockers = builtCapabilityBlockers;
125169
+ exports.builtinRemoteComponent = builtinRemoteComponent;
124134
125170
  exports.bundledCamstack = bundledCamstack;
124135
125171
  exports.buttonCapability = buttonCapability;
124136
125172
  exports.calendarEventLocalSpan = calendarEventLocalSpan;
@@ -124148,6 +125184,7 @@ exports.canConvertUnit = canConvertUnit;
124148
125184
  exports.canonicalEgressPlan = canonicalEgressPlan;
124149
125185
  exports.canonicalIconKey = canonicalIconKey;
124150
125186
  exports.canonicalizeAppCallback = canonicalizeAppCallback;
125187
+ exports.capUnitResolver = capUnitResolver;
124151
125188
  exports.capabilityScopesOf = capabilityScopesOf;
124152
125189
  exports.capacityForModel = capacityForModel;
124153
125190
  exports.capacitySessionsOf = capacitySessionsOf;
@@ -124230,6 +125267,7 @@ exports.contactCapability = contactCapability;
124230
125267
  exports.containingParentIndex = containingParentIndex;
124231
125268
  exports.controlCapability = controlCapability;
124232
125269
  exports.convertPresetFeature = convertPresetFeature;
125270
+ exports.convertQuantityInput = convertQuantityInput;
124233
125271
  exports.convertUnit = convertUnit;
124234
125272
  exports.convertUnitTargets = convertUnitTargets;
124235
125273
  exports.coralUsableUnits = coralUsableUnits;
@@ -124418,6 +125456,7 @@ exports.fieldReads = require_composition.fieldReads;
124418
125456
  exports.fieldUnitKeys = fieldUnitKeys;
124419
125457
  exports.fieldsPinnedAtValueBelow = fieldsPinnedAtValueBelow;
124420
125458
  exports.filesystemBrowseCapability = filesystemBrowseCapability;
125459
+ exports.fillWidthScale = fillWidthScale;
124421
125460
  exports.findCandidateCycles = findCandidateCycles;
124422
125461
  exports.findCompositionCycles = findCompositionCycles;
124423
125462
  exports.findTimezone = findTimezone;
@@ -124482,6 +125521,7 @@ exports.inferExpressionKinds = inferExpressionKinds;
124482
125521
  exports.inferModelProvider = inferModelProvider;
124483
125522
  exports.ingestReading = ingestReading;
124484
125523
  exports.initialPoolMemoryState = initialPoolMemoryState;
125524
+ exports.insertComponent = insertComponent;
124485
125525
  exports.integralCadenceMs = require_composition.integralCadenceMs;
124486
125526
  exports.integralDraft = integralDraft;
124487
125527
  exports.integrateReading = require_composition.integrateReading;
@@ -124545,6 +125585,7 @@ exports.isObjectInput = isObjectInput;
124545
125585
  exports.isOccupancyRule = isOccupancyRule;
124546
125586
  exports.isOwnerOnlyEventCategory = require_event_category.isOwnerOnlyEventCategory;
124547
125587
  exports.isPlugApplianceType = isPlugApplianceType;
125588
+ exports.isQuantityValue = isQuantityValue;
124548
125589
  exports.isReadingQuantity = require_sleep.isReadingQuantity;
124549
125590
  exports.isRemoteCustomKey = isRemoteCustomKey;
124550
125591
  exports.isRemoteKnownRole = isRemoteKnownRole;
@@ -124660,6 +125701,7 @@ exports.motionDetectionCapability = motionDetectionCapability;
124660
125701
  exports.motionSourceLabel = motionSourceLabel;
124661
125702
  exports.motionTriggerCapability = motionTriggerCapability;
124662
125703
  exports.motionZonesCapability = motionZonesCapability;
125704
+ exports.moveGroup = moveGroup;
124663
125705
  exports.mqttBrokerCapability = mqttBrokerCapability;
124664
125706
  exports.nativeObjectDetectionCapability = nativeObjectDetectionCapability;
124665
125707
  exports.navigationCapability = navigationCapability;
@@ -124675,12 +125717,15 @@ exports.nodePickerItems = nodePickerItems;
124675
125717
  exports.nodePin = require_sleep.nodePin;
124676
125718
  exports.nodeRemovalHookCapability = nodeRemovalHookCapability;
124677
125719
  exports.nodesCapability = nodesCapability;
125720
+ exports.normalizeActionUnits = normalizeActionUnits;
124678
125721
  exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
124679
125722
  exports.normalizeAudioLabel = normalizeAudioLabel;
125723
+ exports.normalizeConditionUnits = normalizeConditionUnits;
124680
125724
  exports.normalizeCoreBlockWrite = normalizeCoreBlockWrite;
124681
125725
  exports.normalizeNcMediaPolicy = normalizeNcMediaPolicy;
124682
125726
  exports.normalizeNodeId = require_sleep.normalizeNodeId;
124683
125727
  exports.normalizeOrigin = normalizeOrigin;
125728
+ exports.normalizeRecipeUnits = normalizeRecipeUnits;
124684
125729
  exports.normalizeSecretOwner = require_sleep.normalizeSecretOwner;
124685
125730
  exports.normalizeTokenScopes = normalizeTokenScopes;
124686
125731
  exports.normalizeUnit = normalizeUnit;
@@ -124712,6 +125757,8 @@ exports.paletteFromStored = paletteFromStored;
124712
125757
  exports.paletteLegend = paletteLegend;
124713
125758
  exports.paletteResultSchema = paletteResultSchema;
124714
125759
  exports.paletteSwatchSchema = paletteSwatchSchema;
125760
+ exports.paramMeta = paramMeta;
125761
+ exports.paramMetaOf = paramMetaOf;
124715
125762
  exports.parseAcceleratorFactsJson = parseAcceleratorFactsJson;
124716
125763
  exports.parseAdminBase = parseAdminBase;
124717
125764
  exports.parseAlertEpisodePayload = parseAlertEpisodePayload;
@@ -124833,12 +125880,16 @@ exports.recordingOnboardCapability = recordingOnboardCapability;
124833
125880
  exports.recordingSignalCapability = recordingSignalCapability;
124834
125881
  exports.recordsCapability = recordsCapability;
124835
125882
  exports.recordsErrorFromMessage = require_sleep.recordsErrorFromMessage;
125883
+ exports.rectContains = rectContains;
125884
+ exports.rectsOverlap = rectsOverlap;
124836
125885
  exports.rectsToCells = rectsToCells;
124837
125886
  exports.reducePoints = reducePoints;
124838
125887
  exports.reducerForActive = reducerForActive;
124839
125888
  exports.registerKnownSecret = require_sleep.registerKnownSecret;
124840
125889
  exports.remapCropDetections = remapCropDetections;
124841
125890
  exports.remoteCapability = remoteCapability;
125891
+ exports.remoteLayoutComponentsCapability = remoteLayoutComponentsCapability;
125892
+ exports.removeGroup = removeGroup;
124842
125893
  exports.removeScalarTarget = removeScalarTarget;
124843
125894
  exports.renameAlarmProfile = renameAlarmProfile;
124844
125895
  exports.renderVocabPhrase = renderVocabPhrase;
@@ -125094,9 +126145,11 @@ exports.transformSecretWrite = require_sleep.transformSecretWrite;
125094
126145
  exports.tryConvertDelta = tryConvertDelta;
125095
126146
  exports.tryConvertUnit = tryConvertUnit;
125096
126147
  exports.turnProviderCapability = turnProviderCapability;
126148
+ exports.ungroup = ungroup;
125097
126149
  exports.unitCompanionField = unitCompanionField;
125098
126150
  exports.unitDimension = unitDimension;
125099
126151
  exports.unitsForDimension = unitsForDimension;
126152
+ exports.unitsForQuantity = unitsForQuantity;
125100
126153
  exports.unreachableExtensionSlotRow = unreachableExtensionSlotRow;
125101
126154
  exports.unwrapSchema = unwrapSchema;
125102
126155
  exports.updateCapability = updateCapability;