@camstack/addon-model-studio 1.1.322 → 1.1.324

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 (19) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{MotionZonesSettings-DIoKHy61.mjs → MotionZonesSettings-BFSzpDlc.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-SkRUYyni.mjs → PrivacyMaskSettings-bzbS1q77.mjs} +4 -4
  4. package/dist/{SceneMonitorEditor-CB_GHDb6.mjs → SceneMonitorEditor-5jtlWR_n.mjs} +3 -3
  5. package/dist/_stub.js +1298 -1264
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-onE-V79z.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_model_studio_page-DMLYtwXQ.mjs} +3 -3
  7. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-D8h8qfbd.mjs +26 -0
  8. package/dist/{hostInit-DvZygcN-.mjs → hostInit-BQhIepOe.mjs} +2 -2
  9. package/dist/model-studio.addon.js +803 -64
  10. package/dist/model-studio.addon.mjs +803 -64
  11. package/dist/{player-overlays-_zERIZfO.mjs → player-overlays-DwnklNvS.mjs} +1 -1
  12. package/dist/remoteEntry.js +1 -1
  13. package/dist/{responsive-CFFPG77U.mjs → responsive-DFhiWOST.mjs} +1 -1
  14. package/dist/{scene-monitor-copy-DEJ23ZZg.mjs → scene-monitor-copy-DVaiZyk_.mjs} +1 -1
  15. package/dist/{square-Prn9rHwz.mjs → square-CL1GZCDy.mjs} +1 -1
  16. package/dist/{trash-2-Ddf3KFsK.mjs → trash-2-Bs4Zqar1.mjs} +1 -1
  17. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-DDvX_rph.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_model_studio_page__remoteEntry_js-B3z4_zyV.mjs} +1 -1
  18. package/package.json +1 -1
  19. package/dist/_virtual_mf___mfe_internal__addon_model_studio_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Dt_IlI_h.mjs +0 -26
@@ -5824,7 +5824,7 @@ var ZodIssueCode = {
5824
5824
  var ZodFirstPartyTypeKind;
5825
5825
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5826
5826
  //#endregion
5827
- //#region ../types/dist/sleep-6eG9C8Xl.mjs
5827
+ //#region ../types/dist/sleep-63gM_1JR.mjs
5828
5828
  /**
5829
5829
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5830
5830
  * window to float samples (D455).
@@ -9185,6 +9185,7 @@ var CAP_NAMES = Object.freeze([
9185
9185
  "recording-signal",
9186
9186
  "records",
9187
9187
  "remote",
9188
+ "remote-layout-components",
9188
9189
  "running-state",
9189
9190
  "scene-monitor",
9190
9191
  "script-runner",
@@ -16042,6 +16043,478 @@ object({
16042
16043
  path: ["expr"]
16043
16044
  });
16044
16045
  });
16046
+ /**
16047
+ * Identity envelope for a device's upstream-system metadata.
16048
+ *
16049
+ * Two jobs:
16050
+ *
16051
+ * 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
16052
+ * uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
16053
+ * WS-Discovery endpoint UUID, …). Provider code keeps a
16054
+ * `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
16055
+ * without round-tripping through `stableId` / `id` mappings.
16056
+ *
16057
+ * 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
16058
+ * one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
16059
+ * for vendors where MAC is the natural key). For systems where the
16060
+ * live dispatch key can be renamed (HA `entity_id` can be edited),
16061
+ * providers detect renames by matching `uniqueId` and patch `id`
16062
+ * in-place so dispatch keeps working.
16063
+ *
16064
+ * **Display unit and precision are NOT stored here.** They are a read-time
16065
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
16066
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
16067
+ * neither is repeated on each sample. The `device_class` tag is an
16068
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
16069
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
16070
+ *
16071
+ * The `raw` escape hatch carries provider-specific attributes that don't
16072
+ * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
16073
+ * details, …). Providers control what lands here.
16074
+ */
16075
+ var SourceInfoSchema = object({
16076
+ /** Live dispatch key — mutable when the source system allows rename. */
16077
+ id: string(),
16078
+ /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
16079
+ system: string(),
16080
+ /** Immutable upstream identifier when available (HA `unique_id`, … ).
16081
+ * Used to detect rename when `id` changes. */
16082
+ uniqueId: string().optional(),
16083
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
16084
+ quantity: ReadingQuantitySchema.optional(),
16085
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
16086
+ dateTimeShape: _enum([
16087
+ "date",
16088
+ "time",
16089
+ "datetime"
16090
+ ]).optional(),
16091
+ /** Provider-specific extras that don't fit the structured slots.
16092
+ * Whitelist what goes here per provider — do NOT dump entire HA
16093
+ * attribute blobs (would explode the meta JSON column). */
16094
+ raw: record(string(), unknown()).optional()
16095
+ });
16096
+ /**
16097
+ * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
16098
+ * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
16099
+ * `factor = 5/9`, `offset = -160/9` (kept as exact expressions, not rounded
16100
+ * decimals). K derives from `°C = K − 273.15`.
16101
+ */
16102
+ var UNIT_TABLE = {
16103
+ "°C": {
16104
+ dimension: "temperature",
16105
+ factor: 1,
16106
+ offset: 0
16107
+ },
16108
+ "°F": {
16109
+ dimension: "temperature",
16110
+ factor: 5 / 9,
16111
+ offset: -160 / 9
16112
+ },
16113
+ K: {
16114
+ dimension: "temperature",
16115
+ factor: 1,
16116
+ offset: -273.15
16117
+ },
16118
+ hPa: {
16119
+ dimension: "pressure",
16120
+ factor: 1,
16121
+ offset: 0
16122
+ },
16123
+ kPa: {
16124
+ dimension: "pressure",
16125
+ factor: 10,
16126
+ offset: 0
16127
+ },
16128
+ Pa: {
16129
+ dimension: "pressure",
16130
+ factor: .01,
16131
+ offset: 0
16132
+ },
16133
+ mbar: {
16134
+ dimension: "pressure",
16135
+ factor: 1,
16136
+ offset: 0
16137
+ },
16138
+ bar: {
16139
+ dimension: "pressure",
16140
+ factor: 1e3,
16141
+ offset: 0
16142
+ },
16143
+ inHg: {
16144
+ dimension: "pressure",
16145
+ factor: 33.8639,
16146
+ offset: 0
16147
+ },
16148
+ mmHg: {
16149
+ dimension: "pressure",
16150
+ factor: 1.33322,
16151
+ offset: 0
16152
+ },
16153
+ psi: {
16154
+ dimension: "pressure",
16155
+ factor: 68.9476,
16156
+ offset: 0
16157
+ },
16158
+ "m/s": {
16159
+ dimension: "speed",
16160
+ factor: 1,
16161
+ offset: 0
16162
+ },
16163
+ "km/h": {
16164
+ dimension: "speed",
16165
+ factor: 1 / 3.6,
16166
+ offset: 0
16167
+ },
16168
+ mph: {
16169
+ dimension: "speed",
16170
+ factor: .44704,
16171
+ offset: 0
16172
+ },
16173
+ kn: {
16174
+ dimension: "speed",
16175
+ factor: .514444,
16176
+ offset: 0
16177
+ },
16178
+ "mm/h": {
16179
+ dimension: "precipitation-rate",
16180
+ factor: 1,
16181
+ offset: 0
16182
+ },
16183
+ "in/h": {
16184
+ dimension: "precipitation-rate",
16185
+ factor: 25.4,
16186
+ offset: 0
16187
+ },
16188
+ m: {
16189
+ dimension: "length",
16190
+ factor: 1,
16191
+ offset: 0
16192
+ },
16193
+ mm: {
16194
+ dimension: "length",
16195
+ factor: .001,
16196
+ offset: 0
16197
+ },
16198
+ cm: {
16199
+ dimension: "length",
16200
+ factor: .01,
16201
+ offset: 0
16202
+ },
16203
+ km: {
16204
+ dimension: "length",
16205
+ factor: 1e3,
16206
+ offset: 0
16207
+ },
16208
+ in: {
16209
+ dimension: "length",
16210
+ factor: .0254,
16211
+ offset: 0
16212
+ },
16213
+ ft: {
16214
+ dimension: "length",
16215
+ factor: .3048,
16216
+ offset: 0
16217
+ },
16218
+ mi: {
16219
+ dimension: "length",
16220
+ factor: 1609.344,
16221
+ offset: 0
16222
+ },
16223
+ "m²": {
16224
+ dimension: "area",
16225
+ factor: 1,
16226
+ offset: 0
16227
+ },
16228
+ lx: {
16229
+ dimension: "illuminance",
16230
+ factor: 1,
16231
+ offset: 0
16232
+ },
16233
+ "W/m²": {
16234
+ dimension: "irradiance",
16235
+ factor: 1,
16236
+ offset: 0
16237
+ },
16238
+ W: {
16239
+ dimension: "power",
16240
+ factor: 1,
16241
+ offset: 0
16242
+ },
16243
+ kW: {
16244
+ dimension: "power",
16245
+ factor: 1e3,
16246
+ offset: 0
16247
+ },
16248
+ VA: {
16249
+ dimension: "apparent-power",
16250
+ factor: 1,
16251
+ offset: 0
16252
+ },
16253
+ Wh: {
16254
+ dimension: "energy",
16255
+ factor: 1,
16256
+ offset: 0
16257
+ },
16258
+ kWh: {
16259
+ dimension: "energy",
16260
+ factor: 1e3,
16261
+ offset: 0
16262
+ },
16263
+ MWh: {
16264
+ dimension: "energy",
16265
+ factor: 1e6,
16266
+ offset: 0
16267
+ },
16268
+ V: {
16269
+ dimension: "voltage",
16270
+ factor: 1,
16271
+ offset: 0
16272
+ },
16273
+ mV: {
16274
+ dimension: "voltage",
16275
+ factor: .001,
16276
+ offset: 0
16277
+ },
16278
+ A: {
16279
+ dimension: "current",
16280
+ factor: 1,
16281
+ offset: 0
16282
+ },
16283
+ mA: {
16284
+ dimension: "current",
16285
+ factor: .001,
16286
+ offset: 0
16287
+ },
16288
+ ppm: {
16289
+ dimension: "concentration-volume",
16290
+ factor: 1,
16291
+ offset: 0
16292
+ },
16293
+ ppb: {
16294
+ dimension: "concentration-volume",
16295
+ factor: .001,
16296
+ offset: 0
16297
+ },
16298
+ "µg/m³": {
16299
+ dimension: "concentration-mass",
16300
+ factor: 1,
16301
+ offset: 0
16302
+ },
16303
+ "mg/m³": {
16304
+ dimension: "concentration-mass",
16305
+ factor: 1e3,
16306
+ offset: 0
16307
+ },
16308
+ kg: {
16309
+ dimension: "mass",
16310
+ factor: 1,
16311
+ offset: 0
16312
+ },
16313
+ g: {
16314
+ dimension: "mass",
16315
+ factor: .001,
16316
+ offset: 0
16317
+ },
16318
+ lb: {
16319
+ dimension: "mass",
16320
+ factor: .453592,
16321
+ offset: 0
16322
+ },
16323
+ oz: {
16324
+ dimension: "mass",
16325
+ factor: .0283495,
16326
+ offset: 0
16327
+ },
16328
+ "%": {
16329
+ dimension: "percentage",
16330
+ factor: 1,
16331
+ offset: 0
16332
+ },
16333
+ L: {
16334
+ dimension: "volume",
16335
+ factor: 1,
16336
+ offset: 0
16337
+ },
16338
+ mL: {
16339
+ dimension: "volume",
16340
+ factor: .001,
16341
+ offset: 0
16342
+ },
16343
+ gal: {
16344
+ dimension: "volume",
16345
+ factor: 3.785411784,
16346
+ offset: 0
16347
+ },
16348
+ s: {
16349
+ dimension: "duration",
16350
+ factor: 1,
16351
+ offset: 0
16352
+ },
16353
+ ms: {
16354
+ dimension: "duration",
16355
+ factor: .001,
16356
+ offset: 0
16357
+ },
16358
+ min: {
16359
+ dimension: "duration",
16360
+ factor: 60,
16361
+ offset: 0
16362
+ },
16363
+ h: {
16364
+ dimension: "duration",
16365
+ factor: 3600,
16366
+ offset: 0
16367
+ },
16368
+ d: {
16369
+ dimension: "duration",
16370
+ factor: 86400,
16371
+ offset: 0
16372
+ },
16373
+ B: {
16374
+ dimension: "information",
16375
+ factor: 1,
16376
+ offset: 0
16377
+ },
16378
+ KiB: {
16379
+ dimension: "information",
16380
+ factor: 1024,
16381
+ offset: 0
16382
+ },
16383
+ MiB: {
16384
+ dimension: "information",
16385
+ factor: 1024 ** 2,
16386
+ offset: 0
16387
+ },
16388
+ GiB: {
16389
+ dimension: "information",
16390
+ factor: 1024 ** 3,
16391
+ offset: 0
16392
+ },
16393
+ TiB: {
16394
+ dimension: "information",
16395
+ factor: 1024 ** 4,
16396
+ offset: 0
16397
+ },
16398
+ kB: {
16399
+ dimension: "information",
16400
+ factor: 1e3,
16401
+ offset: 0
16402
+ },
16403
+ MB: {
16404
+ dimension: "information",
16405
+ factor: 1e6,
16406
+ offset: 0
16407
+ },
16408
+ GB: {
16409
+ dimension: "information",
16410
+ factor: 1e9,
16411
+ offset: 0
16412
+ },
16413
+ "°": {
16414
+ dimension: "angle",
16415
+ factor: 1,
16416
+ offset: 0
16417
+ },
16418
+ dBFS: {
16419
+ dimension: "sound-level",
16420
+ factor: 1,
16421
+ offset: 0
16422
+ },
16423
+ dBm: {
16424
+ dimension: "signal-level",
16425
+ factor: 1,
16426
+ offset: 0
16427
+ },
16428
+ mired: {
16429
+ dimension: "colour-temperature",
16430
+ factor: 1,
16431
+ offset: 0
16432
+ },
16433
+ kbps: {
16434
+ dimension: "bitrate",
16435
+ factor: 1,
16436
+ offset: 0
16437
+ },
16438
+ Mbps: {
16439
+ dimension: "bitrate",
16440
+ factor: 1e3,
16441
+ offset: 0
16442
+ },
16443
+ fps: {
16444
+ dimension: "rate",
16445
+ factor: 1,
16446
+ offset: 0
16447
+ }
16448
+ };
16449
+ /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
16450
+ function specFor(unit) {
16451
+ return Object.hasOwn(UNIT_TABLE, unit) ? UNIT_TABLE[unit] : void 0;
16452
+ }
16453
+ /** Value in `spec`'s unit → its dimension's canonical unit. */
16454
+ function toCanonical(value, spec) {
16455
+ return value * spec.factor + spec.offset;
16456
+ }
16457
+ /** Canonical-unit value → the unit described by `spec`. */
16458
+ function fromCanonical(canonical, spec) {
16459
+ return (canonical - spec.offset) / spec.factor;
16460
+ }
16461
+ /**
16462
+ * Graceful variant for the render path: returns the converted value, or
16463
+ * `undefined` when the conversion is refused (unknown unit / cross-dimension).
16464
+ * Never throws — a render caller falls back to the raw value + source unit.
16465
+ */
16466
+ function tryConvertUnit(value, from, to) {
16467
+ if (from === to) return value;
16468
+ const fromSpec = specFor(from);
16469
+ if (!fromSpec) return void 0;
16470
+ const toSpec = specFor(to);
16471
+ if (!toSpec) return void 0;
16472
+ if (fromSpec.dimension !== toSpec.dimension) return void 0;
16473
+ return fromCanonical(toCanonical(value, fromSpec), toSpec);
16474
+ }
16475
+ /**
16476
+ * A number a person types for a physical quantity — the input side of the
16477
+ * value + unit contract (D972).
16478
+ *
16479
+ * Readings are stored in the base unit of their quantity (D944); a cap method
16480
+ * param is stored in the unit its input schema DECLARES (`paramMeta`). The
16481
+ * wire pair {@link QuantityValue} says "this number is expressed in this
16482
+ * unit"; {@link convertQuantityInput} brings it to the stored unit and refuses,
16483
+ * with a typed {@link QuantityUnitError}, a unit that cannot be that quantity.
16484
+ */
16485
+ /** A unit that cannot be applied to the number it was given with. */
16486
+ var QuantityUnitError = class extends Error {
16487
+ code;
16488
+ constructor(code, message) {
16489
+ super(`[${code}] ${message}`);
16490
+ this.name = "QuantityUnitError";
16491
+ this.code = code;
16492
+ }
16493
+ };
16494
+ /** The wire pair: `value` is expressed in `unit`. */
16495
+ var QuantityValueSchema = object({
16496
+ value: number(),
16497
+ unit: string().min(1)
16498
+ });
16499
+ /**
16500
+ * Zod `.meta()` bag for a cap METHOD param that is a physical quantity — the
16501
+ * input-side twin of `readingMeta`. The value is carried in `unit` (a gram
16502
+ * portion stays in grams); the unit must belong to the quantity, checked once
16503
+ * when the cap is declared.
16504
+ */
16505
+ function paramMeta(declared) {
16506
+ const base = baseUnitOf(declared.quantity);
16507
+ if (base === void 0) throw new QuantityUnitError("unknown-unit", `quantity "${declared.quantity}" has no unit`);
16508
+ if (tryConvertUnit(1, declared.unit, base) === void 0) throw new QuantityUnitError("cross-dimension", `"${declared.unit}" is not a unit of ${declared.quantity}`);
16509
+ return {
16510
+ quantity: declared.quantity,
16511
+ unit: declared.unit
16512
+ };
16513
+ }
16514
+ object({
16515
+ quantity: ReadingQuantitySchema,
16516
+ unit: string().min(1)
16517
+ });
16045
16518
  /** How a leaf compares a device field to a value. Derived from the field's
16046
16519
  * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16047
16520
  var AutomationConditionOperatorSchema = _enum([
@@ -16064,8 +16537,10 @@ var AutomationConditionLeafSchema = object({
16064
16537
  string(),
16065
16538
  number(),
16066
16539
  boolean(),
16067
- array(union([string(), number()]))
16068
- ])
16540
+ array(union([string(), number()])),
16541
+ QuantityValueSchema
16542
+ ]),
16543
+ unit: string().min(1).optional()
16069
16544
  });
16070
16545
  var AutomationConditionStationaryPresentSchema = object({
16071
16546
  kind: literal("stationary-present"),
@@ -16218,7 +16693,13 @@ var AutomationCapStepSchema = object({
16218
16693
  /** Values may carry `{{vars.x}}` slots, or be exactly `{ $var: 'name' }`, which
16219
16694
  * SUBSTITUTE and do not evaluate (§3.2.3). Anything beyond substitution is
16220
16695
  * the expression leaf. */
16221
- args: record(string(), unknown()).optional()
16696
+ args: record(string(), unknown()).optional(),
16697
+ /**
16698
+ * The unit the person picked per arg (display only). `args` always hold the
16699
+ * value in the unit the method's input DECLARES (`paramMeta`); on a write an
16700
+ * arg may be a {@link QuantityValue}, which the save boundary converts.
16701
+ */
16702
+ argUnits: record(string(), string().min(1)).optional()
16222
16703
  });
16223
16704
  /**
16224
16705
  * The steps an internal Action may contain: cap and wait, and nothing that can
@@ -16598,6 +17079,16 @@ object({
16598
17079
  lastRunAt: number().nullable(),
16599
17080
  lastError: string().nullable()
16600
17081
  });
17082
+ /** A bare number accepted as a base unit — the save succeeded, say so. */
17083
+ var UnitWarningSchema = object({
17084
+ /** Where in the saved record, e.g. `conditions.children.0` or `steps.2.args.grams`. */
17085
+ path: string(),
17086
+ deviceId: number().int().optional(),
17087
+ field: string(),
17088
+ value: number(),
17089
+ assumedUnit: string(),
17090
+ message: string()
17091
+ });
16601
17092
  /**
16602
17093
  * notification-output — canonical, capability-gated notification delivery.
16603
17094
  *
@@ -21970,56 +22461,6 @@ var DeviceAdminLinkSchema = object({
21970
22461
  });
21971
22462
  method(object({ deviceId: number().int().nonnegative() }), DeviceAdminLinkSchema.nullable(), { auth: "admin" });
21972
22463
  /**
21973
- * Identity envelope for a device's upstream-system metadata.
21974
- *
21975
- * Two jobs:
21976
- *
21977
- * 1. **Dispatch key.** `id` is the natural identifier the SOURCE system
21978
- * uses to address this device (HA `entity_id`, Reolink MAC, ONVIF
21979
- * WS-Discovery endpoint UUID, …). Provider code keeps a
21980
- * `Map<sourceInfo.id, IDevice>` to route inbound push events in O(1)
21981
- * without round-tripping through `stableId` / `id` mappings.
21982
- *
21983
- * 2. **Stability bridge.** `uniqueId` is the *immutable* upstream id when
21984
- * one exists (HA `entity.unique_id`, Reolink MAC again — same as `id`
21985
- * for vendors where MAC is the natural key). For systems where the
21986
- * live dispatch key can be renamed (HA `entity_id` can be edited),
21987
- * providers detect renames by matching `uniqueId` and patch `id`
21988
- * in-place so dispatch keeps working.
21989
- *
21990
- * **Display unit and precision are NOT stored here.** They are a read-time
21991
- * preference (D944). A polymorphic reading stores its `quantity` here once,
21992
- * at adopt, and a date/time value stores `dateTimeShape` the same way —
21993
- * neither is repeated on each sample. The `device_class` tag is an
21994
- * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21995
- * to derive the CamStack `DeviceRole`; it is never stored after that point.
21996
- *
21997
- * The `raw` escape hatch carries provider-specific attributes that don't
21998
- * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
21999
- * details, …). Providers control what lands here.
22000
- */
22001
- var SourceInfoSchema = object({
22002
- /** Live dispatch key — mutable when the source system allows rename. */
22003
- id: string(),
22004
- /** Source system tag — e.g. 'homeassistant' | 'reolink' | 'onvif'. */
22005
- system: string(),
22006
- /** Immutable upstream identifier when available (HA `unique_id`, … ).
22007
- * Used to detect rename when `id` changes. */
22008
- uniqueId: string().optional(),
22009
- /** Quantity of a polymorphic reading, written once at adopt (D944). */
22010
- quantity: ReadingQuantitySchema.optional(),
22011
- /** Date / time / datetime shape of a string reading, written once at adopt. */
22012
- dateTimeShape: _enum([
22013
- "date",
22014
- "time",
22015
- "datetime"
22016
- ]).optional(),
22017
- /** Provider-specific extras that don't fit the structured slots.
22018
- * Whitelist what goes here per provider — do NOT dump entire HA
22019
- * attribute blobs (would explode the meta JSON column). */
22020
- raw: record(string(), unknown()).optional()
22021
- });
22022
- /**
22023
22464
  * device-discovery — device-scoped capability for parents that host /
22024
22465
  * enumerate child devices (Reolink Hub / NVR, ONVIF gateway, future
22025
22466
  * integrations). The parent advertises a list of discoverable children,
@@ -58032,13 +58473,19 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
58032
58473
  }), method(ActionIdInput, _void(), {
58033
58474
  kind: "mutation",
58034
58475
  auth: "admin"
58035
- }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({ action: ActionEntrySchema }), {
58476
+ }), method(ActionIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" }), method(object({ action: ActionInputSchema }), object({
58477
+ action: ActionEntrySchema,
58478
+ warnings: array(UnitWarningSchema).optional()
58479
+ }), {
58036
58480
  kind: "mutation",
58037
58481
  auth: "admin"
58038
58482
  }), method(object({
58039
58483
  actionId: string().min(1),
58040
58484
  action: ActionInputSchema.partial()
58041
- }), object({ action: ActionEntrySchema }), {
58485
+ }), object({
58486
+ action: ActionEntrySchema,
58487
+ warnings: array(UnitWarningSchema).optional()
58488
+ }), {
58042
58489
  kind: "mutation",
58043
58490
  auth: "admin"
58044
58491
  }), method(ActionIdInput, _void(), {
@@ -58344,6 +58791,30 @@ object({
58344
58791
  at: number()
58345
58792
  }).nullable()
58346
58793
  });
58794
+ var RemoteButtonShapeSchema = _enum([
58795
+ "rect",
58796
+ "rounded",
58797
+ "round",
58798
+ "pill"
58799
+ ]);
58800
+ var RemoteButtonToneSchema = _enum([
58801
+ "neutral",
58802
+ "accent",
58803
+ "danger",
58804
+ "red",
58805
+ "green",
58806
+ "yellow",
58807
+ "blue"
58808
+ ]);
58809
+ var RemoteGroupFrameSchema = _enum([
58810
+ "none",
58811
+ "panel",
58812
+ "divider",
58813
+ "dpad",
58814
+ "rocker-v",
58815
+ "rocker-h",
58816
+ "keypad"
58817
+ ]);
58347
58818
  var RemoteLayoutButtonSchema = object({
58348
58819
  id: string().min(1).max(40),
58349
58820
  /** 0-based grid position. */
@@ -58353,12 +58824,94 @@ var RemoteLayoutButtonSchema = object({
58353
58824
  colSpan: number().int().min(1).max(24),
58354
58825
  icon: string().min(1).max(60).optional(),
58355
58826
  label: string().min(1).max(40).optional(),
58356
- role: RemoteRoleSchema
58827
+ role: RemoteRoleSchema,
58828
+ /** Absent = the plain rendering every layout had before shapes existed. */
58829
+ shape: RemoteButtonShapeSchema.optional(),
58830
+ tone: RemoteButtonToneSchema.optional(),
58831
+ /** The group this button belongs to; it must lie inside that group's rectangle. */
58832
+ groupId: string().min(1).max(40).optional()
58833
+ });
58834
+ /**
58835
+ * A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
58836
+ * titled panel, a section divider, a D-pad ring. `componentId` names the palette
58837
+ * entry it came from (informational: the layout is self-contained).
58838
+ */
58839
+ var RemoteLayoutGroupSchema = object({
58840
+ id: string().min(1).max(40),
58841
+ componentId: string().min(1).max(80),
58842
+ frame: RemoteGroupFrameSchema,
58843
+ row: number().int().min(0).max(24),
58844
+ col: number().int().min(0).max(24),
58845
+ rowSpan: number().int().min(1).max(24),
58846
+ colSpan: number().int().min(1).max(24),
58847
+ title: string().min(1).max(40).optional()
58357
58848
  });
58849
+ function rectsOverlap(a, b) {
58850
+ 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;
58851
+ }
58852
+ function rectContains(outer, inner) {
58853
+ 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;
58854
+ }
58855
+ /**
58856
+ * The group rules of a layout: inside the grid, no two groups overlap, a
58857
+ * member lies inside its group, a divider holds nothing, and a button that is
58858
+ * not a member never sits inside someone else's rectangle (a group moves as
58859
+ * one thing, so a stray key under it would be left behind).
58860
+ */
58861
+ function refineGroups(grid, ctx) {
58862
+ const groups = grid.groups ?? [];
58863
+ const byId = /* @__PURE__ */ new Map();
58864
+ const fail = (message, path) => {
58865
+ ctx.addIssue({
58866
+ code: "custom",
58867
+ message,
58868
+ path
58869
+ });
58870
+ };
58871
+ groups.forEach((group, index) => {
58872
+ if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
58873
+ "groups",
58874
+ index,
58875
+ "id"
58876
+ ]);
58877
+ byId.set(group.id, group);
58878
+ if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
58879
+ for (let other = 0; other < index; other += 1) {
58880
+ const earlier = groups[other];
58881
+ if (earlier !== void 0 && rectsOverlap(earlier, group)) {
58882
+ fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
58883
+ return;
58884
+ }
58885
+ }
58886
+ });
58887
+ grid.buttons.forEach((button, index) => {
58888
+ if (button.groupId !== void 0) {
58889
+ const group = byId.get(button.groupId);
58890
+ if (group === void 0) {
58891
+ fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
58892
+ "buttons",
58893
+ index,
58894
+ "groupId"
58895
+ ]);
58896
+ return;
58897
+ }
58898
+ if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
58899
+ "buttons",
58900
+ index,
58901
+ "groupId"
58902
+ ]);
58903
+ if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
58904
+ return;
58905
+ }
58906
+ const foreign = groups.find((group) => rectsOverlap(group, button));
58907
+ if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
58908
+ });
58909
+ }
58358
58910
  object({
58359
58911
  columns: number().int().min(1).max(24),
58360
58912
  rows: number().int().min(1).max(24),
58361
- buttons: array(RemoteLayoutButtonSchema).max(200)
58913
+ buttons: array(RemoteLayoutButtonSchema).max(200),
58914
+ groups: array(RemoteLayoutGroupSchema).max(50).optional()
58362
58915
  }).superRefine((grid, ctx) => {
58363
58916
  const ids = /* @__PURE__ */ new Set();
58364
58917
  const taken = /* @__PURE__ */ new Map();
@@ -58395,6 +58948,7 @@ object({
58395
58948
  taken.set(cell, button.id);
58396
58949
  }
58397
58950
  });
58951
+ refineGroups(grid, ctx);
58398
58952
  });
58399
58953
  var RemoteBoundCommandSchema = object({
58400
58954
  role: RemoteRoleSchema,
@@ -58526,6 +59080,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
58526
59080
  kind: "mutation",
58527
59081
  auth: "admin"
58528
59082
  });
59083
+ var RemoteComponentCategorySchema = _enum([
59084
+ "power",
59085
+ "navigation",
59086
+ "volume",
59087
+ "numbers",
59088
+ "media",
59089
+ "colour",
59090
+ "apps",
59091
+ "layout"
59092
+ ]);
59093
+ /** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
59094
+ var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
59095
+ var RemoteComponentButtonSchema = object({
59096
+ /** Joined to the instance id when the component is inserted. */
59097
+ idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
59098
+ row: number().int().min(0).max(24),
59099
+ col: number().int().min(0).max(24),
59100
+ rowSpan: number().int().min(1).max(24),
59101
+ colSpan: number().int().min(1).max(24),
59102
+ role: RemoteRoleSchema,
59103
+ icon: string().min(1).max(60).optional(),
59104
+ label: string().min(1).max(40).optional(),
59105
+ shape: RemoteButtonShapeSchema.optional(),
59106
+ tone: RemoteButtonToneSchema.optional()
59107
+ });
59108
+ var RemoteLayoutComponentSchema = object({
59109
+ id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
59110
+ name: string().min(1).max(60),
59111
+ nameI18n: object({
59112
+ en: string().min(1).max(60),
59113
+ it: string().min(1).max(60)
59114
+ }).optional(),
59115
+ /** A display icon key, resolved by the UI like a button icon. */
59116
+ icon: string().min(1).max(60),
59117
+ category: RemoteComponentCategorySchema,
59118
+ size: object({
59119
+ rows: number().int().min(1).max(24),
59120
+ cols: number().int().min(1).max(24)
59121
+ }),
59122
+ /** The frame drawn around the whole component, or null for loose buttons. */
59123
+ group: object({
59124
+ frame: RemoteGroupFrameSchema,
59125
+ title: string().min(1).max(40).optional()
59126
+ }).nullable(),
59127
+ buttons: array(RemoteComponentButtonSchema).max(40)
59128
+ }).superRefine((component, ctx) => {
59129
+ const suffixes = /* @__PURE__ */ new Set();
59130
+ component.buttons.forEach((button, index) => {
59131
+ if (suffixes.has(button.idSuffix)) ctx.addIssue({
59132
+ code: "custom",
59133
+ message: `button "${button.idSuffix}" is listed twice`,
59134
+ path: [
59135
+ "buttons",
59136
+ index,
59137
+ "idSuffix"
59138
+ ]
59139
+ });
59140
+ suffixes.add(button.idSuffix);
59141
+ if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
59142
+ ctx.addIssue({
59143
+ code: "custom",
59144
+ message: `button "${button.idSuffix}" leaves the component`,
59145
+ path: ["buttons", index]
59146
+ });
59147
+ return;
59148
+ }
59149
+ for (let other = 0; other < index; other += 1) {
59150
+ const earlier = component.buttons[other];
59151
+ if (earlier !== void 0 && rectsOverlap(earlier, button)) {
59152
+ ctx.addIssue({
59153
+ code: "custom",
59154
+ message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
59155
+ path: ["buttons", index]
59156
+ });
59157
+ return;
59158
+ }
59159
+ }
59160
+ });
59161
+ if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
59162
+ code: "custom",
59163
+ message: "a divider component holds no buttons",
59164
+ path: ["buttons"]
59165
+ });
59166
+ });
59167
+ method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
58529
59168
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
58530
59169
  kind: "mutation",
58531
59170
  auth: "protected"
@@ -62685,6 +63324,14 @@ var PetFeederDeviceStatusSchema = _enum([
62685
63324
  "on_batteries"
62686
63325
  ]);
62687
63326
  var gramsPortion = number().int().min(4).max(200);
63327
+ /** A portion REQUESTED of the feeder is a mass the method takes in GRAMS (the
63328
+ * hardware's own unit). The bounds are grams; a caller entering another unit
63329
+ * converts to grams first (`paramMeta`, D972). The STATUS fields above stay
63330
+ * plain grams — they are not readings in the kilogram base. */
63331
+ var gramsParam = gramsPortion.meta(paramMeta({
63332
+ quantity: "mass",
63333
+ unit: "g"
63334
+ }));
62688
63335
  object({
62689
63336
  /** Food in the TANK (hopper), as the feeder's own sensor reports it
62690
63337
  * (`state.food`). A coarse level code, not grams — `0` is empty. The
@@ -62759,9 +63406,9 @@ object({
62759
63406
  });
62760
63407
  DeviceType.PetFeeder, method(object({
62761
63408
  deviceId: number().int().nonnegative(),
62762
- grams: gramsPortion.optional(),
62763
- hopper1: gramsPortion.optional(),
62764
- hopper2: gramsPortion.optional()
63409
+ grams: gramsParam.optional(),
63410
+ hopper1: gramsParam.optional(),
63411
+ hopper2: gramsParam.optional()
62765
63412
  }), _void(), {
62766
63413
  kind: "mutation",
62767
63414
  auth: "protected"
@@ -67453,6 +68100,87 @@ method(object({}), array(TrainingBaseModelSchema).readonly(), { auth: "admin" })
67453
68100
  kind: "mutation",
67454
68101
  auth: "admin"
67455
68102
  });
68103
+ var btn = (idSuffix, row, col, init) => ({
68104
+ idSuffix,
68105
+ row,
68106
+ col,
68107
+ rowSpan: 1,
68108
+ colSpan: 1,
68109
+ ...init
68110
+ });
68111
+ function rocker(id, name, it, frame, title, buttons, size) {
68112
+ return {
68113
+ id,
68114
+ name,
68115
+ nameI18n: {
68116
+ en: name,
68117
+ it
68118
+ },
68119
+ icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
68120
+ category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
68121
+ size,
68122
+ group: title === null ? { frame } : {
68123
+ frame,
68124
+ title
68125
+ },
68126
+ buttons
68127
+ };
68128
+ }
68129
+ btn("power", 0, 0, {
68130
+ role: "power",
68131
+ shape: "round",
68132
+ tone: "danger"
68133
+ }), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
68134
+ role: "ok",
68135
+ shape: "round",
68136
+ tone: "accent"
68137
+ }), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
68138
+ role: "volume_up",
68139
+ shape: "pill"
68140
+ }), btn("down", 1, 0, {
68141
+ role: "volume_down",
68142
+ shape: "pill"
68143
+ })], {
68144
+ rows: 2,
68145
+ cols: 1
68146
+ }), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
68147
+ role: "channel_up",
68148
+ shape: "pill"
68149
+ }), btn("down", 1, 0, {
68150
+ role: "channel_down",
68151
+ shape: "pill"
68152
+ })], {
68153
+ rows: 2,
68154
+ cols: 1
68155
+ }), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
68156
+ role: "custom:minus",
68157
+ label: "−",
68158
+ shape: "pill"
68159
+ }), btn("plus", 0, 1, {
68160
+ role: "custom:plus",
68161
+ label: "+",
68162
+ shape: "pill"
68163
+ })], {
68164
+ rows: 1,
68165
+ cols: 2
68166
+ }), [...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" })], btn("red", 0, 0, {
68167
+ role: "custom:color_red",
68168
+ tone: "red"
68169
+ }), btn("green", 0, 1, {
68170
+ role: "custom:color_green",
68171
+ tone: "green"
68172
+ }), btn("yellow", 0, 2, {
68173
+ role: "custom:color_yellow",
68174
+ tone: "yellow"
68175
+ }), btn("blue", 0, 3, {
68176
+ role: "custom:color_blue",
68177
+ tone: "blue"
68178
+ }), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
68179
+ 1,
68180
+ 2,
68181
+ 3,
68182
+ 4
68183
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
67456
68184
  discriminatedUnion("kind", [object({
67457
68185
  kind: literal("raw"),
67458
68186
  timings: array(number().int()),
@@ -75415,6 +76143,13 @@ Object.freeze({
75415
76143
  access: "create",
75416
76144
  role: "admin"
75417
76145
  },
76146
+ "remoteLayoutComponents.listComponents": {
76147
+ capName: "remote-layout-components",
76148
+ capScope: "system",
76149
+ addonId: null,
76150
+ access: "view",
76151
+ role: "admin"
76152
+ },
75418
76153
  "runningState.getStatus": {
75419
76154
  capName: "running-state",
75420
76155
  capScope: "device",
@@ -79458,7 +80193,8 @@ var APPLIANCE_CAP_VOCABULARY = {
79458
80193
  min: 4,
79459
80194
  max: 200,
79460
80195
  step: 1,
79461
- unit: "g"
80196
+ unit: "g",
80197
+ quantity: "mass"
79462
80198
  },
79463
80199
  optional: true
79464
80200
  }]
@@ -79477,7 +80213,8 @@ var APPLIANCE_CAP_VOCABULARY = {
79477
80213
  min: 4,
79478
80214
  max: 200,
79479
80215
  step: 1,
79480
- unit: "g"
80216
+ unit: "g",
80217
+ quantity: "mass"
79481
80218
  },
79482
80219
  optional: true
79483
80220
  }, {
@@ -79488,7 +80225,8 @@ var APPLIANCE_CAP_VOCABULARY = {
79488
80225
  min: 4,
79489
80226
  max: 200,
79490
80227
  step: 1,
79491
- unit: "g"
80228
+ unit: "g",
80229
+ quantity: "mass"
79492
80230
  },
79493
80231
  optional: true
79494
80232
  }]
@@ -81588,6 +82326,7 @@ var SYSTEM_CAP_VOCABULARY = {
81588
82326
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
81589
82327
  records: system(INTERNAL),
81590
82328
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
82329
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
81591
82330
  "server-management": system(CLUSTER),
81592
82331
  "settings-store": system(INTERNAL),
81593
82332
  "smtp-provider": system(INTERNAL),