@camstack/addon-agent-ui 1.2.347 → 1.2.349

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.2.349] - 2026-11-08
11
+
12
+ ### Changed
13
+
14
+ - Rebuilt against updated dependencies.
15
+
16
+ ## [1.2.348] - 2026-11-08
17
+
18
+ ### Changed
19
+
20
+ - Rebuilt against updated dependencies.
21
+
10
22
  ## [1.2.347] - 2026-11-08
11
23
 
12
24
  ### Changed
@@ -367,14 +367,14 @@
367
367
  "server/server/packages/addon-agent-ui/src/updates-panel.tsx": "ec6b5b3701322bbb68dbe581703409cd373418cf9227ce829ea0945e70e60d54",
368
368
  "server/server/packages/addon-agent-ui/src/use-pairing-status.ts": "46193a1e83a64250dfff75d417448d74ba0159465e710861b996e903b03e427a",
369
369
  "server/server/packages/sdk/dist/index.js": "89409bfb9632c46ed63ee4882c70b7994c1ff90c67d27a5d4b86b0990c41a131",
370
- "server/server/packages/types/dist/client.mjs": "8ec4d194aa22772c84d63a56743eec768ddcfc2dbb2736c05be673a4cba6a3a5",
371
- "server/server/packages/types/dist/composition-Bx0JvKUU.mjs": "7eaf5a76c9be8083ca45d388c56bde1bf572cef7a387c4bafe0de5138add21e6",
370
+ "server/server/packages/types/dist/client.mjs": "2b0d99cac9848d23d5c34ab56fff1eefda7ba00945c0fb6b86dd168405a21a91",
371
+ "server/server/packages/types/dist/composition-CXCX78IR.mjs": "3c34431544a6b88404f5f7c855c84ebbf2e5736ad300d4d4dfa2417759f30d46",
372
372
  "server/server/packages/types/dist/convert-BAzH15HU.mjs": "baa969d99051a3b05b2620e8b8e776a6f5539ac74ee9f9dbee1cc6b6a3a892a0",
373
- "server/server/packages/types/dist/device-type-DxCT0krO.mjs": "162d92905be0d367e47ad8a9fb605a2e63706bc4010c5bc9de0b2c5a2aae1377",
373
+ "server/server/packages/types/dist/device-type-CbDhVwFP.mjs": "94089ac4e225fbd1a8109ff7d5db680acff824c0745d60a1ccf52b4e2717451d",
374
374
  "server/server/packages/types/dist/event-category-78tcBemo.mjs": "767f1bb576027d9ee79ec036367001330203d84842c2c2111fa465fa434045e2",
375
- "server/server/packages/types/dist/index.mjs": "5c54c3021486b4362f1146a3f16c681339af3b0e6dd6467046c4fe9819398b0f",
375
+ "server/server/packages/types/dist/index.mjs": "b2b587eb81d6aa49b93f08908766fa76d6516b23c0110a5b6ba55f33956611d7",
376
376
  "server/server/packages/types/dist/node-color.mjs": "942e663074f0a4ed2f8643848ec139eba4b5fd16d0d01c8a23e08a4819a8843b",
377
- "server/server/packages/types/dist/sleep-t7BftoJG.mjs": "84bc33f597f36472d398b50f381fded4dd76f098104c9e6f6e97ff585568ca0c",
377
+ "server/server/packages/types/dist/sleep-CqvzKIq2.mjs": "1c57cc25803331fab405ae466ef5adfeda60e57c7dcda5fe9083d87ddf3105dc",
378
378
  "server/server/packages/ui-library/src/composites/adaptive-downlink.ts": "6e9698e97721461b72a3750f930dd7214d049be865491bfb5a64b87c032b0e22",
379
379
  "server/server/packages/ui-library/src/composites/camera-stream-player.tsx": "c04809229b4e091fed09e2bcfe643d6b36cb3a39e294229b49f8a095e4e6ebd3",
380
380
  "server/server/packages/ui-library/src/composites/cap-settings/CameraGridLayoutEditor.tsx": "fae93899c4903183e1b78db39e58bd52a3dd4dc2c11ce85d64e6d2a2bab7516d",
@@ -394,7 +394,7 @@
394
394
  "server/server/packages/ui-library/src/composites/data-table-pager.tsx": "3ffe4b0adfe333c36b65e8808fdb8c0d1c2162c3e0582b590e094c9fe1a6cc68",
395
395
  "server/server/packages/ui-library/src/composites/data-table-pagination.ts": "ed1192495b99ba34a2b621aea4b3016971cd203adff568b31e076b5e6c0e4882",
396
396
  "server/server/packages/ui-library/src/composites/data-table-selection.tsx": "a0ede042754de79674a224b7ac74b81e2f27f57ad2d3622811f7938fc736eaa7",
397
- "server/server/packages/ui-library/src/composites/data-table.tsx": "590fb29d4810ca52b372e98f221cbf03a1e592bd1301433c50dbb8fef9f3d627",
397
+ "server/server/packages/ui-library/src/composites/data-table.tsx": "f79e4de10939a561cce752c226c4a6a8fecec0ec8652186c9ec4c7b0a1ba6890",
398
398
  "server/server/packages/ui-library/src/composites/detection-colors.ts": "6a1da1e4ed0aafdc0e90377f911e8492a849f831cfc695f0db4a30c56c20c349",
399
399
  "server/server/packages/ui-library/src/composites/dev-shell.tsx": "c3c4102dca52080b6eab45141a2fabff11afb9fdf8919516dbf7f2b8d2c17a61",
400
400
  "server/server/packages/ui-library/src/composites/device-controls/ac-panel.tsx": "19649d3703843779006728547128f7fe4af73d9ba9e0fe4620a202895796c6e9",
@@ -486,7 +486,7 @@
486
486
  "server/server/packages/ui-library/src/contexts/vod-playback.tsx": "c07bc3ecc786182ef2f27d13bec3f520288e0b47367edd7ddf1c2e7b4a2bbaee",
487
487
  "server/server/packages/ui-library/src/contexts/widget-registry.tsx": "71a2815c645abe96821a9e9e5668e321187527d569414f5ca197d615856cc17a",
488
488
  "server/server/packages/ui-library/src/contexts/zone-editing.tsx": "275f680304ab09dd747fd9e859dc5aa0535df33eb2d33eb669a6de66bdd848d0",
489
- "server/server/packages/ui-library/src/generated/system-hooks.ts": "003f4825b6f0b1a04e7a6eba29437e70624190499174fe26e564c38a28b78ef4",
489
+ "server/server/packages/ui-library/src/generated/system-hooks.ts": "a2f919762a833d62521e99931f2e51f80a5efa6288ee50e1133f1169a6972918",
490
490
  "server/server/packages/ui-library/src/hooks/turn-server-cache.ts": "fab22f069c3761e0d268449e056d9c499b6ea63b879684a40023735b34bad061",
491
491
  "server/server/packages/ui-library/src/hooks/use-device-capability.ts": "b74ede60be8039c45f6c712a407ee838ab208610435870ddabaa8598cb7cd7f3",
492
492
  "server/server/packages/ui-library/src/hooks/use-device-detections.ts": "61cc54cf2e7f53864e5aa533c05679e4c204c4004a0296f63ab169b003888611",
package/dist/addon.js CHANGED
@@ -803,7 +803,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
803
803
  return EventCategory;
804
804
  }({});
805
805
  //#endregion
806
- //#region ../types/dist/device-type-DxCT0krO.mjs
806
+ //#region ../types/dist/device-type-CbDhVwFP.mjs
807
807
  /**
808
808
  import { errMsg } from '@camstack/types'
809
809
  * Extract a human-readable message from an unknown error value.
@@ -972,6 +972,13 @@ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
972
972
  * (`featuresOfBoundCaps`). Gates the quick-run control in the viewer.
973
973
  */
974
974
  DeviceFeature["TimedRun"] = "timed-run";
975
+ /**
976
+ * Device binds a plain `switch` cap. Derived from the bindings
977
+ * (`featuresOfBoundCaps`) for a composed device, whose switch is bound by the
978
+ * composition and not by a driver: an appliance (a heater behind a smart
979
+ * plug) has no control of its own, so a surface reads this as its on/off.
980
+ */
981
+ DeviceFeature["Switch"] = "switch";
975
982
  /** Light supports rgb-triplet color via `color` cap. */
976
983
  DeviceFeature["LightColorRgb"] = "light-color-rgb";
977
984
  /** Light supports HSV color via `color` cap. */
@@ -5873,7 +5880,7 @@ var ZodIssueCode = {
5873
5880
  var ZodFirstPartyTypeKind;
5874
5881
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5875
5882
  //#endregion
5876
- //#region ../types/dist/sleep-t7BftoJG.mjs
5883
+ //#region ../types/dist/sleep-CqvzKIq2.mjs
5877
5884
  /**
5878
5885
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5879
5886
  * window to float samples (D455).
@@ -16300,7 +16307,7 @@ object({
16300
16307
  params: record(string(), union([string(), number()])).optional()
16301
16308
  });
16302
16309
  //#endregion
16303
- //#region ../types/dist/composition-Bx0JvKUU.mjs
16310
+ //#region ../types/dist/composition-CXCX78IR.mjs
16304
16311
  /**
16305
16312
  * The ffmpeg build every CamStack node downloads, per platform and arch.
16306
16313
  *
@@ -47840,6 +47847,90 @@ DeviceType.Control, method(object({
47840
47847
  kind: "mutation",
47841
47848
  auth: "protected"
47842
47849
  });
47850
+ var IrCodecError = class extends Error {
47851
+ code;
47852
+ constructor(code, message) {
47853
+ super(message);
47854
+ this.name = "IrCodecError";
47855
+ this.code = code;
47856
+ }
47857
+ };
47858
+ /**
47859
+ * A pulse train in signed microseconds: mark (+) first, then space (-), strictly
47860
+ * alternating, no zeros, 2..1024 entries.
47861
+ */
47862
+ var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
47863
+ timings.forEach((value, index) => {
47864
+ const expectMark = index % 2 === 0;
47865
+ if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
47866
+ code: "custom",
47867
+ message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
47868
+ path: [index]
47869
+ });
47870
+ });
47871
+ });
47872
+ /** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
47873
+ var TransmitSettingsSchema = object({
47874
+ carrierHz: number().int().positive().optional(),
47875
+ dutyCyclePercent: number().int().min(1).max(100).optional(),
47876
+ repeat: number().int().min(0).max(20).optional(),
47877
+ repeatDelayMs: number().int().min(0).optional()
47878
+ });
47879
+ /** One Pronto frequency-word unit, in microseconds, per unit of the word. */
47880
+ var PRONTO_CLOCK_US = .241246;
47881
+ var LEARNED_FORM = 0;
47882
+ /**
47883
+ * Decode a learned (0000 form) Pronto hex string. The intro burst is followed by
47884
+ * the repeat burst once; a different form is refused with `pronto-unsupported-form`.
47885
+ */
47886
+ function decodePronto(code) {
47887
+ const words = code.trim().split(/\s+/).filter((w) => w.length > 0);
47888
+ if (words.length < 4) throw new IrCodecError("pronto-malformed", `Pronto code needs at least 4 words, got ${words.length}`);
47889
+ const values = words.map((w) => /^[0-9a-fA-F]{1,4}$/.test(w) ? parseInt(w, 16) : NaN);
47890
+ if (values.some((v) => Number.isNaN(v))) throw new IrCodecError("pronto-malformed", "Pronto code must be 4-digit hexadecimal words");
47891
+ const [form, freqWord, introCount, repeatCount] = values;
47892
+ if (form === void 0 || freqWord === void 0 || introCount === void 0 || repeatCount === void 0) throw new IrCodecError("pronto-malformed", "Pronto header incomplete");
47893
+ if (form !== LEARNED_FORM) throw new IrCodecError("pronto-unsupported-form", `Pronto form ${words[0] ?? ""} is not supported, only the learned 0000 form`);
47894
+ if (freqWord === 0) throw new IrCodecError("pronto-malformed", "Pronto frequency word is zero");
47895
+ const pairs = introCount + repeatCount;
47896
+ if (pairs === 0 || values.length !== 4 + pairs * 2) throw new IrCodecError("pronto-malformed", `Pronto declares ${pairs} burst pairs but carries ${(values.length - 4) / 2} word pairs`);
47897
+ const unitUs = freqWord * PRONTO_CLOCK_US;
47898
+ const timings = values.slice(4).map((count, index) => {
47899
+ const us = Math.max(1, Math.round(count * unitUs));
47900
+ return index % 2 === 0 ? us : -us;
47901
+ });
47902
+ const checked = IrTimingsSchema.safeParse(timings);
47903
+ if (!checked.success) throw new IrCodecError("invalid-timings", `Pronto decoded to invalid timings: ${checked.error.message}`);
47904
+ return {
47905
+ timings: checked.data,
47906
+ carrierHz: Math.round(1e6 / unitUs)
47907
+ };
47908
+ }
47909
+ /** What a remote command stores: a raw pulse train, or a learned Pronto code. */
47910
+ var IrPayloadSchema = discriminatedUnion("kind", [object({
47911
+ kind: literal("raw"),
47912
+ timings: array(number().int()),
47913
+ settings: TransmitSettingsSchema.optional()
47914
+ }), object({
47915
+ kind: literal("pronto"),
47916
+ code: string()
47917
+ })]);
47918
+ /** Resolve a payload to what `ir-transmitter.send` takes. Throws `IrCodecError`. */
47919
+ function toTimings(payload) {
47920
+ if (payload.kind === "pronto") {
47921
+ const decoded = decodePronto(payload.code);
47922
+ return {
47923
+ timings: decoded.timings,
47924
+ settings: { carrierHz: decoded.carrierHz }
47925
+ };
47926
+ }
47927
+ const checked = IrTimingsSchema.safeParse(payload.timings);
47928
+ if (!checked.success) throw new IrCodecError("invalid-timings", checked.error.message);
47929
+ return {
47930
+ timings: checked.data,
47931
+ settings: payload.settings ?? {}
47932
+ };
47933
+ }
47843
47934
  /**
47844
47935
  * The role vocabulary of a remote (D963): what a button MEANS, independent of
47845
47936
  * how it is wired. A layout template places buttons by role; a device binds a
@@ -47927,11 +48018,18 @@ var RemoteRoleSchema = string().refine(isRemoteRole, { message: "a role is one o
47927
48018
  * a voice command or `playMedia` names.
47928
48019
  * - `sequence`: a name and explicit steps — bound roles pressed in order
47929
48020
  * (an input key, a menu macro such as `source, down, down, ok`).
48021
+ * - `ir`: a name and one infrared code of its own (raw timings or a Pronto
48022
+ * string), sent through a named transmitter (planning#464). It presses no
48023
+ * button: a source the remote's layout has no key for.
47930
48024
  *
47931
48025
  * The name and the number resolve to the SAME declared source. A number no
47932
48026
  * `digits` source declares is not a channel: it is refused, never guessed.
47933
48027
  */
47934
- var REMOTE_SOURCE_KINDS = ["digits", "sequence"];
48028
+ var REMOTE_SOURCE_KINDS = [
48029
+ "digits",
48030
+ "sequence",
48031
+ "ir"
48032
+ ];
47935
48033
  _enum(REMOTE_SOURCE_KINDS);
47936
48034
  var REMOTE_SOURCE_ID_RE = /^[a-z0-9][a-z0-9_-]{0,47}$/;
47937
48035
  var CHANNEL_NUMBER_RE = new RegExp(`^[0-9]{1,6}$`);
@@ -47941,19 +48039,50 @@ var sourceBase = {
47941
48039
  /** What the operator calls it, and what a voice command or a picker names. */
47942
48040
  name: string().trim().min(1).max(60)
47943
48041
  };
47944
- var RemoteSourceSchema = discriminatedUnion("kind", [object({
47945
- ...sourceBase,
47946
- kind: literal("digits"),
47947
- number: string().regex(CHANNEL_NUMBER_RE, { message: "up to 6 digits" })
47948
- }), object({
47949
- ...sourceBase,
47950
- kind: literal("sequence"),
47951
- steps: array(RemoteRoleSchema).min(1).max(16)
47952
- })]);
48042
+ var RemoteSourceSchema = discriminatedUnion("kind", [
48043
+ object({
48044
+ ...sourceBase,
48045
+ kind: literal("digits"),
48046
+ number: string().regex(CHANNEL_NUMBER_RE, { message: "up to 6 digits" })
48047
+ }),
48048
+ object({
48049
+ ...sourceBase,
48050
+ kind: literal("sequence"),
48051
+ steps: array(RemoteRoleSchema).min(1).max(16)
48052
+ }),
48053
+ object({
48054
+ ...sourceBase,
48055
+ kind: literal("ir"),
48056
+ /** The `ir-transmitter` device the code is sent through. */
48057
+ transmitterId: number().int().nonnegative(),
48058
+ /** The code itself; whether a transmitter could send it is `irSourceProblem`. */
48059
+ payload: IrPayloadSchema
48060
+ })
48061
+ ]);
47953
48062
  var REMOTE_STEP_DELAY_MAX_MS = 3e3;
47954
- /** The roles a source presses, in order. A `digits` number is its digit roles. */
48063
+ /**
48064
+ * The roles a source presses, in order. A `digits` number is its digit roles;
48065
+ * an `ir` source presses none — it carries its own code.
48066
+ */
47955
48067
  function remoteSourceSteps(source) {
47956
- return source.kind === "sequence" ? source.steps : Array.from(source.number, (digit) => `digit_${digit}`);
48068
+ switch (source.kind) {
48069
+ case "sequence": return source.steps;
48070
+ case "digits": return Array.from(source.number, (digit) => `digit_${digit}`);
48071
+ case "ir": return [];
48072
+ }
48073
+ }
48074
+ /**
48075
+ * Why an `ir` source's code could not be sent (the codec's own words), or null
48076
+ * when it resolves to a pulse train. The schema refuses on it; the editor shows it.
48077
+ */
48078
+ function irSourceProblem(source) {
48079
+ try {
48080
+ toTimings(source.payload);
48081
+ return null;
48082
+ } catch (err) {
48083
+ if (err instanceof IrCodecError) return err.message;
48084
+ throw err;
48085
+ }
47957
48086
  }
47958
48087
  /**
47959
48088
  * Media-player cap. Models HA `media_player.*` (Sonos, Chromecast,
@@ -53470,8 +53599,9 @@ DeviceType.Thermostat;
53470
53599
  *
53471
53600
  * "Heat for 30 minutes", "run the fountain pump for 2 minutes": the device has
53472
53601
  * no timer of its own, so the hub keeps one. `runFor` turns the device on and
53473
- * records a deadline; `cancel` (or the device being switched off by hand)
53474
- * forgets it; at the deadline the hub issues the off once.
53602
+ * records a deadline; `cancel` switches the device off and forgets the deadline,
53603
+ * and the device being switched off by hand just forgets it; at the deadline
53604
+ * the hub issues the off once.
53475
53605
  *
53476
53606
  * The deadline is DURABLE: it is a row in the records store, so a hub restart
53477
53607
  * neither loses a pending off nor leaves the device running past its time. At
@@ -53608,6 +53738,56 @@ var StateLookSchema = object({
53608
53738
  background: _enum(HERO_BACKGROUNDS),
53609
53739
  animation: HeroAnimationSchema
53610
53740
  });
53741
+ /** The scene as the model carries it. `null` = the device did not say (never a guess). */
53742
+ var HeroSceneSchema = object({
53743
+ id: _enum([
53744
+ "ac",
53745
+ "thermostat",
53746
+ "heater",
53747
+ "fan",
53748
+ "washer",
53749
+ "dryer",
53750
+ "dishwasher",
53751
+ "pet-fountain",
53752
+ "pet-feeder",
53753
+ "vacuum",
53754
+ "lawn-mower",
53755
+ "tv",
53756
+ "speaker",
53757
+ "camera",
53758
+ "light",
53759
+ "cover",
53760
+ "lock",
53761
+ "alarm",
53762
+ "siren",
53763
+ "sensor-sparkline",
53764
+ "contact",
53765
+ "presence",
53766
+ "weather",
53767
+ "plug",
53768
+ "button",
53769
+ "update",
53770
+ "control-dial",
53771
+ "event-stream",
53772
+ "gateway",
53773
+ "generic"
53774
+ ]),
53775
+ mode: _enum([
53776
+ "cool",
53777
+ "heat",
53778
+ "dry",
53779
+ "fan",
53780
+ "auto",
53781
+ "playing",
53782
+ "paused",
53783
+ "open",
53784
+ "closed"
53785
+ ]).nullable(),
53786
+ /** 0..1: volume, cover position, cycle progress. */
53787
+ level: number().min(0).max(1).nullable()
53788
+ });
53789
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Heater, DeviceType.Dryer, DeviceType.Gateway;
53790
+ DeviceRole.Siren, DeviceRole.Floodlight, DeviceRole.Spotlight, DeviceRole.PirSensor, DeviceRole.Chime, DeviceRole.Autotrack, DeviceRole.Nightvision, DeviceRole.PrivacyMask, DeviceRole.Doorbell, DeviceRole.CoverBlind, DeviceRole.CoverShutter, DeviceRole.CoverAwning, DeviceRole.CoverCurtain, DeviceRole.CoverGarage, DeviceRole.CoverGate, DeviceRole.CoverDoor, DeviceRole.CoverWindow, DeviceRole.BinaryHelper, DeviceRole.Outlet, DeviceRole.MotionSensor, DeviceRole.ContactSensor, DeviceRole.LeakSensor, DeviceRole.SmokeSensor, DeviceRole.COSensor, DeviceRole.GasSensor, DeviceRole.TamperSensor, DeviceRole.VibrationSensor, DeviceRole.ConnectivitySensor, DeviceRole.SoundSensor, DeviceRole.BinarySensor, DeviceRole.TemperatureSensor, DeviceRole.HumiditySensor, DeviceRole.AmbientLightSensor, DeviceRole.PressureSensor, DeviceRole.WindSensor, DeviceRole.RainSensor, DeviceRole.UvSensor, DeviceRole.SolarRadiationSensor, DeviceRole.SoilMoistureSensor, DeviceRole.PowerSensor, DeviceRole.EnergySensor, DeviceRole.VoltageSensor, DeviceRole.CurrentSensor, DeviceRole.AirQualitySensor, DeviceRole.BatterySensor, DeviceRole.NumericSensor, DeviceRole.EnumSensor, DeviceRole.DateTimeSensor, DeviceRole.GenericSensor, DeviceRole.NumericControl, DeviceRole.SelectControl, DeviceRole.TextControl, DeviceRole.DateTimeControl, DeviceRole.MobilePushNotifier, DeviceRole.MessagingNotifier, DeviceRole.EmailNotifier, DeviceRole.GenericNotifier, DeviceRole.IrTransmitter;
53611
53791
  /** A string in both languages, as the cap vocabulary writes it (D920): no `t()` in the model. */
53612
53792
  var HeroTextSchema = object({
53613
53793
  en: string(),
@@ -53759,6 +53939,32 @@ var HeroStateBackdropSchema = object({
53759
53939
  token: _enum(HERO_BACKGROUNDS),
53760
53940
  animation: _enum(HERO_ANIMATIONS)
53761
53941
  });
53942
+ var HeroFactSchema = object({
53943
+ id: _enum([
53944
+ "power",
53945
+ "energy",
53946
+ "battery",
53947
+ "link",
53948
+ "phase",
53949
+ "remaining",
53950
+ "volume",
53951
+ "source",
53952
+ "consumables"
53953
+ ]),
53954
+ icon: IconKeySchema.nullable(),
53955
+ text: HeroTextSchema,
53956
+ tone: HeroToneSchema
53957
+ });
53958
+ /** A labelled value beside the scene. */
53959
+ var HeroSideTileSchema = object({
53960
+ label: HeroTextSchema,
53961
+ value: HeroTextSchema,
53962
+ tone: HeroToneSchema.optional()
53963
+ });
53964
+ var HeroSideTilesSchema = object({
53965
+ left: array(HeroSideTileSchema).max(2),
53966
+ right: array(HeroSideTileSchema).max(2)
53967
+ });
53762
53968
  var HeroBackgroundSchema = discriminatedUnion("kind", [object({
53763
53969
  kind: literal("artwork"),
53764
53970
  url: string(),
@@ -53782,6 +53988,11 @@ var HeroModelSchema = object({
53782
53988
  stateAssumed: boolean(),
53783
53989
  look: StateLookSchema,
53784
53990
  background: HeroBackgroundSchema,
53991
+ /** Which animated picture the hero (and the list row) draws; its motion is `look.animation`. */
53992
+ scene: HeroSceneSchema,
53993
+ /** The top line's inline facts, in order; never wraps, so a client may cut the tail. */
53994
+ facts: array(HeroFactSchema),
53995
+ sideTiles: HeroSideTilesSchema,
53785
53996
  /** The status line under the main icon. */
53786
53997
  title: HeroTextSchema,
53787
53998
  badges: array(HeroBadgeSchema),
@@ -53797,6 +54008,42 @@ var HeroModelSchema = object({
53797
54008
  provenance: array(HeroProvenanceSchema)
53798
54009
  });
53799
54010
  /**
54011
+ * The ROW of a device in a list (D1023 P2): four fixed zones, all taken from
54012
+ * the hero model and nothing else.
54013
+ *
54014
+ * 1. mini-scene the same scene id as the hero, drawn 44x44 with the state look;
54015
+ * 2. name + ONE status line the facts joined by " · " by the client;
54016
+ * 3. the type's primary inline control;
54017
+ * 4. a trailing fact (power, else battery, else consumables).
54018
+ *
54019
+ * Selection rules, all taken from the model:
54020
+ * - ONE control is ever shown: the device's own power; else the FIRST control of
54021
+ * the FIRST row that has any (a later row's control is never promoted). When
54022
+ * the model lists that part in `gated`, the row has no inline control: the
54023
+ * projection never falls through to another control the caller could use.
54024
+ * - the status line is the head (what plays, else the state) plus at most
54025
+ * {@link ROW_STATUS_FACTS_MAX} facts, the trailing fact excluded;
54026
+ * - the trailing fact is the first of power, battery, consumables.
54027
+ *
54028
+ * `projectDeviceRow` is pure and computes nothing: it selects from a
54029
+ * {@link HeroModel}. A row that needed a value the hero lacks would mean the
54030
+ * model is missing it, so the fix goes in the model.
54031
+ */
54032
+ var HeroRowModelSchema = object({
54033
+ deviceId: number().int(),
54034
+ name: string(),
54035
+ type: string(),
54036
+ state: HeroStateSchema,
54037
+ stateAssumed: boolean(),
54038
+ look: StateLookSchema,
54039
+ scene: HeroSceneSchema,
54040
+ /** Joined by " · ": the state (or what is playing), then the facts. */
54041
+ statusLine: array(HeroTextSchema),
54042
+ /** `null` = the type has no inline control, or the caller may not act on it. */
54043
+ primary: HeroControlSchema.nullable(),
54044
+ trailing: HeroFactSchema.nullable()
54045
+ });
54046
+ /**
53800
54047
  * hero-layout — what a device's hero says (D1023). Read-only: the FIXED
53801
54048
  * layouts live in `@camstack/types` and nothing here stores or edits one.
53802
54049
  * `getHeroModel` builds the pure model from the device's bindings and its
@@ -53812,6 +54059,11 @@ var heroInput = object({
53812
54059
  */
53813
54060
  canActuate: boolean().optional()
53814
54061
  });
54062
+ var rowModelsInput = object({
54063
+ deviceIds: array(number().int().nonnegative()).max(500),
54064
+ /** As `canActuate` of `getHeroModel`; absent = may. */
54065
+ canActuate: boolean().optional()
54066
+ });
53815
54067
  var HeroLayoutRowsSchema = object({
53816
54068
  type: string(),
53817
54069
  rows: array(HeroRowIdSchema)
@@ -53819,6 +54071,9 @@ var HeroLayoutRowsSchema = object({
53819
54071
  method(heroInput, HeroModelSchema.nullable(), {
53820
54072
  auth: "protected",
53821
54073
  role: "viewer"
54074
+ }), method(rowModelsInput, array(HeroRowModelSchema.nullable()), {
54075
+ auth: "protected",
54076
+ role: "viewer"
53822
54077
  }), method(object({ deviceType: string() }), HeroLayoutRowsSchema, {
53823
54078
  auth: "protected",
53824
54079
  role: "viewer"
@@ -54342,13 +54597,27 @@ var IntegrationInstancePatchSchema = object({
54342
54597
  method(_void(), IntegrationInstanceDescriptionSchema, { auth: "admin" }), method(object({
54343
54598
  id: IntegrationInstanceIdSchema.nullable(),
54344
54599
  /** The kind of the create form (`id: null`) of a provider with several. */
54345
- kind: string().min(1).optional()
54600
+ kind: string().min(1).optional(),
54601
+ /** The wizard mode of the integration type (`getAvailableTypes`); a form may differ per mode. */
54602
+ mode: _enum([
54603
+ "standalone",
54604
+ "account",
54605
+ "broker"
54606
+ ]).optional(),
54607
+ /** The discovery candidate the wizard was seeded from (`stableId`), if any. */
54608
+ candidateStableId: string().min(1).optional()
54346
54609
  }), ConfigSchemaPassthrough$2, { auth: "admin" }), method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }), method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }), method(object({
54347
54610
  id: IntegrationInstanceIdSchema.optional(),
54348
54611
  name: string().min(1),
54349
54612
  kind: string().min(1).optional(),
54350
54613
  /** Form values written with the record (a link picked in the create dialog). */
54351
- values: record(string(), unknown()).optional()
54614
+ values: record(string(), unknown()).optional(),
54615
+ /**
54616
+ * The discovery candidate (`stableId`) the instance is set up from. Kept on the integration
54617
+ * registry row of an instance that brings devices, so discovery stops offering a candidate
54618
+ * that already has its integration (D984). Ignored by an instance with no registry row.
54619
+ */
54620
+ originStableId: string().min(1).optional()
54352
54621
  }), object({ id: string() }), {
54353
54622
  kind: "mutation",
54354
54623
  auth: "admin"
@@ -54475,27 +54744,6 @@ DeviceType.Camera, method(object({ deviceId: number() }), object({
54475
54744
  deviceId: number(),
54476
54745
  status: IntercomStatusSchema
54477
54746
  });
54478
- /**
54479
- * A pulse train in signed microseconds: mark (+) first, then space (-), strictly
54480
- * alternating, no zeros, 2..1024 entries.
54481
- */
54482
- var IrTimingsSchema = array(number().int()).min(2).max(1024).superRefine((timings, ctx) => {
54483
- timings.forEach((value, index) => {
54484
- const expectMark = index % 2 === 0;
54485
- if (value === 0 || value > 0 !== expectMark) ctx.addIssue({
54486
- code: "custom",
54487
- message: `timing ${index} must be a non-zero ${expectMark ? "mark (positive)" : "space (negative)"}`,
54488
- path: [index]
54489
- });
54490
- });
54491
- });
54492
- /** Per-command transmit options. Every key is optional; a transmitter applies what it can. */
54493
- var TransmitSettingsSchema = object({
54494
- carrierHz: number().int().positive().optional(),
54495
- dutyCyclePercent: number().int().min(1).max(100).optional(),
54496
- repeat: number().int().min(0).max(20).optional(),
54497
- repeatDelayMs: number().int().min(0).optional()
54498
- });
54499
54747
  /** The setting keys a transmitter may declare as settable (`getInfo.settable`). */
54500
54748
  var TransmitSettingKeySchema = _enum([
54501
54749
  "carrierHz",
@@ -70818,6 +71066,10 @@ function refineSources(config, ctx) {
70818
71066
  if (numbers.has(source.number)) fail(`channel ${source.number} is declared twice`, "number");
70819
71067
  numbers.add(source.number);
70820
71068
  }
71069
+ if (source.kind === "ir") {
71070
+ const problem = irSourceProblem(source);
71071
+ if (problem !== null) fail(problem, "payload");
71072
+ }
70821
71073
  for (const role of remoteSourceSteps(source)) if (isRemoteWidgetRole(role)) fail(`"${source.name}" presses ${role}, a layout widget, not a command`, "steps");
70822
71074
  else if (config.bindings[role] === void 0) fail(`"${source.name}" presses ${role}, which is not bound`, "steps");
70823
71075
  });
@@ -70839,6 +71091,14 @@ function refineSources(config, ctx) {
70839
71091
  }
70840
71092
  });
70841
71093
  }
71094
+ /** A stored source that was left out of a read, and why (the UI shows it). */
71095
+ var RemoteRefusedSourceSchema = object({
71096
+ /** Position in the stored list. */
71097
+ index: number().int().nonnegative(),
71098
+ id: string().nullable(),
71099
+ name: string().nullable(),
71100
+ reason: string()
71101
+ });
70842
71102
  var RemoteConfigSchema = object({
70843
71103
  layoutTemplateId: string().min(1).nullable(),
70844
71104
  /**
@@ -70856,7 +71116,13 @@ var RemoteConfigSchema = object({
70856
71116
  sources: array(RemoteSourceSchema).max(200).default([]),
70857
71117
  /** The pause between two steps of a source: the television's receiver, not a repeat. */
70858
71118
  stepDelayMs: number().int().min(100).max(REMOTE_STEP_DELAY_MAX_MS).default(500),
70859
- power: object({ mode: RemotePowerModeSchema })
71119
+ power: object({ mode: RemotePowerModeSchema }),
71120
+ /**
71121
+ * READ-ONLY: sources a stored configuration carries that no longer parse,
71122
+ * left out of `sources` with the reason (`readStoredRemoteConfig`). Never
71123
+ * stored: the store strips it on write.
71124
+ */
71125
+ refusedSources: array(RemoteRefusedSourceSchema).optional()
70860
71126
  }).superRefine((config, ctx) => {
70861
71127
  const roles = Object.keys(config.bindings);
70862
71128
  if (roles.length > 200) ctx.addIssue({
@@ -70897,6 +71163,11 @@ var RemoteConfigSchema = object({
70897
71163
  }
70898
71164
  refineSources(config, ctx);
70899
71165
  });
71166
+ looseObject({ sources: array(unknown()) });
71167
+ object({
71168
+ id: string(),
71169
+ name: string()
71170
+ }).partial();
70900
71171
  object({
70901
71172
  power: object({
70902
71173
  state: _enum(["on", "off"]),
@@ -75971,14 +76242,6 @@ btn("power", 0, 0, {
75971
76242
  3,
75972
76243
  4
75973
76244
  ].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" });
75974
- discriminatedUnion("kind", [object({
75975
- kind: literal("raw"),
75976
- timings: array(number().int()),
75977
- settings: TransmitSettingsSchema.optional()
75978
- }), object({
75979
- kind: literal("pronto"),
75980
- code: string()
75981
- })]);
75982
76245
  /**
75983
76246
  * Propose a running threshold from a device's own power history (planning#405).
75984
76247
  *
@@ -79793,6 +80056,13 @@ Object.freeze({
79793
80056
  access: "view",
79794
80057
  role: "viewer"
79795
80058
  },
80059
+ "heroLayout.getRowModels": {
80060
+ capName: "hero-layout",
80061
+ capScope: "system",
80062
+ addonId: null,
80063
+ access: "view",
80064
+ role: "viewer"
80065
+ },
79796
80066
  "humidifier.getStatus": {
79797
80067
  capName: "humidifier",
79798
80068
  capScope: "device",
@@ -87754,6 +88024,11 @@ Object.freeze({
87754
88024
  form: "single",
87755
88025
  optional: false
87756
88026
  }],
88027
+ "heroLayout.getRowModels": [{
88028
+ name: "deviceIds",
88029
+ form: "array",
88030
+ optional: false
88031
+ }],
87757
88032
  "humidifier.getStatus": [{
87758
88033
  name: "deviceId",
87759
88034
  form: "single",
@@ -91063,7 +91338,7 @@ var AgentUIAddon = class extends BaseAddon {
91063
91338
  capability: adminUiCapability,
91064
91339
  provider: {
91065
91340
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
91066
- getVersion: async () => ({ version: "1.2.347" })
91341
+ getVersion: async () => ({ version: "1.2.349" })
91067
91342
  }
91068
91343
  }];
91069
91344
  }
@@ -1 +1 @@
1
- import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{T as a,_ as o,c as s,d as c,l,p as u,t as d}from"./index-D2TZmpoQ.js";import{MaskShapeCanvas as f}from"./MaskShapeCanvas-BTvl6Rin.js";var p=a(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),m=n(i(),1),h=n(e(),1),g=110,_=`motion-zones`,v=0,y=[1,2,3],b=1,x=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,S=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function C(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function w(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function T(e,t){return Math.ceil(e/t)}function E(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function D(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+E(n,t,i)]=!0)}return o}function O(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+E(n,t,i)]===!0}return o}function k({deviceId:e}){let n=t(),i=c(o().trpcClient,e),[a,E]=(0,m.useState)(null),[k,A]=(0,m.useState)(!1),[j,M]=(0,m.useState)(null),[N,P]=(0,m.useState)(null),[F,I]=(0,m.useState)(b),[L,R]=(0,m.useState)(!1),[z,B]=(0,m.useState)(!1),V=(0,m.useRef)(!1);(0,m.useEffect)(()=>{if(!i)return;let e=!1;return V.current=!1,E(null),A(!1),M(null),P(null),I(b),B(!1),(async()=>{try{let t=await i.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(E(t),V.current)return;let n=await i.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);V.current=!0;let r=n.regions.find(e=>e.shape.kind===`grid`),a=C(r?r.shape.cells:[],t.grid.width,t.grid.height);P(a),M(D(a,t.grid.width,t.grid.height,b))}catch(t){if(e)return;l(t)?A(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[i]);let H=a?a.grid.width:0,U=a?a.grid.height:0,W=T(H,F),G=T(U,F),K=(0,m.useMemo)(()=>j&&a?O(j,H,U,F):null,[j,a,H,U,F]),q=(0,m.useMemo)(()=>K!==null&&N!==null&&!w(K,N),[K,N]),J=(0,m.useMemo)(()=>K?K.reduce((e,t)=>t?e+1:e,0):0,[K]),Y=H*U,X=W*G,Z=(0,m.useCallback)(e=>{X>0&&M(Array.from({length:X},()=>e))},[X]),ee=(0,m.useCallback)(()=>{M(e=>e&&e.map(e=>!e))},[]),te=(0,m.useCallback)(()=>{N&&a&&M(D(N,H,U,F))},[N,a,H,U,F]),ne=(0,m.useCallback)(e=>{e===F||!a||M(t=>{if(!t)return I(e),t;let n=D(O(t,H,U,F),H,U,e);return I(e),n})},[F,a,H,U]),Q=(0,m.useMemo)(()=>a&&j?[{id:v,shape:{kind:`grid`,gridWidth:W,gridHeight:G,cells:[...j]}}]:[],[a,j,W,G]),$=(0,m.useCallback)((e,t)=>{t.kind===`grid`&&M(t.cells)},[]),re=(0,m.useCallback)(async()=>{if(!(!i||!j||!a)){R(!0);try{let e=O(j,a.grid.width,a.grid.height,F),t={kind:`grid`,gridWidth:a.grid.width,gridHeight:a.grid.height,cells:e};await i.motionZones?.setZone({patch:{regions:[{id:v,enabled:!0,shape:t}]}});let n=await i.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=C(e?e.shape.cells:[],a.grid.width,a.grid.height);P(t),M(D(t,a.grid.width,a.grid.height,F))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{R(!1)}}},[i,j,a,F]);d((0,m.useMemo)(()=>z&&!k&&a&&j?{id:_,order:g,node:(0,h.jsx)(f,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:W,height:G},selectedId:v,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[z,k,a,j,Q,$,W,G]));let ie=!k&&a!==null&&j!==null;return i?(0,h.jsx)(u,{title:n(`uiLibrary.motionZones.title`,`Motion Zones`),icon:(0,h.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsx)(`div`,{className:`flex flex-col gap-3`,children:k?(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.unsupported`,`This camera doesn't expose an on-board motion zones grid.`)}):ie?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:(0,h.jsx)(r,{uiKey:`uiLibrary.motionZones.intro`,fallback:`Toggle <0>Edit grid</0> to paint the region directly on the live frame, then <1>Save</1> to push the mask to the camera. Pick a bigger <2>Cell size</2> for quicker, broad-stroke painting — it's resampled to the camera's native {{width}}×{{height}} grid on save (×1 is the finest).`,components:[(0,h.jsx)(`strong`,{className:`text-foreground`},`e`),(0,h.jsx)(`strong`,{className:`text-foreground`},`s`),(0,h.jsx)(`strong`,{className:`text-foreground`},`c`)],values:{width:a.grid.width,height:a.grid.height}})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:()=>B(e=>!e),disabled:L,"aria-pressed":z,className:z?S:x,children:z?n(`uiLibrary.motionZones.doneEditing`,`Done editing`):n(`uiLibrary.motionZones.editGrid`,`Edit grid`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!0),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOn`,`All on`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!1),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOff`,`All off`)}),(0,h.jsx)(`button`,{type:`button`,onClick:ee,disabled:L,className:x,children:n(`uiLibrary.motionZones.invert`,`Invert`)}),(0,h.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":n(`uiLibrary.motionZones.cellSize`,`Cell size`),children:[(0,h.jsx)(`span`,{className:`${s} mr-0.5`,children:n(`uiLibrary.motionZones.cellSize`,`Cell size`)}),y.map(e=>(0,h.jsxs)(`button`,{type:`button`,onClick:()=>ne(e),disabled:L,"aria-pressed":F===e,title:e===1?n(`uiLibrary.motionZones.cameraGrid`,`Camera grid {{width}}×{{height}} (finest)`,{width:H,height:U}):n(`uiLibrary.motionZones.paintingGrid`,`{{width}}×{{height}} painting grid · cells ×{{factor}} bigger`,{width:T(H,e),height:T(U,e),factor:e}),className:F===e?S:x,children:[`×`,e]},e))]}),(0,h.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[n(`uiLibrary.motionZones.cells`,`{{active}} / {{total}} cells · {{width}}×{{height}}`,{active:J,total:Y,width:a.grid.width,height:a.grid.height}),F===1?``:n(`uiLibrary.motionZones.paintSuffix`,` · paint {{width}}×{{height}}`,{width:W,height:G})]}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:te,disabled:L||!q,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:n(`uiLibrary.motionZones.revert`,`Revert`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>void re(),disabled:L||!q,className:S,children:L?n(`uiLibrary.motionZones.saving`,`Saving…`):n(`uiLibrary.motionZones.save`,`Save`)})]})]}):(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.loading`,`Loading the camera's grid…`)})})}):null}export{k as MotionZonesSettings};
1
+ import{a as e,i as t,l as n,n as r,o as i}from"./scheduler-0XZJE03d.js";import{T as a,_ as o,c as s,d as c,l,p as u,t as d}from"./index-Bngw8_Yl.js";import{MaskShapeCanvas as f}from"./MaskShapeCanvas-BTvl6Rin.js";var p=a(`grid-2x2`,[[`path`,{d:`M12 3v18`,key:`108xh3`}],[`path`,{d:`M3 12h18`,key:`1i2n21`}],[`rect`,{x:`3`,y:`3`,width:`18`,height:`18`,rx:`2`,key:`h1oib`}]]),m=n(i(),1),h=n(e(),1),g=110,_=`motion-zones`,v=0,y=[1,2,3],b=1,x=`rounded-md border border-border bg-surface px-2 py-1 text-[11px] font-medium text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,S=`rounded-md border border-primary/50 bg-primary/15 px-2.5 py-1 text-[11px] font-medium text-primary hover:bg-primary/25 disabled:opacity-40 transition-colors`;function C(e,t,n){let r=t*n,i=Array.from({length:r});for(let t=0;t<r;t+=1)i[t]=e[t]===!0;return i}function w(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n+=1)if(e[n]!==t[n])return!1;return!0}function T(e,t){return Math.ceil(e/t)}function E(e,t,n){return Math.min(n-1,Math.floor((e+.5)/t*n))}function D(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:i*a},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)e[r*t+n]===!0&&(o[s*i+E(n,t,i)]=!0)}return o}function O(e,t,n,r){let i=T(t,r),a=T(n,r),o=Array.from({length:t*n},()=>!1);for(let r=0;r<n;r+=1){let s=E(r,n,a);for(let n=0;n<t;n+=1)o[r*t+n]=e[s*i+E(n,t,i)]===!0}return o}function k({deviceId:e}){let n=t(),i=c(o().trpcClient,e),[a,E]=(0,m.useState)(null),[k,A]=(0,m.useState)(!1),[j,M]=(0,m.useState)(null),[N,P]=(0,m.useState)(null),[F,I]=(0,m.useState)(b),[L,R]=(0,m.useState)(!1),[z,B]=(0,m.useState)(!1),V=(0,m.useRef)(!1);(0,m.useEffect)(()=>{if(!i)return;let e=!1;return V.current=!1,E(null),A(!1),M(null),P(null),I(b),B(!1),(async()=>{try{let t=await i.motionZones?.getOptions({});if(e)return;if(!t)throw Error(`device proxy not ready`);if(E(t),V.current)return;let n=await i.motionZones?.getStatus({});if(e)return;if(!n)throw Error(`device proxy not ready`);V.current=!0;let r=n.regions.find(e=>e.shape.kind===`grid`),a=C(r?r.shape.cells:[],t.grid.width,t.grid.height);P(a),M(D(a,t.grid.width,t.grid.height,b))}catch(t){if(e)return;l(t)?A(!0):console.error(`Motion Zones load failed`,t)}})(),()=>{e=!0}},[i]);let H=a?a.grid.width:0,U=a?a.grid.height:0,W=T(H,F),G=T(U,F),K=(0,m.useMemo)(()=>j&&a?O(j,H,U,F):null,[j,a,H,U,F]),q=(0,m.useMemo)(()=>K!==null&&N!==null&&!w(K,N),[K,N]),J=(0,m.useMemo)(()=>K?K.reduce((e,t)=>t?e+1:e,0):0,[K]),Y=H*U,X=W*G,Z=(0,m.useCallback)(e=>{X>0&&M(Array.from({length:X},()=>e))},[X]),ee=(0,m.useCallback)(()=>{M(e=>e&&e.map(e=>!e))},[]),te=(0,m.useCallback)(()=>{N&&a&&M(D(N,H,U,F))},[N,a,H,U,F]),ne=(0,m.useCallback)(e=>{e===F||!a||M(t=>{if(!t)return I(e),t;let n=D(O(t,H,U,F),H,U,e);return I(e),n})},[F,a,H,U]),Q=(0,m.useMemo)(()=>a&&j?[{id:v,shape:{kind:`grid`,gridWidth:W,gridHeight:G,cells:[...j]}}]:[],[a,j,W,G]),$=(0,m.useCallback)((e,t)=>{t.kind===`grid`&&M(t.cells)},[]),re=(0,m.useCallback)(async()=>{if(!(!i||!j||!a)){R(!0);try{let e=O(j,a.grid.width,a.grid.height,F),t={kind:`grid`,gridWidth:a.grid.width,gridHeight:a.grid.height,cells:e};await i.motionZones?.setZone({patch:{regions:[{id:v,enabled:!0,shape:t}]}});let n=await i.motionZones?.getStatus({});if(n){let e=n.regions.find(e=>e.shape.kind===`grid`),t=C(e?e.shape.cells:[],a.grid.width,a.grid.height);P(t),M(D(t,a.grid.width,a.grid.height,F))}}catch(e){console.error(`Motion Zones save failed`,e)}finally{R(!1)}}},[i,j,a,F]);d((0,m.useMemo)(()=>z&&!k&&a&&j?{id:_,order:g,node:(0,h.jsx)(f,{transparent:!0,items:Q,supportedShapes:[`grid`],grid:{width:W,height:G},selectedId:v,onSelect:()=>{},onShapeChange:$,onDrawComplete:()=>{},drawingKind:null})}:null,[z,k,a,j,Q,$,W,G]));let ie=!k&&a!==null&&j!==null;return i?(0,h.jsx)(u,{title:n(`uiLibrary.motionZones.title`,`Motion Zones`),icon:(0,h.jsx)(p,{className:`h-3.5 w-3.5 text-foreground-subtle`}),children:(0,h.jsx)(`div`,{className:`flex flex-col gap-3`,children:k?(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.unsupported`,`This camera doesn't expose an on-board motion zones grid.`)}):ie?(0,h.jsxs)(h.Fragment,{children:[(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:(0,h.jsx)(r,{uiKey:`uiLibrary.motionZones.intro`,fallback:`Toggle <0>Edit grid</0> to paint the region directly on the live frame, then <1>Save</1> to push the mask to the camera. Pick a bigger <2>Cell size</2> for quicker, broad-stroke painting — it's resampled to the camera's native {{width}}×{{height}} grid on save (×1 is the finest).`,components:[(0,h.jsx)(`strong`,{className:`text-foreground`},`e`),(0,h.jsx)(`strong`,{className:`text-foreground`},`s`),(0,h.jsx)(`strong`,{className:`text-foreground`},`c`)],values:{width:a.grid.width,height:a.grid.height}})}),(0,h.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,h.jsx)(`button`,{type:`button`,onClick:()=>B(e=>!e),disabled:L,"aria-pressed":z,className:z?S:x,children:z?n(`uiLibrary.motionZones.doneEditing`,`Done editing`):n(`uiLibrary.motionZones.editGrid`,`Edit grid`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!0),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOn`,`All on`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>Z(!1),disabled:L,className:x,children:n(`uiLibrary.motionZones.allOff`,`All off`)}),(0,h.jsx)(`button`,{type:`button`,onClick:ee,disabled:L,className:x,children:n(`uiLibrary.motionZones.invert`,`Invert`)}),(0,h.jsxs)(`div`,{className:`flex items-center gap-1 ml-1`,role:`group`,"aria-label":n(`uiLibrary.motionZones.cellSize`,`Cell size`),children:[(0,h.jsx)(`span`,{className:`${s} mr-0.5`,children:n(`uiLibrary.motionZones.cellSize`,`Cell size`)}),y.map(e=>(0,h.jsxs)(`button`,{type:`button`,onClick:()=>ne(e),disabled:L,"aria-pressed":F===e,title:e===1?n(`uiLibrary.motionZones.cameraGrid`,`Camera grid {{width}}×{{height}} (finest)`,{width:H,height:U}):n(`uiLibrary.motionZones.paintingGrid`,`{{width}}×{{height}} painting grid · cells ×{{factor}} bigger`,{width:T(H,e),height:T(U,e),factor:e}),className:F===e?S:x,children:[`×`,e]},e))]}),(0,h.jsxs)(`span`,{className:`${s} ml-1 tabular-nums`,children:[n(`uiLibrary.motionZones.cells`,`{{active}} / {{total}} cells · {{width}}×{{height}}`,{active:J,total:Y,width:a.grid.width,height:a.grid.height}),F===1?``:n(`uiLibrary.motionZones.paintSuffix`,` · paint {{width}}×{{height}}`,{width:W,height:G})]}),(0,h.jsx)(`span`,{className:`flex-1`}),(0,h.jsx)(`button`,{type:`button`,onClick:te,disabled:L||!q,className:`rounded-md border border-border bg-surface px-2 py-1 text-[11px] text-foreground-subtle hover:bg-surface-hover disabled:opacity-40 transition-colors`,children:n(`uiLibrary.motionZones.revert`,`Revert`)}),(0,h.jsx)(`button`,{type:`button`,onClick:()=>void re(),disabled:L||!q,className:S,children:L?n(`uiLibrary.motionZones.saving`,`Saving…`):n(`uiLibrary.motionZones.save`,`Save`)})]})]}):(0,h.jsx)(`p`,{className:`${s} leading-relaxed`,children:n(`uiLibrary.motionZones.loading`,`Loading the camera's grid…`)})})}):null}export{k as MotionZonesSettings};