@camstack/addon-admin-ui 1.2.26 → 1.2.28

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 (105) hide show
  1. package/dist/assets/{AddLocationWizard-Cag16oOP.js → AddLocationWizard-C4cgS_sV.js} +1 -1
  2. package/dist/assets/{AddonCollectionPage-DGDkI-LS.js → AddonCollectionPage-CkA07HR3.js} +1 -1
  3. package/dist/assets/Addons-YnIxjSpR.js +1 -0
  4. package/dist/assets/{AdminTabs-NQAUqr0e.js → AdminTabs-dOwKLlYn.js} +1 -1
  5. package/dist/assets/{AiProfiles-DoLNbJPh.js → AiProfiles-DF-QzpSJ.js} +1 -1
  6. package/dist/assets/Automation-uUiVBTXW.js +29 -0
  7. package/dist/assets/{BrokerForm-ugwa3BSK.js → BrokerForm-C1dAxSMZ.js} +1 -1
  8. package/dist/assets/Capabilities-Bi9PPtcB.js +1 -0
  9. package/dist/assets/CapabilityBadges-DUcgEEu0.js +1 -0
  10. package/dist/assets/{Cluster-CJvEjOoL.js → Cluster-BR6gzpxe.js} +1 -1
  11. package/dist/assets/{ClusterRecordingTab-Byq8Qou-.js → ClusterRecordingTab-FfLNKnbk.js} +1 -1
  12. package/dist/assets/{Dashboard-k_bHtEea.js → Dashboard-B81TEt3W.js} +1 -1
  13. package/dist/assets/Data-CEIl23U0.js +1 -0
  14. package/dist/assets/DetectionIntelligence-D5jycjTb.js +1 -0
  15. package/dist/assets/{DeviceAggregateSection-BfxGhWeq.js → DeviceAggregateSection-Db2HD40T.js} +1 -1
  16. package/dist/assets/{DeviceDetail-ByptkOHJ.js → DeviceDetail-BQV0LZy9.js} +2 -2
  17. package/dist/assets/{Devices-BFCfTAfE.js → Devices-BM7TfmP9.js} +1 -1
  18. package/dist/assets/{EmbedPlayerPage-DaJCWQUp.js → EmbedPlayerPage-WqPjW6_I.js} +1 -1
  19. package/dist/assets/Events-DrUrl0Sb.js +1 -0
  20. package/dist/assets/{FormBuilder-C-yJfjVz.js → FormBuilder-Ck1OM5tg.js} +1 -1
  21. package/dist/assets/Identity-CgsfyZA4.js +1 -0
  22. package/dist/assets/IntegrationDetail-DuTA5P2l.js +1 -0
  23. package/dist/assets/{Integrations-DRgCm09e.js → Integrations-Ath993ok.js} +1 -1
  24. package/dist/assets/{Integrations-ub2WvYtH.js → Integrations-BmDA6vO2.js} +1 -1
  25. package/dist/assets/{Logs-DbfWDfqh.js → Logs-DelaeBuD.js} +1 -1
  26. package/dist/assets/{MotionZonesSettings-DMkPYwfm.js → MotionZonesSettings-DMFemsdS.js} +1 -1
  27. package/dist/assets/{MyAccess-DXBVxHNA.js → MyAccess-CM9UYXLz.js} +1 -1
  28. package/dist/assets/Network-BC_HGROR.js +1 -0
  29. package/dist/assets/{NodeAddonsSettingsPanel-BhixaPn8.js → NodeAddonsSettingsPanel-C4gs_yPA.js} +1 -1
  30. package/dist/assets/NodeInferenceDevices-CPvS2MmJ.js +1 -0
  31. package/dist/assets/Notifications-jmm9B_2b.js +1 -0
  32. package/dist/assets/Pipeline-D3rvH4lf.js +1 -0
  33. package/dist/assets/{Plates-BtyczmJ5.js → Plates-Dr-ZMJeO.js} +1 -1
  34. package/dist/assets/{PrivacyMaskSettings-D5_vzIkB.js → PrivacyMaskSettings-CRu5EIoG.js} +1 -1
  35. package/dist/assets/{ProviderIcon-DMKVAJ5a.js → ProviderIcon-CBS0BKDo.js} +1 -1
  36. package/dist/assets/Recordings-C06Ft_Gu.js +1 -0
  37. package/dist/assets/ScanNetworkModal-CT2aBbLl.js +1 -0
  38. package/dist/assets/{Settings-C3UcxXQS.js → Settings-vLlutyMg.js} +1 -1
  39. package/dist/assets/{Showroom-C3DCipkF.js → Showroom-BJZjnijB.js} +1 -1
  40. package/dist/assets/{Terminal-B_WkWKes.js → Terminal-C2F6wtQm.js} +2 -2
  41. package/dist/assets/_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-BRYQboI0.js +1 -0
  42. package/dist/assets/{build-cluster-graph-BmbI20Mf.js → build-cluster-graph-GVtnSlPN.js} +2 -2
  43. package/dist/assets/circle-check-big-FKRqDJa3.js +1 -0
  44. package/dist/assets/clock-CFnxh3Lc.js +1 -0
  45. package/dist/assets/{copy-DBW0xhoa.js → copy-DLnm7qG9.js} +1 -1
  46. package/dist/assets/{debounced-invalidate-DUEoR3Uw.js → debounced-invalidate-B0iO1xWv.js} +1 -1
  47. package/dist/assets/{dist-DvvMnEMz.js → dist-ftkxgZkp.js} +1 -1
  48. package/dist/assets/dist-nmKwdp_o.js +7 -0
  49. package/dist/assets/{download-Bio_dAdJ.js → download-Cg3DhK3V.js} +1 -1
  50. package/dist/assets/{face-bbox-p3f8zVs-.js → face-bbox-BSL6Ue8E.js} +1 -1
  51. package/dist/assets/hostInit-BM_83ApD.js +1 -0
  52. package/dist/assets/index-BHm1FmD-.css +2 -0
  53. package/dist/assets/index-eKdCK3OK.js +4 -0
  54. package/dist/assets/{key-round-WmM4HaJB.js → key-round-D-RvYsRs.js} +1 -1
  55. package/dist/assets/{mf-entry-bootstrap-0-e003dc9c.js → mf-entry-bootstrap-0-f13bc78f.js} +2 -2
  56. package/dist/assets/{pencil-D5L2sDXQ.js → pencil-CLzB_MES.js} +1 -1
  57. package/dist/assets/{player-overlays-BBqyPKM2.js → player-overlays-hk9wHsGU.js} +1 -1
  58. package/dist/assets/plus-CJJnnB7X.js +1 -0
  59. package/dist/assets/{radio-CxxbN6A6.js → radio-DGwPjS9G.js} +1 -1
  60. package/dist/assets/{refresh-cw-DOD5m7xj.js → refresh-cw-BC6uIqpo.js} +1 -1
  61. package/dist/assets/remoteEntry-BVUCQx6J.js +1 -0
  62. package/dist/assets/{rotate-ccw-DvFrN7DE.js → rotate-ccw-DF8Y4TIr.js} +1 -1
  63. package/dist/assets/rotate-cw-BVJGeMUG.js +1 -0
  64. package/dist/assets/search-_keVsHDh.js +1 -0
  65. package/dist/assets/{send-aYVyvUaE.js → send-BHmfSote.js} +1 -1
  66. package/dist/assets/{server-Dry2EPJh.js → server-B9iE-8Fk.js} +1 -1
  67. package/dist/assets/square-BTjaA9DN.js +1 -0
  68. package/dist/assets/{square-check-big-bsZZ4Xal.js → square-check-big-Dtv7UruI.js} +1 -1
  69. package/dist/assets/{src-BWAp3lG2.js → src-Ch-eL-Nn.js} +10 -10
  70. package/dist/assets/src-u_aT9_UX.js +7 -0
  71. package/dist/assets/{upload-CoX0nC7y.js → upload-B6ASOz4s.js} +1 -1
  72. package/dist/assets/{useEventInvalidation-DXe161Bu.js → useEventInvalidation-BnpkWD9m.js} +1 -1
  73. package/dist/assets/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-Cbj5rBDu.js → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__admin_ui_host__remoteEntry-_hash_-CFOuJd11.js} +2 -2
  74. package/dist/assets/{wifi-D43tdlQ_.js → wifi-D2NPRxIr.js} +1 -1
  75. package/dist/assets/{zap-D_Y2ZMPu.js → zap-CYhChCGl.js} +1 -1
  76. package/dist/index.html +6 -6
  77. package/dist/server/addon.js +374 -236
  78. package/package.json +1 -1
  79. package/dist/assets/Addons-BdC85lG-.js +0 -1
  80. package/dist/assets/Capabilities-BTEadEUd.js +0 -1
  81. package/dist/assets/CapabilityBadges-6ZPTx-s-.js +0 -1
  82. package/dist/assets/Data-DYHpaMmM.js +0 -1
  83. package/dist/assets/DetectionIntelligence-BU2unG_U.js +0 -1
  84. package/dist/assets/Events-DMF9VE7a.js +0 -1
  85. package/dist/assets/Identity-Cng3B1PY.js +0 -1
  86. package/dist/assets/IntegrationDetail-BOnUmIbS.js +0 -1
  87. package/dist/assets/Network-BwdIu8XY.js +0 -1
  88. package/dist/assets/NodeInferenceDevices-2J3V9zlr.js +0 -1
  89. package/dist/assets/Notifications-CvUp0BCC.js +0 -1
  90. package/dist/assets/Pipeline-r3w3U7gx.js +0 -1
  91. package/dist/assets/Recordings-f5pTShxs.js +0 -1
  92. package/dist/assets/ScanNetworkModal-DbtTmTyR.js +0 -1
  93. package/dist/assets/_virtual_mf-localSharedImportMap___mfe_internal__admin_ui_host-ZmJ_mFoL.js +0 -1
  94. package/dist/assets/circle-check-big-BoUy_LWE.js +0 -1
  95. package/dist/assets/clock-CwAedBOG.js +0 -1
  96. package/dist/assets/dist-tJNbI9ls.js +0 -7
  97. package/dist/assets/hostInit-CqMMdrHd.js +0 -1
  98. package/dist/assets/index-Ck97SVdI.js +0 -4
  99. package/dist/assets/index-DvpMTqDC.css +0 -2
  100. package/dist/assets/plus-C5eJEpBX.js +0 -1
  101. package/dist/assets/remoteEntry-BcsGH14_.js +0 -1
  102. package/dist/assets/rotate-cw-BKu7Xohu.js +0 -1
  103. package/dist/assets/search-Cgpv3f3a.js +0 -1
  104. package/dist/assets/square-C6ZWC_Kv.js +0 -1
  105. package/dist/assets/src-CSJZQK3p.js +0 -7
@@ -8995,6 +8995,276 @@ var MaskGridDimsSchema = object({
8995
8995
  height: number()
8996
8996
  });
8997
8997
  /**
8998
+ * notification-output — canonical, capability-gated notification delivery.
8999
+ *
9000
+ * Apprise-derived model (see
9001
+ * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
9002
+ * callers emit ONE canonical `Notification`; each provider declares a
9003
+ * per-kind capability descriptor (`TargetKind`), and the pure degrade
9004
+ * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
9005
+ * message to what the kind supports — callers never special-case a service.
9006
+ *
9007
+ * DESIGN DECISIONS (locked):
9008
+ * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
9009
+ * `setTargetEnabled`), each provider persisting via the `settings-store`
9010
+ * cap. Rationale: the admin UI needs one uniform surface across the
9011
+ * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
9012
+ * alternative would fork the UI per addon and cannot host the
9013
+ * discovery→adopt flow.
9014
+ * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
9015
+ * the generated cap-mount auto-`concatCollection`-fans them across every
9016
+ * registered provider (notifiers addon + HA addon) so one catalog is
9017
+ * routable. `send` / `testTarget` / CRUD route to ONE provider by the
9018
+ * `addonId` the generated collection router extracts from the call input.
9019
+ * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
9020
+ * transformer) + UDS MsgPack both round-trip typed arrays — already used by
9021
+ * `storage` / `storage-provider` / `recording` caps over the same path. No
9022
+ * base64 fallback needed.
9023
+ *
9024
+ * TODO (deferred, closed-set change — separate decision): add
9025
+ * `providerKind: 'notify'` so notification providers surface on the unified
9026
+ * admin "Integrations" page.
9027
+ */
9028
+ /**
9029
+ * Zentik-derived typed-media enum — the superset across every kind. Each
9030
+ * adapter picks what it supports and the degrade engine filters the rest.
9031
+ */
9032
+ var AttachmentMediaTypeSchema = _enum([
9033
+ "image",
9034
+ "video",
9035
+ "gif",
9036
+ "audio",
9037
+ "icon"
9038
+ ]);
9039
+ /**
9040
+ * A single attachment. Exactly one of `url` (remote source, most adapters
9041
+ * prefer this) or `bytes` (inline source; required for Pushover-style
9042
+ * bytes-only kinds) MUST be present — the degrade engine expresses a
9043
+ * url→bytes fetch as a `needsFetch` directive the adapter executes.
9044
+ */
9045
+ var AttachmentSchema = object({
9046
+ mediaType: AttachmentMediaTypeSchema,
9047
+ url: string().optional(),
9048
+ bytes: _instanceof(Uint8Array).optional(),
9049
+ mime: string().optional(),
9050
+ name: string().optional()
9051
+ }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
9052
+ var NotificationFormatSchema = _enum([
9053
+ "text",
9054
+ "markdown",
9055
+ "html"
9056
+ ]);
9057
+ /**
9058
+ * The CLOSED icon vocabulary an action button may use.
9059
+ *
9060
+ * A closed set, not a free string, and that is the whole point: an arbitrary
9061
+ * icon name is one that ntfy renders, zentik silently drops, and nobody
9062
+ * notices — the same class of gap as a zone vocabulary nothing produced
9063
+ * ([D35](../../../docs/decisions/adr-0035.md)). Every adapter maps this set or
9064
+ * declares `actionIcons: false` and the degrade engine strips the field.
9065
+ *
9066
+ * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
9067
+ * renderer's icon set; "acknowledge" survives an adapter that draws it
9068
+ * differently.
9069
+ */
9070
+ var NotificationActionIconSchema = _enum([
9071
+ "acknowledge",
9072
+ "dismiss",
9073
+ "silence",
9074
+ "view",
9075
+ "play",
9076
+ "open",
9077
+ "close",
9078
+ "lock",
9079
+ "unlock",
9080
+ "arm",
9081
+ "disarm",
9082
+ "light",
9083
+ "alert"
9084
+ ]);
9085
+ /** A single tap-through action button. */
9086
+ var NotificationActionSchema = object({
9087
+ id: string(),
9088
+ label: string(),
9089
+ url: string().optional(),
9090
+ /** Dropped by the degrade engine for a kind with `caps.actionIcons: false`. */
9091
+ icon: NotificationActionIconSchema.optional(),
9092
+ /**
9093
+ * Renders in a warning style where the notifier supports it.
9094
+ *
9095
+ * A HINT, never a gate. The callback's authority is its token and nothing
9096
+ * else — see `notification-center/action-token.ts` for what that does and
9097
+ * does not buy.
9098
+ */
9099
+ destructive: boolean().optional()
9100
+ });
9101
+ /**
9102
+ * The canonical notification. `body` is the only hard field (Apprise model).
9103
+ * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
9104
+ * NOT a fixed severity enum — each kind declares its own `caps.levels` and
9105
+ * the adapter maps this ordinal onto its native level. `level?` is an
9106
+ * optional kind-native level id (`emergency`, `silent`, …) that overrides
9107
+ * `priority` for that one target.
9108
+ */
9109
+ var NotificationSchema = object({
9110
+ body: string(),
9111
+ title: string().optional(),
9112
+ format: NotificationFormatSchema.default("text"),
9113
+ priority: number().int().min(1).max(5).default(3),
9114
+ level: string().optional(),
9115
+ attachments: array(AttachmentSchema).optional(),
9116
+ clickUrl: string().optional(),
9117
+ actions: array(NotificationActionSchema).optional(),
9118
+ sound: string().optional(),
9119
+ ttl: number().optional(),
9120
+ tag: string().optional(),
9121
+ deviceId: number().optional(),
9122
+ eventId: string().optional(),
9123
+ metadata: record(string(), unknown()).optional()
9124
+ });
9125
+ /** One declared native severity/priority level for a kind. */
9126
+ var TargetKindLevelSchema = object({
9127
+ id: string(),
9128
+ label: string(),
9129
+ /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
9130
+ ordinal: number().int().min(1).max(5).nullable(),
9131
+ flags: object({
9132
+ critical: boolean().optional(),
9133
+ silent: boolean().optional(),
9134
+ noPush: boolean().optional()
9135
+ }).optional(),
9136
+ /** e.g. Pushover `emergency` requires `retry` / `expire`. */
9137
+ requires: array(string()).optional(),
9138
+ description: string().optional()
9139
+ });
9140
+ /** The full capability block consulted before dispatch. */
9141
+ var TargetKindCapsSchema = object({
9142
+ attachments: object({
9143
+ mediaTypes: array(AttachmentMediaTypeSchema),
9144
+ mode: _enum([
9145
+ "url",
9146
+ "bytes",
9147
+ "both"
9148
+ ]),
9149
+ max: number().int().nonnegative(),
9150
+ maxBytes: number().int().positive().optional()
9151
+ }),
9152
+ /** Max action buttons (0 = none). */
9153
+ actions: number().int().nonnegative(),
9154
+ /**
9155
+ * Whether this kind renders a per-action ICON.
9156
+ *
9157
+ * `.optional()`, deliberately NOT `.default(false)`: a Zod default does not
9158
+ * run on the addon cap path — three production failures in one day taught
9159
+ * this repo that once. Absent is read as false by the degrade engine, which
9160
+ * is the safe direction: an icon that is not rendered costs nothing, an icon
9161
+ * assumed and dropped costs the operator's trust in the field.
9162
+ */
9163
+ actionIcons: boolean().optional(),
9164
+ levels: array(TargetKindLevelSchema),
9165
+ format: array(NotificationFormatSchema),
9166
+ clickUrl: boolean(),
9167
+ sound: boolean(),
9168
+ ttl: boolean(),
9169
+ bodyMaxLen: number().int().positive()
9170
+ });
9171
+ /**
9172
+ * `configSchema` is a `ConfigUISchema` tree passed through to the admin
9173
+ * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
9174
+ * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
9175
+ * the union is large and not meant for runtime validation here; the exported
9176
+ * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
9177
+ */
9178
+ var ConfigSchemaPassthrough$1 = unknown();
9179
+ var TargetKindSchema = object({
9180
+ kind: string(),
9181
+ label: string(),
9182
+ icon: string(),
9183
+ /** Stamped by each provider so the concat-fanned catalog stays routable. */
9184
+ addonId: string(),
9185
+ /**
9186
+ * URL of the kind's bundled BRAND icon, served by the providing addon over
9187
+ * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
9188
+ * when the addon bundles no icon for that kind — the client then falls back
9189
+ * to a neutral glyph rather than rendering the raw `icon` NAME as text.
9190
+ *
9191
+ * Root-relative on purpose: it resolves against whatever origin serves a web
9192
+ * client, and a native client joins it onto its own hub base.
9193
+ *
9194
+ * DECLARED here deliberately. It used to travel as an undeclared passthrough
9195
+ * field that survived only because the runtime cap-router forwards provider
9196
+ * output verbatim — so every consumer had to re-declare it by hand to stop
9197
+ * its own Zod parse from stripping it, and the whole arrangement would have
9198
+ * broken silently the moment output validation was tightened anywhere.
9199
+ */
9200
+ iconUrl: string().optional(),
9201
+ /**
9202
+ * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
9203
+ *
9204
+ * The server knows this and therefore says it, because the client cannot
9205
+ * safely guess: a React-Native client renders SVG and raster through two
9206
+ * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
9207
+ * not decode SVG on iOS/Android), so without this it silently fell back to a
9208
+ * placeholder glyph for every vector icon while the web build looked fine.
9209
+ *
9210
+ * Absent when {@link iconUrl} is absent, or for a legacy provider that has
9211
+ * not been updated — a client that cannot determine the type should prefer
9212
+ * its raster path, which is the safe default for an unknown image.
9213
+ */
9214
+ iconMediaType: string().optional(),
9215
+ configSchema: ConfigSchemaPassthrough$1,
9216
+ supportsDiscovery: boolean(),
9217
+ caps: TargetKindCapsSchema
9218
+ });
9219
+ /**
9220
+ * A persisted target. `config` holds secrets; providers REDACT secret fields
9221
+ * (return a presence marker only) when serving `listTargets` — never
9222
+ * round-trip a stored secret to the UI.
9223
+ */
9224
+ var TargetSchema = object({
9225
+ id: string(),
9226
+ name: string(),
9227
+ kind: string(),
9228
+ addonId: string(),
9229
+ enabled: boolean(),
9230
+ config: record(string(), unknown())
9231
+ });
9232
+ /** A discovery-surfaced candidate (config is partial + non-secret). */
9233
+ var DiscoveredTargetSchema = object({
9234
+ kind: string(),
9235
+ suggestedName: string(),
9236
+ config: record(string(), unknown())
9237
+ });
9238
+ /** The degrade engine's report — what was resolved / dropped / degraded. */
9239
+ var RenderedAsSchema = object({
9240
+ level: string(),
9241
+ format: NotificationFormatSchema,
9242
+ attachmentsSent: number().int().nonnegative(),
9243
+ actionsSent: number().int().nonnegative(),
9244
+ truncated: boolean(),
9245
+ dropped: array(string())
9246
+ });
9247
+ var SendResultSchema = object({
9248
+ success: boolean(),
9249
+ error: string().optional(),
9250
+ renderedAs: RenderedAsSchema.optional()
9251
+ });
9252
+ /** Same shape as SendResult — kept as a distinct name for the test panel. */
9253
+ var TestResultSchema = SendResultSchema;
9254
+ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
9255
+ kind: string(),
9256
+ config: record(string(), unknown()).optional()
9257
+ }), array(DiscoveredTargetSchema)), method(object({
9258
+ targetId: string(),
9259
+ notification: NotificationSchema
9260
+ }), SendResultSchema, { kind: "mutation" }), method(object({
9261
+ targetId: string(),
9262
+ sample: NotificationSchema.optional()
9263
+ }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
9264
+ targetId: string(),
9265
+ enabled: boolean()
9266
+ }), _void(), { kind: "mutation" });
9267
+ /**
8998
9268
  * notification-rules — the Notification Center rule surface (P1 core).
8999
9269
  *
9000
9270
  * Spec: `docs/superpowers/specs/2026-07-22-notification-center-requirements.md`
@@ -9164,6 +9434,41 @@ var NcRuleActionSchema = discriminatedUnion("kind", [object({
9164
9434
  args: record(string(), unknown()).optional()
9165
9435
  })]);
9166
9436
  /**
9437
+ * A named, ordered run of steps with its own throttle.
9438
+ *
9439
+ * `minDelaySec` exists because a noisy rule otherwise hammers a physical
9440
+ * actuator — the rule's own cooldown governs NOTIFICATIONS, which is a
9441
+ * different budget from "how often may this gate actually open".
9442
+ */
9443
+ var NcRuleActionSequenceSchema = object({
9444
+ name: string().min(1).max(120),
9445
+ enabled: boolean(),
9446
+ minDelaySec: number().int().min(0).max(86400).optional(),
9447
+ actions: array(NcRuleActionSchema).min(1)
9448
+ });
9449
+ /**
9450
+ * One button carried by the notification, running a named sequence on tap.
9451
+ *
9452
+ * **Read this before adding a button that does something physical.** The tap
9453
+ * arrives over a link that travelled through third-party infrastructure — ntfy,
9454
+ * a push relay, whatever forwarded the message — and the callback's ONLY
9455
+ * authority is the token in that link: single-use, short-lived, bound to this
9456
+ * one action of this one notification. It does not identify who tapped.
9457
+ * Whoever holds the notification can run the button, once, inside the window.
9458
+ * That is the operator's explicit choice (2026-08-05), and `destructive` is a
9459
+ * rendering hint, not a second gate. [D47](decisions/adr-0047.md).
9460
+ */
9461
+ var NcRuleNotificationButtonSchema = object({
9462
+ /** Stable id — travels in the callback and identifies the button in logs. */
9463
+ id: string().min(1).max(64),
9464
+ label: string().min(1).max(40),
9465
+ /** Name of a sequence in `onTrigger`. The dispatcher drops a button whose
9466
+ * sequence does not exist rather than minting a token for nothing. */
9467
+ sequence: string().min(1).max(120),
9468
+ icon: NotificationActionIconSchema.optional(),
9469
+ destructive: boolean().optional()
9470
+ });
9471
+ /**
9167
9472
  * Sequences a rule runs, by hook point.
9168
9473
  *
9169
9474
  * ONLY `onTrigger` is here, deliberately. The reference also has activation /
@@ -9171,14 +9476,26 @@ var NcRuleActionSchema = discriminatedUnion("kind", [object({
9171
9476
  * repo's expensive failure mode is declaring a surface nothing produces, so a
9172
9477
  * hook appears here in the same change that produces its edge, never before.
9173
9478
  */
9174
- var NcRuleActionsSchema = object({
9175
- /** Runs when the rule MATCHES. */
9176
- onTrigger: array(object({
9177
- name: string().min(1).max(120),
9178
- enabled: boolean(),
9179
- minDelaySec: number().int().min(0).max(86400).optional(),
9180
- actions: array(NcRuleActionSchema).min(1)
9181
- })).optional() });
9479
+ var NcRuleActionsSchema = object({
9480
+ /** Runs when the rule MATCHES. */
9481
+ onTrigger: array(NcRuleActionSequenceSchema).optional(),
9482
+ /**
9483
+ * Buttons the NOTIFICATION carries, each running one of this rule's
9484
+ * sequences when tapped.
9485
+ *
9486
+ * Deliberately a REFERENCE to a sequence rather than a second place to
9487
+ * author steps. A button that could define its own actions would be a
9488
+ * parallel actuation vocabulary — the executor's device-scope check, the
9489
+ * stop-at-first-failure rule and the per-sequence throttle all live on
9490
+ * sequences, and a second authoring surface would drift from every one of
9491
+ * them.
9492
+ *
9493
+ * A sequence reachable ONLY by a button simply appears in `onTrigger` with
9494
+ * `enabled: false`: it is then authored, throttled and validated like the
9495
+ * rest, and nothing runs it automatically.
9496
+ */
9497
+ buttons: array(NcRuleNotificationButtonSchema).max(8).optional()
9498
+ });
9182
9499
  var NcConditionsSchema = object({
9183
9500
  /** Gate on ANOTHER device's current state (the alarm armed, a switch on). */
9184
9501
  deviceState: object({
@@ -16166,7 +16483,20 @@ method(object({
16166
16483
  }), _void(), {
16167
16484
  kind: "mutation",
16168
16485
  auth: "admin"
16169
- }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({ addonId: string().optional() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
16486
+ }), method(object({ addonId: string() }), array(SavedDeviceRowSchema)), method(object({
16487
+ addonId: string().optional(),
16488
+ /**
16489
+ * `slim` omits `config` (returned `{}`), `metadata` (null) and the
16490
+ * `sourceInfo` derived from config — and skips the per-device settings
16491
+ * read that produces them. Everything identifying a device (id, name,
16492
+ * type, online, features, isCamera, parent/link ids) is unchanged.
16493
+ * Do not use it for dispatch routing, which needs `sourceInfo`.
16494
+ */
16495
+ projection: _enum(["full", "slim"]).optional(),
16496
+ /** Return only camera devices. Filtering server-side instead of
16497
+ * shipping 293 rows to find 12. */
16498
+ isCamera: boolean().optional()
16499
+ }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), DeviceInfoSchema.nullable()), method(object({ parentDeviceId: number() }), array(DeviceInfoSchema)), method(object({ deviceId: number() }), object({
16170
16500
  mode: DeviceLinkModeSchema,
16171
16501
  devices: array(LinkedDeviceSchema)
16172
16502
  })), method(object({ deviceId: number() }), array(StreamSourceEntrySchema$1)), method(object({ deviceId: number() }), array(ConfigEntrySchema)), method(object({ deviceId: number() }), ConfigUISchemaOutput), method(object({
@@ -16605,14 +16935,14 @@ var LlmProfileSchema = object({
16605
16935
  /** ConfigUISchema tree passed through untyped on the wire (the
16606
16936
  * notification-output `ConfigSchemaPassthrough` precedent at
16607
16937
  * notification-output.cap.ts:151); the exported TS type re-tightens it. */
16608
- var ConfigSchemaPassthrough$1 = unknown();
16938
+ var ConfigSchemaPassthrough = unknown();
16609
16939
  var LlmProfileKindDescriptorSchema = object({
16610
16940
  kind: LlmProfileKindSchema,
16611
16941
  label: string(),
16612
16942
  icon: string(),
16613
16943
  /** Stamped by each provider so the concat-fanned catalog stays routable. */
16614
16944
  addonId: string(),
16615
- configSchema: ConfigSchemaPassthrough$1
16945
+ configSchema: ConfigSchemaPassthrough
16616
16946
  });
16617
16947
  var LlmDefaultSelectorSchema = union([object({ consumer: string() }), object({ purpose: _enum(["text", "vision"]) })]);
16618
16948
  var LlmDefaultSchema = object({
@@ -17143,228 +17473,6 @@ var NetworkEndpointEntrySchema = NetworkEndpointSchema.extend({
17143
17473
  });
17144
17474
  method(_void(), NetworkEndpointSchema, { kind: "mutation" }), method(_void(), _void(), { kind: "mutation" }), method(_void(), NetworkEndpointSchema.nullable()), method(_void(), NetworkAccessStatusSchema), method(_void(), array(NetworkEndpointEntrySchema).readonly());
17145
17475
  /**
17146
- * notification-output — canonical, capability-gated notification delivery.
17147
- *
17148
- * Apprise-derived model (see
17149
- * `docs/superpowers/specs/2026-07-03-notification-output-notifier-matrix.md`):
17150
- * callers emit ONE canonical `Notification`; each provider declares a
17151
- * per-kind capability descriptor (`TargetKind`), and the pure degrade
17152
- * engine (`@camstack/types` `prepareNotification`) transcodes / degrades the
17153
- * message to what the kind supports — callers never special-case a service.
17154
- *
17155
- * DESIGN DECISIONS (locked):
17156
- * - Target CRUD lives on THIS cap (`upsertTarget` / `deleteTarget` /
17157
- * `setTargetEnabled`), each provider persisting via the `settings-store`
17158
- * cap. Rationale: the admin UI needs one uniform surface across the
17159
- * notifiers addon AND the HA addon; the addon-`globalSettingsSchema`-array
17160
- * alternative would fork the UI per addon and cannot host the
17161
- * discovery→adopt flow.
17162
- * - `listTargetKinds` / `listTargets` / `discoverTargets` return arrays →
17163
- * the generated cap-mount auto-`concatCollection`-fans them across every
17164
- * registered provider (notifiers addon + HA addon) so one catalog is
17165
- * routable. `send` / `testTarget` / CRUD route to ONE provider by the
17166
- * `addonId` the generated collection router extracts from the call input.
17167
- * - `Attachment.bytes` is `Uint8Array`. Transport-safe: superjson (the tRPC
17168
- * transformer) + UDS MsgPack both round-trip typed arrays — already used by
17169
- * `storage` / `storage-provider` / `recording` caps over the same path. No
17170
- * base64 fallback needed.
17171
- *
17172
- * TODO (deferred, closed-set change — separate decision): add
17173
- * `providerKind: 'notify'` so notification providers surface on the unified
17174
- * admin "Integrations" page.
17175
- */
17176
- /**
17177
- * Zentik-derived typed-media enum — the superset across every kind. Each
17178
- * adapter picks what it supports and the degrade engine filters the rest.
17179
- */
17180
- var AttachmentMediaTypeSchema = _enum([
17181
- "image",
17182
- "video",
17183
- "gif",
17184
- "audio",
17185
- "icon"
17186
- ]);
17187
- /**
17188
- * A single attachment. Exactly one of `url` (remote source, most adapters
17189
- * prefer this) or `bytes` (inline source; required for Pushover-style
17190
- * bytes-only kinds) MUST be present — the degrade engine expresses a
17191
- * url→bytes fetch as a `needsFetch` directive the adapter executes.
17192
- */
17193
- var AttachmentSchema = object({
17194
- mediaType: AttachmentMediaTypeSchema,
17195
- url: string().optional(),
17196
- bytes: _instanceof(Uint8Array).optional(),
17197
- mime: string().optional(),
17198
- name: string().optional()
17199
- }).refine((a) => a.url !== void 0 || a.bytes !== void 0, { message: "Attachment requires either `url` or `bytes`" });
17200
- var NotificationFormatSchema = _enum([
17201
- "text",
17202
- "markdown",
17203
- "html"
17204
- ]);
17205
- /** A single tap-through action button. */
17206
- var NotificationActionSchema = object({
17207
- id: string(),
17208
- label: string(),
17209
- url: string().optional()
17210
- });
17211
- /**
17212
- * The canonical notification. `body` is the only hard field (Apprise model).
17213
- * `priority` is a 5-level ORDINAL (1=lowest … 3=normal(default) … 5=urgent),
17214
- * NOT a fixed severity enum — each kind declares its own `caps.levels` and
17215
- * the adapter maps this ordinal onto its native level. `level?` is an
17216
- * optional kind-native level id (`emergency`, `silent`, …) that overrides
17217
- * `priority` for that one target.
17218
- */
17219
- var NotificationSchema = object({
17220
- body: string(),
17221
- title: string().optional(),
17222
- format: NotificationFormatSchema.default("text"),
17223
- priority: number().int().min(1).max(5).default(3),
17224
- level: string().optional(),
17225
- attachments: array(AttachmentSchema).optional(),
17226
- clickUrl: string().optional(),
17227
- actions: array(NotificationActionSchema).optional(),
17228
- sound: string().optional(),
17229
- ttl: number().optional(),
17230
- tag: string().optional(),
17231
- deviceId: number().optional(),
17232
- eventId: string().optional(),
17233
- metadata: record(string(), unknown()).optional()
17234
- });
17235
- /** One declared native severity/priority level for a kind. */
17236
- var TargetKindLevelSchema = object({
17237
- id: string(),
17238
- label: string(),
17239
- /** Which canonical priority (1..5) this level maps to. `null` = qualitative-only. */
17240
- ordinal: number().int().min(1).max(5).nullable(),
17241
- flags: object({
17242
- critical: boolean().optional(),
17243
- silent: boolean().optional(),
17244
- noPush: boolean().optional()
17245
- }).optional(),
17246
- /** e.g. Pushover `emergency` requires `retry` / `expire`. */
17247
- requires: array(string()).optional(),
17248
- description: string().optional()
17249
- });
17250
- /** The full capability block consulted before dispatch. */
17251
- var TargetKindCapsSchema = object({
17252
- attachments: object({
17253
- mediaTypes: array(AttachmentMediaTypeSchema),
17254
- mode: _enum([
17255
- "url",
17256
- "bytes",
17257
- "both"
17258
- ]),
17259
- max: number().int().nonnegative(),
17260
- maxBytes: number().int().positive().optional()
17261
- }),
17262
- /** Max action buttons (0 = none). */
17263
- actions: number().int().nonnegative(),
17264
- levels: array(TargetKindLevelSchema),
17265
- format: array(NotificationFormatSchema),
17266
- clickUrl: boolean(),
17267
- sound: boolean(),
17268
- ttl: boolean(),
17269
- bodyMaxLen: number().int().positive()
17270
- });
17271
- /**
17272
- * `configSchema` is a `ConfigUISchema` tree passed through to the admin
17273
- * FormBuilder. Stored as `z.unknown()` at the cap seam (mirrors
17274
- * `device-provider.getChildCreationSchema` `CreationSchemaOutputSchema`) —
17275
- * the union is large and not meant for runtime validation here; the exported
17276
- * `TargetKind` type re-tightens `configSchema` to `ConfigUISchema`.
17277
- */
17278
- var ConfigSchemaPassthrough = unknown();
17279
- var TargetKindSchema = object({
17280
- kind: string(),
17281
- label: string(),
17282
- icon: string(),
17283
- /** Stamped by each provider so the concat-fanned catalog stays routable. */
17284
- addonId: string(),
17285
- /**
17286
- * URL of the kind's bundled BRAND icon, served by the providing addon over
17287
- * its own `addon-routes` surface (`/addon/<addonId>/icons/<kind>`). Absent
17288
- * when the addon bundles no icon for that kind — the client then falls back
17289
- * to a neutral glyph rather than rendering the raw `icon` NAME as text.
17290
- *
17291
- * Root-relative on purpose: it resolves against whatever origin serves a web
17292
- * client, and a native client joins it onto its own hub base.
17293
- *
17294
- * DECLARED here deliberately. It used to travel as an undeclared passthrough
17295
- * field that survived only because the runtime cap-router forwards provider
17296
- * output verbatim — so every consumer had to re-declare it by hand to stop
17297
- * its own Zod parse from stripping it, and the whole arrangement would have
17298
- * broken silently the moment output validation was tightened anywhere.
17299
- */
17300
- iconUrl: string().optional(),
17301
- /**
17302
- * Media type of {@link iconUrl} (`image/svg+xml`, `image/png`, …).
17303
- *
17304
- * The server knows this and therefore says it, because the client cannot
17305
- * safely guess: a React-Native client renders SVG and raster through two
17306
- * DIFFERENT components (`react-native-svg` vs `expo-image` — expo-image does
17307
- * not decode SVG on iOS/Android), so without this it silently fell back to a
17308
- * placeholder glyph for every vector icon while the web build looked fine.
17309
- *
17310
- * Absent when {@link iconUrl} is absent, or for a legacy provider that has
17311
- * not been updated — a client that cannot determine the type should prefer
17312
- * its raster path, which is the safe default for an unknown image.
17313
- */
17314
- iconMediaType: string().optional(),
17315
- configSchema: ConfigSchemaPassthrough,
17316
- supportsDiscovery: boolean(),
17317
- caps: TargetKindCapsSchema
17318
- });
17319
- /**
17320
- * A persisted target. `config` holds secrets; providers REDACT secret fields
17321
- * (return a presence marker only) when serving `listTargets` — never
17322
- * round-trip a stored secret to the UI.
17323
- */
17324
- var TargetSchema = object({
17325
- id: string(),
17326
- name: string(),
17327
- kind: string(),
17328
- addonId: string(),
17329
- enabled: boolean(),
17330
- config: record(string(), unknown())
17331
- });
17332
- /** A discovery-surfaced candidate (config is partial + non-secret). */
17333
- var DiscoveredTargetSchema = object({
17334
- kind: string(),
17335
- suggestedName: string(),
17336
- config: record(string(), unknown())
17337
- });
17338
- /** The degrade engine's report — what was resolved / dropped / degraded. */
17339
- var RenderedAsSchema = object({
17340
- level: string(),
17341
- format: NotificationFormatSchema,
17342
- attachmentsSent: number().int().nonnegative(),
17343
- actionsSent: number().int().nonnegative(),
17344
- truncated: boolean(),
17345
- dropped: array(string())
17346
- });
17347
- var SendResultSchema = object({
17348
- success: boolean(),
17349
- error: string().optional(),
17350
- renderedAs: RenderedAsSchema.optional()
17351
- });
17352
- /** Same shape as SendResult — kept as a distinct name for the test panel. */
17353
- var TestResultSchema = SendResultSchema;
17354
- method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSchema)), method(object({
17355
- kind: string(),
17356
- config: record(string(), unknown()).optional()
17357
- }), array(DiscoveredTargetSchema)), method(object({
17358
- targetId: string(),
17359
- notification: NotificationSchema
17360
- }), SendResultSchema, { kind: "mutation" }), method(object({
17361
- targetId: string(),
17362
- sample: NotificationSchema.optional()
17363
- }), TestResultSchema, { kind: "mutation" }), method(object({ target: TargetSchema }), TargetSchema, { kind: "mutation" }), method(object({ targetId: string() }), _void(), { kind: "mutation" }), method(object({
17364
- targetId: string(),
17365
- enabled: boolean()
17366
- }), _void(), { kind: "mutation" });
17367
- /**
17368
17476
  * core-blocks — user-authored TypeScript, stored in the kernel and executed in
17369
17477
  * its own process.
17370
17478
  *
@@ -17708,6 +17816,11 @@ var EventKindDescriptorSchema = object({
17708
17816
  deviceId: number()
17709
17817
  })
17710
17818
  });
17819
+ /** One camera's event vocabulary, as returned by `listEventKindsBatch`. */
17820
+ var EventKindsForDeviceSchema = object({
17821
+ deviceId: number(),
17822
+ kinds: array(EventKindDescriptorSchema).readonly()
17823
+ });
17711
17824
  var SensorEventSchema = object({
17712
17825
  id: string(),
17713
17826
  /** The CAMERA the event is attributed to (a sensor linked to N cameras
@@ -17964,6 +18077,19 @@ var MediaFileSchema = object({
17964
18077
  sizeBytes: number(),
17965
18078
  timestamp: number()
17966
18079
  });
18080
+ /**
18081
+ * One media row WITHOUT its bytes.
18082
+ *
18083
+ * A track's media is 5-8 MB of base64 (measured: 7.67 MB across 23 files for a
18084
+ * 140 s track), and a client that renders tiles from the media data plane needs
18085
+ * to know only WHAT EXISTS — the bytes then arrive per tile, lazily, over HTTP
18086
+ * with an immutable cache, instead of all at once inside a tRPC response that
18087
+ * blocks the whole view.
18088
+ *
18089
+ * `sizeBytes` is carried because it is what lets a client decide between the
18090
+ * stored blob and a `?variant=thumb` rendering without fetching either.
18091
+ */
18092
+ var MediaFileInfoSchema = MediaFileSchema.omit({ base64: true });
17967
18093
  var DEFAULT_EVENT_QUERY_LIMIT = 1e3;
17968
18094
  var MAX_EVENT_QUERY_LIMIT = 5e3;
17969
18095
  var DeviceEventQueryInput = object({
@@ -18113,7 +18239,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18113
18239
  }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
18114
18240
  kind: "mutation",
18115
18241
  auth: "admin"
18116
- }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({
18242
+ }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
18117
18243
  deviceId: number(),
18118
18244
  since: number().optional(),
18119
18245
  until: number().optional(),
@@ -18187,7 +18313,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18187
18313
  }), array(MediaFileSchema).readonly()), method(object({
18188
18314
  trackId: string(),
18189
18315
  kinds: array(MediaFileKindEnum).optional()
18190
- }), array(MediaFileSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
18316
+ }), array(MediaFileSchema).readonly()), method(object({ trackId: string() }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), object({
18191
18317
  deviceId: number(),
18192
18318
  timestamp: number(),
18193
18319
  frameWidth: number(),
@@ -24991,6 +25117,12 @@ Object.freeze({
24991
25117
  addonId: null,
24992
25118
  access: "view"
24993
25119
  },
25120
+ "pipelineAnalytics.listEventKindsBatch": {
25121
+ capName: "pipeline-analytics",
25122
+ capScope: "device",
25123
+ addonId: null,
25124
+ access: "view"
25125
+ },
24994
25126
  "pipelineAnalytics.listOpsLog": {
24995
25127
  capName: "pipeline-analytics",
24996
25128
  capScope: "device",
@@ -25003,6 +25135,12 @@ Object.freeze({
25003
25135
  addonId: null,
25004
25136
  access: "view"
25005
25137
  },
25138
+ "pipelineAnalytics.listTrackMedia": {
25139
+ capName: "pipeline-analytics",
25140
+ capScope: "device",
25141
+ addonId: null,
25142
+ access: "view"
25143
+ },
25006
25144
  "pipelineAnalytics.listTracks": {
25007
25145
  capName: "pipeline-analytics",
25008
25146
  capScope: "device",
@@ -27132,7 +27270,7 @@ var AdminUIAddon = class extends BaseAddon {
27132
27270
  capability: adminUiCapability,
27133
27271
  provider: {
27134
27272
  getStaticDir: async () => ({ staticDir: resolveAdminUiDistDir() }),
27135
- getVersion: async () => ({ version: "1.2.26" })
27273
+ getVersion: async () => ({ version: "1.2.28" })
27136
27274
  }
27137
27275
  }];
27138
27276
  }