@camstack/addon-osd-manager 0.1.124 → 0.1.126

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 (20) hide show
  1. package/dist/MotionZonesSettings-k6Zzbfy9.mjs +310 -0
  2. package/dist/{PrivacyMaskSettings-9rCXAZlR.mjs → PrivacyMaskSettings-DwhFoc16.mjs} +26 -26
  3. package/dist/{SceneMonitorEditor-D2ATIIZD.mjs → SceneMonitorEditor-DAqbVCgr.mjs} +295 -295
  4. package/dist/_stub.js +354 -354
  5. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-B2cBM7jE.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_osd_manager_page-Dy_PTg0V.mjs} +3 -3
  6. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-Ds-WCEP6.mjs +26 -0
  7. package/dist/addon-osd-manager.css +1 -1
  8. package/dist/{responsive-CC0j2h-d.mjs → cap-error-hton7JwR.mjs} +8 -8
  9. package/dist/{hostInit-Du2Me1GR.mjs → hostInit-D_FWYfaI.mjs} +2 -2
  10. package/dist/index.js +133 -85
  11. package/dist/index.mjs +133 -85
  12. package/dist/{player-overlays-CMeXfcep.mjs → player-overlays-CEidmNyu.mjs} +1 -1
  13. package/dist/remoteEntry.js +1 -1
  14. package/dist/{scene-monitor-copy-CjiHJ4pT.mjs → scene-monitor-copy-CLKtgPk1.mjs} +1 -1
  15. package/dist/{square-hvOcBhdH.mjs → square-Dq8X76sM.mjs} +1 -1
  16. package/dist/{trash-2-rb-x3jwf.mjs → trash-2-BYTSgKQu.mjs} +1 -1
  17. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-D8j_yb3G.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_osd_manager_page__remoteEntry_js-B9gGFib0.mjs} +1 -1
  18. package/package.json +1 -1
  19. package/dist/MotionZonesSettings-DOFOB5MX.mjs +0 -310
  20. package/dist/_virtual_mf___mfe_internal__addon_osd_manager_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BK-eslgm.mjs +0 -26
@@ -36,7 +36,7 @@ async function r() {
36
36
  }
37
37
  },
38
38
  "@camstack/types": {
39
- version: "1.2.234",
39
+ version: "1.2.236",
40
40
  scope: "default",
41
41
  shareConfig: {
42
42
  singleton: !0,
@@ -81,7 +81,7 @@ async function r() {
81
81
  }
82
82
  },
83
83
  "@camstack/ui-library": {
84
- version: "1.2.190",
84
+ version: "1.2.192",
85
85
  scope: "default",
86
86
  shareConfig: {
87
87
  singleton: !0,
package/dist/index.js CHANGED
@@ -5363,7 +5363,7 @@ var ZodIssueCode = {
5363
5363
  var ZodFirstPartyTypeKind;
5364
5364
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5365
5365
  //#endregion
5366
- //#region ../types/dist/sleep-CopaBJss.mjs
5366
+ //#region ../types/dist/sleep-CaB_hL5g.mjs
5367
5367
  /**
5368
5368
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5369
5369
  * window to float samples (D455).
@@ -5563,12 +5563,6 @@ Object.fromEntries([
5563
5563
  icon: "circle-dot",
5564
5564
  order: 40
5565
5565
  },
5566
- {
5567
- id: "clips",
5568
- label: "Clips",
5569
- icon: "clapperboard",
5570
- order: 41
5571
- },
5572
5566
  {
5573
5567
  id: "engine",
5574
5568
  label: "Inference Engine",
@@ -7466,6 +7460,8 @@ var EVENT_OWNER_TYPES = [
7466
7460
  * nothing failing until a caller asked.
7467
7461
  */
7468
7462
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7463
+ /** The same list as a Zod enum, for the cap input that carries it. */
7464
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7469
7465
  new Set(EVENT_OWNER_TYPES);
7470
7466
  var EncodeProfileSchema = object({
7471
7467
  video: object({
@@ -15946,30 +15942,30 @@ var deviceManagerCapability = {
15946
15942
  */
15947
15943
  getAllBindings: method(object({}), array(DeviceBindingsForDeviceSchema)),
15948
15944
  /**
15949
- * Activate (or deactivate) a wrapper addon — or ONE of its sources — for a
15950
- * (device, cap) pair.
15951
- *
15952
- * Persists the binding via ctx.settings; `active: false` on a SINGLETON cap
15953
- * clears the wrapper (falling back to native if one is registered). On a
15954
- * COLLECTION cap it adds to the per-device DENY set instead, and the
15955
- * wrapper binding is left exactly as it was — a cap with several sources
15956
- * has no "which one" for `getBindings` to answer (D556).
15957
- *
15958
- * `sourceId` is what makes the switch match its label. One addon can serve
15959
- * several of a device's sources (`addon-provider-reolink` answers a hub
15960
- * child with `native:reolink:onboard` AND `native:reolink:hub`), so a
15961
- * toggle keyed by addon would turn off both while saying it turned off one
15962
- * — the D62 defect with the polarity reversed (D557). Naming the source
15963
- * denies exactly that source. OMITTING it keeps the cap-wide meaning
15964
- * D556 shipped: every source this addon serves for this device, which is
15965
- * also how a deny entry persisted before the re-key is still read.
15945
+ * Activate (or deactivate) a wrapper addon for a (device, cap) pair — the
15946
+ * per-camera function switch's ONE authority (D62).
15947
+ *
15948
+ * SINGLETON capabilities only. Persists the binding via ctx.settings;
15949
+ * `active: false` clears the wrapper (falling back to native if one is
15950
+ * registered) by persisting the explicit `{ wrapperAddonId: null }` marker,
15951
+ * which is what stops `getBindings` re-binding a `defaultActive` wrapper.
15952
+ *
15953
+ * **A COLLECTION capability is REFUSED by name.** It briefly took a
15954
+ * `sourceId` and wrote a per-device DENY set, so one of an addon's several
15955
+ * sources could be switched off without switching off the rest (D556, then
15956
+ * D555's re-key by source). The operator removed the whole idea the same
15957
+ * day: *"tutti i providers funzionano sempre, non serve
15958
+ * disattivarli/attivarli … loro vivono lì"* (D557 amended, third). A
15959
+ * collection cap's providers are a DERIVED set — every addon that provides
15960
+ * the cap for this device type, computed on every read and persisted
15961
+ * nowhere — so there is nothing here to activate, and a verb that accepted
15962
+ * the call could only write a field the enumeration does not read. That is
15963
+ * the D62 shape, and this repo has shipped it four times.
15966
15964
  */
15967
15965
  setWrapperActive: method(object({
15968
15966
  deviceId: number(),
15969
15967
  capName: string(),
15970
15968
  wrapperAddonId: string(),
15971
- /** The `ClipSource.source` id to toggle. Absent = all of the addon's. */
15972
- sourceId: string().optional(),
15973
15969
  active: boolean()
15974
15970
  }), _void(), {
15975
15971
  kind: "mutation",
@@ -24913,6 +24909,35 @@ var pipelineAnalyticsCapability = {
24913
24909
  deviceId: number()
24914
24910
  }), array(MediaFileInfoSchema).readonly()),
24915
24911
  /**
24912
+ * Which of `ownerIds` hold at least ONE media row — an INDEX read, never a
24913
+ * blob read, and the LICENCE `videoclips` needs to publish a `thumbnail`.
24914
+ *
24915
+ * The event-media URL is stamped from a template for every event in the
24916
+ * system, so "the plane is up" was being served as "there are bytes": 64 of
24917
+ * one camera's 179 clips pointed at a 404 (device 3836, 2026-08-17). This
24918
+ * is the question that turns the stamp into a claim.
24919
+ *
24920
+ * It is on the WIRE because the clip source is registered by the addon that
24921
+ * owns the FOOTAGE, not by this one (D560) — the probe used to be an
24922
+ * in-process `MediaStore` read. One call per OWNER KIND, never one per
24923
+ * owner: that N+1 is what `MediaStore.ownersWithMedia` was written to
24924
+ * prevent, and crossing a process boundary makes it worse, not better.
24925
+ * `ownerIds` is bounded at the same 5 000 the annotation fetch is bounded
24926
+ * at, so one pathological camera-day cannot mint an unbounded request.
24927
+ *
24928
+ * THROWS rather than answering partially. A probe that quietly
24929
+ * under-reports reads as "these owners have nothing" and the caller drops
24930
+ * thumbnails that exist — the opposite failure to the one above, and
24931
+ * equally invisible.
24932
+ *
24933
+ * `deviceId` is the authorization subject, exactly as on `getEventMedia`.
24934
+ */
24935
+ ownersWithMedia: method(object({
24936
+ deviceId: number(),
24937
+ ownerKind: MediaPresenceOwnerKindSchema,
24938
+ ownerIds: array(string()).max(5e3)
24939
+ }), array(string()).readonly()),
24940
+ /**
24916
24941
  * Search object events by text query using CLIP cosine similarity.
24917
24942
  * Encodes `text` via the `embedding-encoder` cap, queries the
24918
24943
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -28966,6 +28991,18 @@ var vectorStoreCapability = {
28966
28991
  stats: method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" })
28967
28992
  }
28968
28993
  };
28994
+ _enum([
28995
+ "queue-full",
28996
+ "camera-backoff",
28997
+ "sleeping",
28998
+ "camera-refused",
28999
+ "no-keyframe",
29000
+ "no-catalog-row",
29001
+ "unsupported",
29002
+ "unknown-device",
29003
+ "uid-missing"
29004
+ ]);
29005
+ _enum(["deferred", "final"]);
28969
29006
  var ClipSchema = object({
28970
29007
  /** Opaque, provider-namespaced id. The default provider encodes the time
28971
29008
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -29116,7 +29153,25 @@ var ClipSchema = object({
29116
29153
  */
29117
29154
  playable: boolean().optional(),
29118
29155
  /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
29119
- unplayableReason: string().optional()
29156
+ unplayableReason: string().optional(),
29157
+ /**
29158
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
29159
+ * is not its end.
29160
+ *
29161
+ * Absent — the common case — means the row is closed and its `endMs` is the
29162
+ * end of the recording. Present and `true` means the source told us the file
29163
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
29164
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
29165
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
29166
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
29167
+ * on growing. A surface that drew 29 s there was lying about a clip that
29168
+ * plays for minutes, on the one row the operator looks at first.
29169
+ *
29170
+ * `endMs` is deliberately still a number: it is the best bound anything has
29171
+ * for a mint window or a byte fetch, and every consumer already requires it.
29172
+ * This flag says what it is WORTH, and a duration is not drawn from it.
29173
+ */
29174
+ inProgress: boolean().optional()
29120
29175
  });
29121
29176
  var ClipPlaybackSchema = object({
29122
29177
  /**
@@ -29199,59 +29254,17 @@ var ClipSourceSchema = object({
29199
29254
  * alternative — a `native:reolink:* → provider-reolink` table inside the
29200
29255
  * widget — is a second authority on provider identity living in the one
29201
29256
  * package with no business knowing it, wrong the day a third source appears
29202
- * (D557). One addon may serve SEVERAL sources: `addon-provider-reolink`
29203
- * answers a hub child with both `native:reolink:onboard` and
29204
- * `native:reolink:hub`, which is why the per-device switch is keyed by
29205
- * SOURCE and not by this (D555).
29257
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
29258
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
29259
+ * served a hub child two of them until the two views were measured to be one
29260
+ * store read twice (D555) and then read ONE way (D565).
29206
29261
  *
29207
29262
  * Optional for version skew only. The collection dispatcher stamps it from
29208
29263
  * the registry, so a row that travelled through the fan-out carries the
29209
29264
  * authoritative id whatever the provider filled in.
29210
29265
  */
29211
29266
  addonId: string().optional(),
29212
- /**
29213
- * Which API this source resolved to FOR THIS CAMERA, when it has a choice.
29214
- *
29215
- * A source may cover one store through more than one surface — the Reolink
29216
- * provider reads a hub child through the parent's event log and a standalone
29217
- * through its own file list, because that is what each camera answers. The
29218
- * CHOICE is the provider's, made from what the camera is, and is never a row
29219
- * the operator has to understand; but it is REPORTED, because a source that
29220
- * silently reads a different API on two cameras and then behaves differently
29221
- * is the thing nobody can debug later. Absent when the source has only one
29222
- * way to read its store.
29223
- */
29224
- via: string().optional(),
29225
- availability: ClipSourceAvailabilitySchema,
29226
- /**
29227
- * The operator switched this source OFF for this camera.
29228
- *
29229
- * Deliberately NOT a member of {@link ClipSourceAvailabilitySchema}'s
29230
- * vocabulary. That enum models what the source CAN do — a sleeping camera, an
29231
- * unmounted card, an index that disagrees with its own calendar — and a
29232
- * switched-off source could answer perfectly well; the operator decided it
29233
- * should not. Folding the choice in is how `disabled` and `broken` stop being
29234
- * distinguishable, which is the D62 rule this repo has already paid for twice:
29235
- * an off switch is REPORTED off (`CameraStatus.switchedOff`, the same word),
29236
- * and disabled must never look like broken. It is also what lets every
29237
- * exhaustive consumer of the availability enum keep compiling.
29238
- *
29239
- * A switched-off source contributes NO clips (`listClips` never calls it) and
29240
- * its row carries no `catalogAsOf`: nothing confirms a catalog it is not
29241
- * allowed to serve, and a frozen age that can only grow draws a stalling
29242
- * source rather than an off switch.
29243
- *
29244
- * The row survives BECAUSE it is the control the operator switches the source
29245
- * back on from — D554 decision 4's rule ("a source that cannot answer
29246
- * produces a ROW, not an absence") applied to the one case D556 carved out of
29247
- * it, and D557's own kept property ("a source is never hidden, only its
29248
- * rows"). Absent means on.
29249
- *
29250
- * A provider never sets this — like {@link ClipSourceSchema.addonId} it is
29251
- * stamped by the collection dispatcher, which holds the registry's projection
29252
- * of the persisted authority (D556) and is the only place that knows it.
29253
- */
29254
- switchedOff: boolean().optional()
29267
+ availability: ClipSourceAvailabilitySchema
29255
29268
  });
29256
29269
  var videoclipsCapability = {
29257
29270
  name: "videoclips",
@@ -29271,17 +29284,31 @@ var videoclipsCapability = {
29271
29284
  * `device-config-widget-wrapped-binding.spec.ts` pins that a `kind:'wrapped'`
29272
29285
  * binding entry derives the same section a native one does.
29273
29286
  *
29274
- * `topTab` because the browser owns the whole pane (a day's clips, a source
29275
- * list and a player), exactly as `ptz` does; the widget itself is
29276
- * `host/clips-browser` in ui-library's `HOST_WIDGETS`.
29287
+ * **It is a SECTION of the Recording tab, at the end of it — not a tab of
29288
+ * its own.** It shipped as a `clips` top-tab and the operator rejected the
29289
+ * placement: *"utilizzerei la stessa tab recordings, lì abbiamo già tutto il
29290
+ * necessario, una nuova sezione alla fine per le clips"*. The Recording tab
29291
+ * already holds the recorder's panel, the schedule bands and the unified
29292
+ * retention policy; footage the camera itself holds is the same question,
29293
+ * asked of a different store. `order: 100` puts it after all of them with
29294
+ * room left in front. The `clips` entry in `WELL_KNOWN_TABS` went with it —
29295
+ * a well-known id nobody declares is an invitation to mint the tab again.
29296
+ *
29297
+ * `topTab` stays: the browser owns a pane (a day's tiles, a source list and
29298
+ * a player) and the Config tab's inner bar has no room for one. The widget
29299
+ * itself is `host/clips-browser` in ui-library's `HOST_WIDGETS`, and because
29300
+ * the admin Recordings page renders every `location:'top-tab'` +
29301
+ * `tab:'recording'` section behind its camera picker
29302
+ * (`CameraRecordingSettingsSection`), this declaration lands the section on
29303
+ * BOTH surfaces with no second wiring.
29277
29304
  */
29278
29305
  deviceConfig: { ui: {
29279
29306
  kind: "widget",
29280
29307
  widgetId: "host/clips-browser",
29281
- tab: "clips",
29308
+ tab: "recording",
29282
29309
  topTab: true,
29283
29310
  label: "Clips",
29284
- order: 0
29311
+ order: 100
29285
29312
  } },
29286
29313
  methods: {
29287
29314
  listClips: method(object({
@@ -29290,13 +29317,23 @@ var videoclipsCapability = {
29290
29317
  until: number(),
29291
29318
  limit: number().int().positive().optional(),
29292
29319
  /**
29293
- * View filter over {@link ClipSourceSchema.source} values — the
29294
- * picker's selection, forwarded so a provider need not list what
29295
- * nobody is looking at. ABSENT means every source this camera has,
29296
- * which is the honest default for a surface whose whole point is that
29297
- * nothing is hidden (D554 3). A provider with one source ignores it.
29320
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
29321
+ * carries, never a source id and never a list.
29322
+ *
29323
+ * Absent means the device's BOUND provider, which is CamStack on every
29324
+ * camera today because that is what `deviceManager.getBindings`
29325
+ * answers with. A provider the device is not bound to is refused by
29326
+ * name rather than answered by another one (D552's
29327
+ * `rejectUnresolvedAddonPin` rule).
29328
+ *
29329
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
29330
+ * assumed the answer was a fan-out over everything a camera has. The
29331
+ * operator settled otherwise on 2026-09-20 — *"il get videoclips
29332
+ * prendere un provider opzionale, di default va su camstack"*,
29333
+ * *"l'utilizzatore è uno solo"* — so the list is asked of one provider
29334
+ * at a time and there is nothing to filter out of it.
29298
29335
  */
29299
- sources: array(string()).optional()
29336
+ provider: string().optional()
29300
29337
  }), array(ClipSchema).readonly(), {
29301
29338
  kind: "query",
29302
29339
  auth: "protected"
@@ -44834,6 +44871,12 @@ Object.freeze({
44834
44871
  addonId: null,
44835
44872
  access: "view"
44836
44873
  },
44874
+ "pipelineAnalytics.ownersWithMedia": {
44875
+ capName: "pipeline-analytics",
44876
+ capScope: "device",
44877
+ addonId: null,
44878
+ access: "view"
44879
+ },
44837
44880
  "pipelineAnalytics.pauseForStorageMigration": {
44838
44881
  capName: "pipeline-analytics",
44839
44882
  capScope: "device",
@@ -48734,6 +48777,11 @@ Object.freeze({
48734
48777
  form: "single",
48735
48778
  optional: false
48736
48779
  }],
48780
+ "pipelineAnalytics.ownersWithMedia": [{
48781
+ name: "deviceId",
48782
+ form: "single",
48783
+ optional: false
48784
+ }],
48737
48785
  "pipelineAnalytics.proposeRetrainAnnotations": [{
48738
48786
  name: "deviceId",
48739
48787
  form: "single",
package/dist/index.mjs CHANGED
@@ -5359,7 +5359,7 @@ var ZodIssueCode = {
5359
5359
  var ZodFirstPartyTypeKind;
5360
5360
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5361
5361
  //#endregion
5362
- //#region ../types/dist/sleep-CopaBJss.mjs
5362
+ //#region ../types/dist/sleep-CaB_hL5g.mjs
5363
5363
  /**
5364
5364
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5365
5365
  * window to float samples (D455).
@@ -5559,12 +5559,6 @@ Object.fromEntries([
5559
5559
  icon: "circle-dot",
5560
5560
  order: 40
5561
5561
  },
5562
- {
5563
- id: "clips",
5564
- label: "Clips",
5565
- icon: "clapperboard",
5566
- order: 41
5567
- },
5568
5562
  {
5569
5563
  id: "engine",
5570
5564
  label: "Inference Engine",
@@ -7462,6 +7456,8 @@ var EVENT_OWNER_TYPES = [
7462
7456
  * nothing failing until a caller asked.
7463
7457
  */
7464
7458
  var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7459
+ /** The same list as a Zod enum, for the cap input that carries it. */
7460
+ var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
7465
7461
  new Set(EVENT_OWNER_TYPES);
7466
7462
  var EncodeProfileSchema = object({
7467
7463
  video: object({
@@ -15942,30 +15938,30 @@ var deviceManagerCapability = {
15942
15938
  */
15943
15939
  getAllBindings: method(object({}), array(DeviceBindingsForDeviceSchema)),
15944
15940
  /**
15945
- * Activate (or deactivate) a wrapper addon — or ONE of its sources — for a
15946
- * (device, cap) pair.
15947
- *
15948
- * Persists the binding via ctx.settings; `active: false` on a SINGLETON cap
15949
- * clears the wrapper (falling back to native if one is registered). On a
15950
- * COLLECTION cap it adds to the per-device DENY set instead, and the
15951
- * wrapper binding is left exactly as it was — a cap with several sources
15952
- * has no "which one" for `getBindings` to answer (D556).
15953
- *
15954
- * `sourceId` is what makes the switch match its label. One addon can serve
15955
- * several of a device's sources (`addon-provider-reolink` answers a hub
15956
- * child with `native:reolink:onboard` AND `native:reolink:hub`), so a
15957
- * toggle keyed by addon would turn off both while saying it turned off one
15958
- * — the D62 defect with the polarity reversed (D557). Naming the source
15959
- * denies exactly that source. OMITTING it keeps the cap-wide meaning
15960
- * D556 shipped: every source this addon serves for this device, which is
15961
- * also how a deny entry persisted before the re-key is still read.
15941
+ * Activate (or deactivate) a wrapper addon for a (device, cap) pair — the
15942
+ * per-camera function switch's ONE authority (D62).
15943
+ *
15944
+ * SINGLETON capabilities only. Persists the binding via ctx.settings;
15945
+ * `active: false` clears the wrapper (falling back to native if one is
15946
+ * registered) by persisting the explicit `{ wrapperAddonId: null }` marker,
15947
+ * which is what stops `getBindings` re-binding a `defaultActive` wrapper.
15948
+ *
15949
+ * **A COLLECTION capability is REFUSED by name.** It briefly took a
15950
+ * `sourceId` and wrote a per-device DENY set, so one of an addon's several
15951
+ * sources could be switched off without switching off the rest (D556, then
15952
+ * D555's re-key by source). The operator removed the whole idea the same
15953
+ * day: *"tutti i providers funzionano sempre, non serve
15954
+ * disattivarli/attivarli … loro vivono lì"* (D557 amended, third). A
15955
+ * collection cap's providers are a DERIVED set — every addon that provides
15956
+ * the cap for this device type, computed on every read and persisted
15957
+ * nowhere — so there is nothing here to activate, and a verb that accepted
15958
+ * the call could only write a field the enumeration does not read. That is
15959
+ * the D62 shape, and this repo has shipped it four times.
15962
15960
  */
15963
15961
  setWrapperActive: method(object({
15964
15962
  deviceId: number(),
15965
15963
  capName: string(),
15966
15964
  wrapperAddonId: string(),
15967
- /** The `ClipSource.source` id to toggle. Absent = all of the addon's. */
15968
- sourceId: string().optional(),
15969
15965
  active: boolean()
15970
15966
  }), _void(), {
15971
15967
  kind: "mutation",
@@ -24909,6 +24905,35 @@ var pipelineAnalyticsCapability = {
24909
24905
  deviceId: number()
24910
24906
  }), array(MediaFileInfoSchema).readonly()),
24911
24907
  /**
24908
+ * Which of `ownerIds` hold at least ONE media row — an INDEX read, never a
24909
+ * blob read, and the LICENCE `videoclips` needs to publish a `thumbnail`.
24910
+ *
24911
+ * The event-media URL is stamped from a template for every event in the
24912
+ * system, so "the plane is up" was being served as "there are bytes": 64 of
24913
+ * one camera's 179 clips pointed at a 404 (device 3836, 2026-08-17). This
24914
+ * is the question that turns the stamp into a claim.
24915
+ *
24916
+ * It is on the WIRE because the clip source is registered by the addon that
24917
+ * owns the FOOTAGE, not by this one (D560) — the probe used to be an
24918
+ * in-process `MediaStore` read. One call per OWNER KIND, never one per
24919
+ * owner: that N+1 is what `MediaStore.ownersWithMedia` was written to
24920
+ * prevent, and crossing a process boundary makes it worse, not better.
24921
+ * `ownerIds` is bounded at the same 5 000 the annotation fetch is bounded
24922
+ * at, so one pathological camera-day cannot mint an unbounded request.
24923
+ *
24924
+ * THROWS rather than answering partially. A probe that quietly
24925
+ * under-reports reads as "these owners have nothing" and the caller drops
24926
+ * thumbnails that exist — the opposite failure to the one above, and
24927
+ * equally invisible.
24928
+ *
24929
+ * `deviceId` is the authorization subject, exactly as on `getEventMedia`.
24930
+ */
24931
+ ownersWithMedia: method(object({
24932
+ deviceId: number(),
24933
+ ownerKind: MediaPresenceOwnerKindSchema,
24934
+ ownerIds: array(string()).max(5e3)
24935
+ }), array(string()).readonly()),
24936
+ /**
24912
24937
  * Search object events by text query using CLIP cosine similarity.
24913
24938
  * Encodes `text` via the `embedding-encoder` cap, queries the
24914
24939
  * `ObjectEmbeddingStore` with optional prefilters, ranks all matching
@@ -28962,6 +28987,18 @@ var vectorStoreCapability = {
28962
28987
  stats: method(VectorStatsInputSchema, VectorStatsResultSchema, { auth: "admin" })
28963
28988
  }
28964
28989
  };
28990
+ _enum([
28991
+ "queue-full",
28992
+ "camera-backoff",
28993
+ "sleeping",
28994
+ "camera-refused",
28995
+ "no-keyframe",
28996
+ "no-catalog-row",
28997
+ "unsupported",
28998
+ "unknown-device",
28999
+ "uid-missing"
29000
+ ]);
29001
+ _enum(["deferred", "final"]);
28965
29002
  var ClipSchema = object({
28966
29003
  /** Opaque, provider-namespaced id. The default provider encodes the time
28967
29004
  * window so `getClipPlayback` is self-contained (no event re-query). */
@@ -29112,7 +29149,25 @@ var ClipSchema = object({
29112
29149
  */
29113
29150
  playable: boolean().optional(),
29114
29151
  /** Why {@link playable} is false, verbatim (`no-file-for-window`). */
29115
- unplayableReason: string().optional()
29152
+ unplayableReason: string().optional(),
29153
+ /**
29154
+ * This clip is STILL BEING WRITTEN, so {@link ClipSchema.timeRange}`.endMs`
29155
+ * is not its end.
29156
+ *
29157
+ * Absent — the common case — means the row is closed and its `endMs` is the
29158
+ * end of the recording. Present and `true` means the source told us the file
29159
+ * has no end yet, and the `endMs` we carry is whatever the camera's index
29160
+ * entry happened to hold: on a Reolink E1 Outdoor PoE (592, measured
29161
+ * 2026-09-20) the newest file `…_192927_000000_…_0.mp4` reported an `endTime`
29162
+ * of 19:29:56 and STILL reported it eight minutes later, while the file went
29163
+ * on growing. A surface that drew 29 s there was lying about a clip that
29164
+ * plays for minutes, on the one row the operator looks at first.
29165
+ *
29166
+ * `endMs` is deliberately still a number: it is the best bound anything has
29167
+ * for a mint window or a byte fetch, and every consumer already requires it.
29168
+ * This flag says what it is WORTH, and a duration is not drawn from it.
29169
+ */
29170
+ inProgress: boolean().optional()
29116
29171
  });
29117
29172
  var ClipPlaybackSchema = object({
29118
29173
  /**
@@ -29195,59 +29250,17 @@ var ClipSourceSchema = object({
29195
29250
  * alternative — a `native:reolink:* → provider-reolink` table inside the
29196
29251
  * widget — is a second authority on provider identity living in the one
29197
29252
  * package with no business knowing it, wrong the day a third source appears
29198
- * (D557). One addon may serve SEVERAL sources: `addon-provider-reolink`
29199
- * answers a hub child with both `native:reolink:onboard` and
29200
- * `native:reolink:hub`, which is why the per-device switch is keyed by
29201
- * SOURCE and not by this (D555).
29253
+ * (D557). One addon may serve SEVERAL sources, which is why the per-device
29254
+ * switch is keyed by SOURCE and not by this (D555) — `addon-provider-reolink`
29255
+ * served a hub child two of them until the two views were measured to be one
29256
+ * store read twice (D555) and then read ONE way (D565).
29202
29257
  *
29203
29258
  * Optional for version skew only. The collection dispatcher stamps it from
29204
29259
  * the registry, so a row that travelled through the fan-out carries the
29205
29260
  * authoritative id whatever the provider filled in.
29206
29261
  */
29207
29262
  addonId: string().optional(),
29208
- /**
29209
- * Which API this source resolved to FOR THIS CAMERA, when it has a choice.
29210
- *
29211
- * A source may cover one store through more than one surface — the Reolink
29212
- * provider reads a hub child through the parent's event log and a standalone
29213
- * through its own file list, because that is what each camera answers. The
29214
- * CHOICE is the provider's, made from what the camera is, and is never a row
29215
- * the operator has to understand; but it is REPORTED, because a source that
29216
- * silently reads a different API on two cameras and then behaves differently
29217
- * is the thing nobody can debug later. Absent when the source has only one
29218
- * way to read its store.
29219
- */
29220
- via: string().optional(),
29221
- availability: ClipSourceAvailabilitySchema,
29222
- /**
29223
- * The operator switched this source OFF for this camera.
29224
- *
29225
- * Deliberately NOT a member of {@link ClipSourceAvailabilitySchema}'s
29226
- * vocabulary. That enum models what the source CAN do — a sleeping camera, an
29227
- * unmounted card, an index that disagrees with its own calendar — and a
29228
- * switched-off source could answer perfectly well; the operator decided it
29229
- * should not. Folding the choice in is how `disabled` and `broken` stop being
29230
- * distinguishable, which is the D62 rule this repo has already paid for twice:
29231
- * an off switch is REPORTED off (`CameraStatus.switchedOff`, the same word),
29232
- * and disabled must never look like broken. It is also what lets every
29233
- * exhaustive consumer of the availability enum keep compiling.
29234
- *
29235
- * A switched-off source contributes NO clips (`listClips` never calls it) and
29236
- * its row carries no `catalogAsOf`: nothing confirms a catalog it is not
29237
- * allowed to serve, and a frozen age that can only grow draws a stalling
29238
- * source rather than an off switch.
29239
- *
29240
- * The row survives BECAUSE it is the control the operator switches the source
29241
- * back on from — D554 decision 4's rule ("a source that cannot answer
29242
- * produces a ROW, not an absence") applied to the one case D556 carved out of
29243
- * it, and D557's own kept property ("a source is never hidden, only its
29244
- * rows"). Absent means on.
29245
- *
29246
- * A provider never sets this — like {@link ClipSourceSchema.addonId} it is
29247
- * stamped by the collection dispatcher, which holds the registry's projection
29248
- * of the persisted authority (D556) and is the only place that knows it.
29249
- */
29250
- switchedOff: boolean().optional()
29263
+ availability: ClipSourceAvailabilitySchema
29251
29264
  });
29252
29265
  var videoclipsCapability = {
29253
29266
  name: "videoclips",
@@ -29267,17 +29280,31 @@ var videoclipsCapability = {
29267
29280
  * `device-config-widget-wrapped-binding.spec.ts` pins that a `kind:'wrapped'`
29268
29281
  * binding entry derives the same section a native one does.
29269
29282
  *
29270
- * `topTab` because the browser owns the whole pane (a day's clips, a source
29271
- * list and a player), exactly as `ptz` does; the widget itself is
29272
- * `host/clips-browser` in ui-library's `HOST_WIDGETS`.
29283
+ * **It is a SECTION of the Recording tab, at the end of it — not a tab of
29284
+ * its own.** It shipped as a `clips` top-tab and the operator rejected the
29285
+ * placement: *"utilizzerei la stessa tab recordings, lì abbiamo già tutto il
29286
+ * necessario, una nuova sezione alla fine per le clips"*. The Recording tab
29287
+ * already holds the recorder's panel, the schedule bands and the unified
29288
+ * retention policy; footage the camera itself holds is the same question,
29289
+ * asked of a different store. `order: 100` puts it after all of them with
29290
+ * room left in front. The `clips` entry in `WELL_KNOWN_TABS` went with it —
29291
+ * a well-known id nobody declares is an invitation to mint the tab again.
29292
+ *
29293
+ * `topTab` stays: the browser owns a pane (a day's tiles, a source list and
29294
+ * a player) and the Config tab's inner bar has no room for one. The widget
29295
+ * itself is `host/clips-browser` in ui-library's `HOST_WIDGETS`, and because
29296
+ * the admin Recordings page renders every `location:'top-tab'` +
29297
+ * `tab:'recording'` section behind its camera picker
29298
+ * (`CameraRecordingSettingsSection`), this declaration lands the section on
29299
+ * BOTH surfaces with no second wiring.
29273
29300
  */
29274
29301
  deviceConfig: { ui: {
29275
29302
  kind: "widget",
29276
29303
  widgetId: "host/clips-browser",
29277
- tab: "clips",
29304
+ tab: "recording",
29278
29305
  topTab: true,
29279
29306
  label: "Clips",
29280
- order: 0
29307
+ order: 100
29281
29308
  } },
29282
29309
  methods: {
29283
29310
  listClips: method(object({
@@ -29286,13 +29313,23 @@ var videoclipsCapability = {
29286
29313
  until: number(),
29287
29314
  limit: number().int().positive().optional(),
29288
29315
  /**
29289
- * View filter over {@link ClipSourceSchema.source} values — the
29290
- * picker's selection, forwarded so a provider need not list what
29291
- * nobody is looking at. ABSENT means every source this camera has,
29292
- * which is the honest default for a surface whose whole point is that
29293
- * nothing is hidden (D554 3). A provider with one source ignores it.
29316
+ * WHICH provider to ask — the `addonId` a {@link ClipSourceSchema} row
29317
+ * carries, never a source id and never a list.
29318
+ *
29319
+ * Absent means the device's BOUND provider, which is CamStack on every
29320
+ * camera today because that is what `deviceManager.getBindings`
29321
+ * answers with. A provider the device is not bound to is refused by
29322
+ * name rather than answered by another one (D552's
29323
+ * `rejectUnresolvedAddonPin` rule).
29324
+ *
29325
+ * It replaced `sources?: string[]`, a VIEW filter over source ids that
29326
+ * assumed the answer was a fan-out over everything a camera has. The
29327
+ * operator settled otherwise on 2026-09-20 — *"il get videoclips
29328
+ * prendere un provider opzionale, di default va su camstack"*,
29329
+ * *"l'utilizzatore è uno solo"* — so the list is asked of one provider
29330
+ * at a time and there is nothing to filter out of it.
29294
29331
  */
29295
- sources: array(string()).optional()
29332
+ provider: string().optional()
29296
29333
  }), array(ClipSchema).readonly(), {
29297
29334
  kind: "query",
29298
29335
  auth: "protected"
@@ -44830,6 +44867,12 @@ Object.freeze({
44830
44867
  addonId: null,
44831
44868
  access: "view"
44832
44869
  },
44870
+ "pipelineAnalytics.ownersWithMedia": {
44871
+ capName: "pipeline-analytics",
44872
+ capScope: "device",
44873
+ addonId: null,
44874
+ access: "view"
44875
+ },
44833
44876
  "pipelineAnalytics.pauseForStorageMigration": {
44834
44877
  capName: "pipeline-analytics",
44835
44878
  capScope: "device",
@@ -48730,6 +48773,11 @@ Object.freeze({
48730
48773
  form: "single",
48731
48774
  optional: false
48732
48775
  }],
48776
+ "pipelineAnalytics.ownersWithMedia": [{
48777
+ name: "deviceId",
48778
+ form: "single",
48779
+ optional: false
48780
+ }],
48733
48781
  "pipelineAnalytics.proposeRetrainAnnotations": [{
48734
48782
  name: "deviceId",
48735
48783
  form: "single",