@camstack/addon-auth 1.2.16 → 1.2.18

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.
@@ -13097,11 +13097,33 @@ var NotificationFormatSchema = _enum([
13097
13097
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
13098
13098
  * renderer's icon set; "acknowledge" survives an adapter that draws it
13099
13099
  * differently.
13100
+ *
13101
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
13102
+ *
13103
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
13104
+ * the whole set onto its own renderer's vocabulary through a
13105
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
13106
+ * fallback, so adding a member here fails every adapter's build until someone
13107
+ * decides its glyph, which is the only place that decision can be made
13108
+ * honestly.
13109
+ *
13110
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
13111
+ * `disarm` straight through; iOS feeds that string to
13112
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
13113
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
13114
+ * mapping, and "the field is documented" is not "the value renders".
13115
+ *
13116
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
13117
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
13118
+ * so an addon that emits a token the running hub does not know does not lose an
13119
+ * icon — its whole `send` fails Zod validation and the notification never
13120
+ * arrives. Never emit a new token from an addon before the train carrying it.
13100
13121
  */
13101
13122
  var NotificationActionIconSchema = _enum([
13102
13123
  "acknowledge",
13103
13124
  "dismiss",
13104
13125
  "silence",
13126
+ "snooze",
13105
13127
  "view",
13106
13128
  "play",
13107
13129
  "open",
@@ -13109,9 +13131,13 @@ var NotificationActionIconSchema = _enum([
13109
13131
  "lock",
13110
13132
  "unlock",
13111
13133
  "arm",
13134
+ "arm-home",
13135
+ "arm-away",
13136
+ "arm-night",
13112
13137
  "disarm",
13113
13138
  "light",
13114
- "alert"
13139
+ "alert",
13140
+ "camera"
13115
13141
  ]);
13116
13142
  /** A single tap-through action button. */
13117
13143
  var NotificationActionSchema = object({
@@ -13127,7 +13153,23 @@ var NotificationActionSchema = object({
13127
13153
  * else — see `notification-center/action-token.ts` for what that does and
13128
13154
  * does not buy.
13129
13155
  */
13130
- destructive: boolean().optional()
13156
+ destructive: boolean().optional(),
13157
+ /**
13158
+ * How the tap should REACH the url.
13159
+ *
13160
+ * `navigate` (absent, and every button authored before this field) opens it:
13161
+ * the phone leaves the notification and shows whatever the callback returns.
13162
+ * That is right for a button whose answer the operator wants to read.
13163
+ *
13164
+ * `background` fires it as a POST and stays put. It exists for the buttons
13165
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
13166
+ * an answer to the notification, and being thrown into a browser tab to
13167
+ * confirm it costs more attention than the notification did. A backend that
13168
+ * cannot do a background call renders it as an ordinary link (the adapters
13169
+ * fall back rather than dropping the button), so this is a preference, never
13170
+ * a requirement.
13171
+ */
13172
+ mode: _enum(["navigate", "background"]).optional()
13131
13173
  });
13132
13174
  /**
13133
13175
  * The canonical notification. `body` is the only hard field (Apprise model).
@@ -13295,6 +13337,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
13295
13337
  targetId: string(),
13296
13338
  enabled: boolean()
13297
13339
  }), _void(), { kind: "mutation" });
13340
+ new Set([
13341
+ {
13342
+ id: "person",
13343
+ name: "Person"
13344
+ },
13345
+ {
13346
+ id: "vehicle",
13347
+ name: "Vehicle"
13348
+ },
13349
+ {
13350
+ id: "animal",
13351
+ name: "Animal"
13352
+ },
13353
+ {
13354
+ id: "package",
13355
+ name: "Package"
13356
+ }
13357
+ ].map((l) => l.id));
13298
13358
  var COCO_TO_MACRO = {
13299
13359
  mapping: {
13300
13360
  person: "person",
@@ -14092,6 +14152,8 @@ var NcSystemEventKindSchema = _enum([
14092
14152
  "alarm-triggered",
14093
14153
  "alarm-armed",
14094
14154
  "alarm-disarmed",
14155
+ "alarm-arming",
14156
+ "alarm-arm-refused",
14095
14157
  "camera-online",
14096
14158
  "camera-offline",
14097
14159
  "camera-disabled",
@@ -14128,6 +14190,9 @@ var NcSystemEventConditionSchema = object({
14128
14190
  nodeIds: array(string().min(1)).min(1).optional(),
14129
14191
  packageNames: array(string().min(1)).min(1).optional()
14130
14192
  });
14193
+ /** Hard ceiling on a window (24h). A snooze that could not expire would be an
14194
+ * outage the operator asked for once and forgot. */
14195
+ var NC_SNOOZE_MAX_MINUTES = 1440;
14131
14196
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14132
14197
  var NcScheduleSchema = object({
14133
14198
  windows: array(object({
@@ -14504,15 +14569,15 @@ var NcConditionsSchema = object({
14504
14569
  * (an `immediate` rule naming an `audio-*` class, one notification per
14505
14570
  * classified sample) stays exactly as it was for rules that already use it.
14506
14571
  *
14507
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
14508
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
14509
- * (`camstack/src/data/notification-center.ts`, guarded by
14510
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
14572
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
14573
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
14574
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
14575
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
14511
14576
  * condition fields it does not know when a rule is saved from the phone.
14512
14577
  * Publishing an editor for a condition the app cannot round-trip is how an
14513
- * operator loses a rule's conditions by opening it — so the descriptor, the
14514
- * admin widget and the viewer mirror land together (P2 + P3), and only then
14515
- * does an audio rule become authorable.
14578
+ * operator loses a rule's conditions by opening it — so the viewer mirror
14579
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
14580
+ * follows here.
14516
14581
  */
14517
14582
  audio: NcAudioConditionSchema.optional()
14518
14583
  });
@@ -14748,6 +14813,30 @@ var NcRuleInputSchema = object({
14748
14813
  */
14749
14814
  snoozeAllowGlobal: boolean().optional(),
14750
14815
  /**
14816
+ * The snooze durations THIS rule's notification offers as buttons, in
14817
+ * minutes.
14818
+ *
14819
+ * Three states, and all three are distinct — which is exactly why this is
14820
+ * `.optional()` and never `.default()`. A Zod default does not run on the
14821
+ * addon cap path (three production failures in one day), so a schema default
14822
+ * would collapse the first two:
14823
+ *
14824
+ * | value | meaning |
14825
+ * | --- | --- |
14826
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
14827
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
14828
+ * | a list | these choices, de-duplicated and sorted, at most four |
14829
+ *
14830
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
14831
+ * three buttons in total) and a rule that spent it all on snooze choices
14832
+ * would push its own tap-through actions off the notification.
14833
+ *
14834
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
14835
+ * that arms the panel, is exempt automatically and cannot be silenced by a
14836
+ * window from anywhere (D133).
14837
+ */
14838
+ snoozeOptions: array(number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
14839
+ /**
14751
14840
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
14752
14841
  *
14753
14842
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -14847,6 +14936,7 @@ var NcConditionDescriptorSchema = object({
14847
14936
  "device",
14848
14937
  "package",
14849
14938
  "occupancy",
14939
+ "audio",
14850
14940
  "system"
14851
14941
  ]),
14852
14942
  label: string(),
@@ -14865,6 +14955,7 @@ var NcConditionDescriptorSchema = object({
14865
14955
  "crossingSelect",
14866
14956
  "polygonDraw",
14867
14957
  "occupancy",
14958
+ "audio",
14868
14959
  "deviceState",
14869
14960
  "systemEvent"
14870
14961
  ]),
@@ -15016,7 +15107,20 @@ var NcSnoozeInputSchema = object({
15016
15107
  ruleId: string().optional(),
15017
15108
  /** Required when `scope: 'device'`. */
15018
15109
  deviceId: number().int().optional(),
15019
- durationMinutes: number().int().min(1).max(1440),
15110
+ /**
15111
+ * Narrow the window to these subject classes — "the cat, not the person".
15112
+ *
15113
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
15114
+ * what every window authored before this field meant, so no persisted row
15115
+ * changes meaning and no client has to learn anything to keep working.
15116
+ *
15117
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
15118
+ * cross rules (D133): the operator points at a camera and a kind of thing,
15119
+ * not at whichever of their four rules happened to produce the notification
15120
+ * they are dismissing.
15121
+ */
15122
+ classes: array(string().min(1)).min(1).optional(),
15123
+ durationMinutes: number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
15020
15124
  /**
15021
15125
  * Silence this for EVERY recipient, not just the caller. Permission is
15022
15126
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -15041,6 +15145,10 @@ var NcSnoozeSchema = object({
15041
15145
  scope: NcSnoozeScopeSchema,
15042
15146
  ruleId: string().optional(),
15043
15147
  deviceId: number().int().optional(),
15148
+ /** Subject classes this window covers. ABSENT = every class — see
15149
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
15150
+ * no SQLite column: nothing queries a window by class. */
15151
+ classes: array(string().min(1)).min(1).optional(),
15044
15152
  startedAt: number(),
15045
15153
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
15046
15154
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -17278,6 +17386,22 @@ var detectionFpsField = {
17278
17386
  default: 10,
17279
17387
  step: 1
17280
17388
  };
17389
+ /**
17390
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
17391
+ *
17392
+ * The recheck is now on by default (a parked car is invisible to occupancy
17393
+ * rules until the stationary registry has been rebuilt by motion, which after a
17394
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
17395
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
17396
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
17397
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
17398
+ *
17399
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
17400
+ * the host validates `attachCamera` against ITS copy of this schema, so a
17401
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
17402
+ * carrying the wider bound is installed everywhere. 300 is the widest value
17403
+ * that ships with an addon deploy.
17404
+ */
17281
17405
  var occupancyRecheckSecField = {
17282
17406
  min: 0,
17283
17407
  max: 300,
@@ -17479,15 +17603,21 @@ var RunnerCameraConfigSchema = object({
17479
17603
  */
17480
17604
  onboardMotionDrivesAnalyzer: boolean().default(true),
17481
17605
  /**
17482
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
17483
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
17484
- * this is off by default because the recheck re-subscribes a detection session
17485
- * every N seconds while `watching`, a major source of pull-decoder re-dial
17486
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
17606
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
17607
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
17487
17608
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
17488
17609
  * (and only render) when this is enabled.
17489
- */
17490
- occupancyRecheckEnabled: boolean().default(false),
17610
+ *
17611
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
17612
+ * recheck re-subscribes a detection session every N seconds while `watching`
17613
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
17614
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
17615
+ * object is counted only while the stationary registry holds it, and the
17616
+ * registry rebuilds from motion, so after a restart a parked car was invisible
17617
+ * to every occupancy rule until something moved in front of it. The churn is
17618
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
17619
+ */
17620
+ occupancyRecheckEnabled: boolean().default(true),
17491
17621
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
17492
17622
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
17493
17623
  /**
@@ -30630,6 +30760,7 @@ Object.freeze({
30630
30760
  "network-access": "ingress",
30631
30761
  "smtp-provider": "email"
30632
30762
  });
30763
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
30633
30764
  new Set(["devices", "classes"]);
30634
30765
  /**
30635
30766
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
@@ -13097,11 +13097,33 @@ var NotificationFormatSchema = _enum([
13097
13097
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
13098
13098
  * renderer's icon set; "acknowledge" survives an adapter that draws it
13099
13099
  * differently.
13100
+ *
13101
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
13102
+ *
13103
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
13104
+ * the whole set onto its own renderer's vocabulary through a
13105
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
13106
+ * fallback, so adding a member here fails every adapter's build until someone
13107
+ * decides its glyph, which is the only place that decision can be made
13108
+ * honestly.
13109
+ *
13110
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
13111
+ * `disarm` straight through; iOS feeds that string to
13112
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
13113
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
13114
+ * mapping, and "the field is documented" is not "the value renders".
13115
+ *
13116
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
13117
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
13118
+ * so an addon that emits a token the running hub does not know does not lose an
13119
+ * icon — its whole `send` fails Zod validation and the notification never
13120
+ * arrives. Never emit a new token from an addon before the train carrying it.
13100
13121
  */
13101
13122
  var NotificationActionIconSchema = _enum([
13102
13123
  "acknowledge",
13103
13124
  "dismiss",
13104
13125
  "silence",
13126
+ "snooze",
13105
13127
  "view",
13106
13128
  "play",
13107
13129
  "open",
@@ -13109,9 +13131,13 @@ var NotificationActionIconSchema = _enum([
13109
13131
  "lock",
13110
13132
  "unlock",
13111
13133
  "arm",
13134
+ "arm-home",
13135
+ "arm-away",
13136
+ "arm-night",
13112
13137
  "disarm",
13113
13138
  "light",
13114
- "alert"
13139
+ "alert",
13140
+ "camera"
13115
13141
  ]);
13116
13142
  /** A single tap-through action button. */
13117
13143
  var NotificationActionSchema = object({
@@ -13127,7 +13153,23 @@ var NotificationActionSchema = object({
13127
13153
  * else — see `notification-center/action-token.ts` for what that does and
13128
13154
  * does not buy.
13129
13155
  */
13130
- destructive: boolean().optional()
13156
+ destructive: boolean().optional(),
13157
+ /**
13158
+ * How the tap should REACH the url.
13159
+ *
13160
+ * `navigate` (absent, and every button authored before this field) opens it:
13161
+ * the phone leaves the notification and shows whatever the callback returns.
13162
+ * That is right for a button whose answer the operator wants to read.
13163
+ *
13164
+ * `background` fires it as a POST and stays put. It exists for the buttons
13165
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
13166
+ * an answer to the notification, and being thrown into a browser tab to
13167
+ * confirm it costs more attention than the notification did. A backend that
13168
+ * cannot do a background call renders it as an ordinary link (the adapters
13169
+ * fall back rather than dropping the button), so this is a preference, never
13170
+ * a requirement.
13171
+ */
13172
+ mode: _enum(["navigate", "background"]).optional()
13131
13173
  });
13132
13174
  /**
13133
13175
  * The canonical notification. `body` is the only hard field (Apprise model).
@@ -13295,6 +13337,24 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
13295
13337
  targetId: string(),
13296
13338
  enabled: boolean()
13297
13339
  }), _void(), { kind: "mutation" });
13340
+ new Set([
13341
+ {
13342
+ id: "person",
13343
+ name: "Person"
13344
+ },
13345
+ {
13346
+ id: "vehicle",
13347
+ name: "Vehicle"
13348
+ },
13349
+ {
13350
+ id: "animal",
13351
+ name: "Animal"
13352
+ },
13353
+ {
13354
+ id: "package",
13355
+ name: "Package"
13356
+ }
13357
+ ].map((l) => l.id));
13298
13358
  var COCO_TO_MACRO = {
13299
13359
  mapping: {
13300
13360
  person: "person",
@@ -14092,6 +14152,8 @@ var NcSystemEventKindSchema = _enum([
14092
14152
  "alarm-triggered",
14093
14153
  "alarm-armed",
14094
14154
  "alarm-disarmed",
14155
+ "alarm-arming",
14156
+ "alarm-arm-refused",
14095
14157
  "camera-online",
14096
14158
  "camera-offline",
14097
14159
  "camera-disabled",
@@ -14128,6 +14190,9 @@ var NcSystemEventConditionSchema = object({
14128
14190
  nodeIds: array(string().min(1)).min(1).optional(),
14129
14191
  packageNames: array(string().min(1)).min(1).optional()
14130
14192
  });
14193
+ /** Hard ceiling on a window (24h). A snooze that could not expire would be an
14194
+ * outage the operator asked for once and forgot. */
14195
+ var NC_SNOOZE_MAX_MINUTES = 1440;
14131
14196
  /** Weekly schedule — OR of windows; absence on the rule = always active. */
14132
14197
  var NcScheduleSchema = object({
14133
14198
  windows: array(object({
@@ -14504,15 +14569,15 @@ var NcConditionsSchema = object({
14504
14569
  * (an `immediate` rule naming an `audio-*` class, one notification per
14505
14570
  * classified sample) stays exactly as it was for rules that already use it.
14506
14571
  *
14507
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
14508
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
14509
- * (`camstack/src/data/notification-center.ts`, guarded by
14510
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
14572
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
14573
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
14574
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
14575
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
14511
14576
  * condition fields it does not know when a rule is saved from the phone.
14512
14577
  * Publishing an editor for a condition the app cannot round-trip is how an
14513
- * operator loses a rule's conditions by opening it — so the descriptor, the
14514
- * admin widget and the viewer mirror land together (P2 + P3), and only then
14515
- * does an audio rule become authorable.
14578
+ * operator loses a rule's conditions by opening it — so the viewer mirror
14579
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
14580
+ * follows here.
14516
14581
  */
14517
14582
  audio: NcAudioConditionSchema.optional()
14518
14583
  });
@@ -14748,6 +14813,30 @@ var NcRuleInputSchema = object({
14748
14813
  */
14749
14814
  snoozeAllowGlobal: boolean().optional(),
14750
14815
  /**
14816
+ * The snooze durations THIS rule's notification offers as buttons, in
14817
+ * minutes.
14818
+ *
14819
+ * Three states, and all three are distinct — which is exactly why this is
14820
+ * `.optional()` and never `.default()`. A Zod default does not run on the
14821
+ * addon cap path (three production failures in one day), so a schema default
14822
+ * would collapse the first two:
14823
+ *
14824
+ * | value | meaning |
14825
+ * | --- | --- |
14826
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
14827
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
14828
+ * | a list | these choices, de-duplicated and sorted, at most four |
14829
+ *
14830
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
14831
+ * three buttons in total) and a rule that spent it all on snooze choices
14832
+ * would push its own tap-through actions off the notification.
14833
+ *
14834
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
14835
+ * that arms the panel, is exempt automatically and cannot be silenced by a
14836
+ * window from anywhere (D133).
14837
+ */
14838
+ snoozeOptions: array(number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
14839
+ /**
14751
14840
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
14752
14841
  *
14753
14842
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -14847,6 +14936,7 @@ var NcConditionDescriptorSchema = object({
14847
14936
  "device",
14848
14937
  "package",
14849
14938
  "occupancy",
14939
+ "audio",
14850
14940
  "system"
14851
14941
  ]),
14852
14942
  label: string(),
@@ -14865,6 +14955,7 @@ var NcConditionDescriptorSchema = object({
14865
14955
  "crossingSelect",
14866
14956
  "polygonDraw",
14867
14957
  "occupancy",
14958
+ "audio",
14868
14959
  "deviceState",
14869
14960
  "systemEvent"
14870
14961
  ]),
@@ -15016,7 +15107,20 @@ var NcSnoozeInputSchema = object({
15016
15107
  ruleId: string().optional(),
15017
15108
  /** Required when `scope: 'device'`. */
15018
15109
  deviceId: number().int().optional(),
15019
- durationMinutes: number().int().min(1).max(1440),
15110
+ /**
15111
+ * Narrow the window to these subject classes — "the cat, not the person".
15112
+ *
15113
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
15114
+ * what every window authored before this field meant, so no persisted row
15115
+ * changes meaning and no client has to learn anything to keep working.
15116
+ *
15117
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
15118
+ * cross rules (D133): the operator points at a camera and a kind of thing,
15119
+ * not at whichever of their four rules happened to produce the notification
15120
+ * they are dismissing.
15121
+ */
15122
+ classes: array(string().min(1)).min(1).optional(),
15123
+ durationMinutes: number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
15020
15124
  /**
15021
15125
  * Silence this for EVERY recipient, not just the caller. Permission is
15022
15126
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -15041,6 +15145,10 @@ var NcSnoozeSchema = object({
15041
15145
  scope: NcSnoozeScopeSchema,
15042
15146
  ruleId: string().optional(),
15043
15147
  deviceId: number().int().optional(),
15148
+ /** Subject classes this window covers. ABSENT = every class — see
15149
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
15150
+ * no SQLite column: nothing queries a window by class. */
15151
+ classes: array(string().min(1)).min(1).optional(),
15044
15152
  startedAt: number(),
15045
15153
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
15046
15154
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -17278,6 +17386,22 @@ var detectionFpsField = {
17278
17386
  default: 10,
17279
17387
  step: 1
17280
17388
  };
17389
+ /**
17390
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
17391
+ *
17392
+ * The recheck is now on by default (a parked car is invisible to occupancy
17393
+ * rules until the stationary registry has been rebuilt by motion, which after a
17394
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
17395
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
17396
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
17397
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
17398
+ *
17399
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
17400
+ * the host validates `attachCamera` against ITS copy of this schema, so a
17401
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
17402
+ * carrying the wider bound is installed everywhere. 300 is the widest value
17403
+ * that ships with an addon deploy.
17404
+ */
17281
17405
  var occupancyRecheckSecField = {
17282
17406
  min: 0,
17283
17407
  max: 300,
@@ -17479,15 +17603,21 @@ var RunnerCameraConfigSchema = object({
17479
17603
  */
17480
17604
  onboardMotionDrivesAnalyzer: boolean().default(true),
17481
17605
  /**
17482
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
17483
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
17484
- * this is off by default because the recheck re-subscribes a detection session
17485
- * every N seconds while `watching`, a major source of pull-decoder re-dial
17486
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
17606
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
17607
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
17487
17608
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
17488
17609
  * (and only render) when this is enabled.
17489
- */
17490
- occupancyRecheckEnabled: boolean().default(false),
17610
+ *
17611
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
17612
+ * recheck re-subscribes a detection session every N seconds while `watching`
17613
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
17614
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
17615
+ * object is counted only while the stationary registry holds it, and the
17616
+ * registry rebuilds from motion, so after a restart a parked car was invisible
17617
+ * to every occupancy rule until something moved in front of it. The churn is
17618
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
17619
+ */
17620
+ occupancyRecheckEnabled: boolean().default(true),
17491
17621
  occupancyRecheckSec: number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
17492
17622
  occupancyRecheckFrames: number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
17493
17623
  /**
@@ -30630,6 +30760,7 @@ Object.freeze({
30630
30760
  "network-access": "ingress",
30631
30761
  "smtp-provider": "email"
30632
30762
  });
30763
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
30633
30764
  new Set(["devices", "classes"]);
30634
30765
  /**
30635
30766
  * TimelapseRule — the STANDALONE scheduled timelapse producer's rule model.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../chunk-Cek0wNdY.js");
6
- const require_dist = require("../dist-jy0Ghg8b.js");
6
+ const require_dist = require("../dist-Tng9asvg.js");
7
7
  //#region src/magic-link/auth-magic-link.addon.ts
8
8
  /**
9
9
  * Magic-link authentication addon.
@@ -1,4 +1,4 @@
1
- import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-SWl70qVs.mjs";
1
+ import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-RQZEyLZg.mjs";
2
2
  //#region src/magic-link/auth-magic-link.addon.ts
3
3
  /**
4
4
  * Magic-link authentication addon.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-Cek0wNdY.js");
6
- const require_dist = require("../dist-jy0Ghg8b.js");
6
+ const require_dist = require("../dist-Tng9asvg.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  //#region node_modules/jose/dist/webapi/lib/buffer_utils.js
@@ -1,4 +1,4 @@
1
- import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-SWl70qVs.mjs";
1
+ import { a as loginMethodCapability, c as BaseAddon, i as buildAddonRouteProvider, r as authProviderCapability, s as errMsg, t as addonRoutesCapability } from "../dist-RQZEyLZg.mjs";
2
2
  import * as crypto$1 from "node:crypto";
3
3
  //#region node_modules/jose/dist/webapi/lib/buffer_utils.js
4
4
  var encoder = new TextEncoder();
@@ -3,7 +3,7 @@ import "./dist-CYZr2fwk.mjs";
3
3
  var e = {
4
4
  "@camstack/sdk": {
5
5
  name: "@camstack/sdk",
6
- version: "1.2.16",
6
+ version: "1.2.18",
7
7
  scope: ["default"],
8
8
  loaded: !1,
9
9
  from: "addon_auth_webauthn_widgets",
@@ -18,7 +18,7 @@ var e = {
18
18
  },
19
19
  "@camstack/types": {
20
20
  name: "@camstack/types",
21
- version: "1.2.65",
21
+ version: "1.2.67",
22
22
  scope: ["default"],
23
23
  loaded: !1,
24
24
  from: "addon_auth_webauthn_widgets",
@@ -33,7 +33,7 @@ var e = {
33
33
  },
34
34
  "@camstack/ui-library": {
35
35
  name: "@camstack/ui-library",
36
- version: "1.2.44",
36
+ version: "1.2.46",
37
37
  scope: ["default"],
38
38
  loaded: !1,
39
39
  from: "addon_auth_webauthn_widgets",
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../chunk-Cek0wNdY.js");
6
- const require_dist = require("../dist-jy0Ghg8b.js");
6
+ const require_dist = require("../dist-Tng9asvg.js");
7
7
  let stream = require("stream");
8
8
  stream = require_chunk.__toESM(stream, 1);
9
9
  let http = require("http");
@@ -1,4 +1,4 @@
1
- import { a as loginMethodCapability, c as BaseAddon, d as number, f as object, l as array, n as addonWidgetsSourceCapability, o as userPasskeysCapability, p as string, u as boolean } from "../dist-SWl70qVs.mjs";
1
+ import { a as loginMethodCapability, c as BaseAddon, d as number, f as object, l as array, n as addonWidgetsSourceCapability, o as userPasskeysCapability, p as string, u as boolean } from "../dist-RQZEyLZg.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import Stream from "stream";
4
4
  import http from "http";
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.65",
39
+ version: "1.2.67",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -45,7 +45,7 @@ async function r() {
45
45
  }
46
46
  },
47
47
  "@camstack/sdk": {
48
- version: "1.2.16",
48
+ version: "1.2.18",
49
49
  scope: "default",
50
50
  shareConfig: {
51
51
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.44",
84
+ version: "1.2.46",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
@@ -30,7 +30,7 @@ async function d(e) {
30
30
  }
31
31
  }
32
32
  async function f() {
33
- return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-B08tVFd1.mjs")).catch((e) => {
33
+ return l ||= d(() => import("./_virtual_mf-localSharedImportMap___mfe_internal__addon_auth_webauthn_widgets-DicoXrsH.mjs")).catch((e) => {
34
34
  throw l = void 0, e;
35
35
  }), l;
36
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-auth",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "description": "Authentication bundle — magic-link, OIDC, and WebAuthn/passkey. Multi-entry npm package shipping 3 addons under a single bundle; each addon keeps its own id, capabilities, and runner.",
5
5
  "keywords": [
6
6
  "camstack",