@camstack/addon-post-analysis 1.2.629 → 1.2.630

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 (15) hide show
  1. package/dist/{clip-model-registry-M_QM7l6j.js → clip-model-registry-DB0TSd14.js} +141 -86
  2. package/dist/{clip-model-registry-SrfKJ0e8.mjs → clip-model-registry-wZ4Nt5jo.mjs} +132 -83
  3. package/dist/embedding-encoder/index.js +1 -1
  4. package/dist/embedding-encoder/index.mjs +1 -1
  5. package/dist/pipeline-analytics/_stub.js +2 -2
  6. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CUsaUp1g.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-BjktQPk_.mjs} +2 -2
  7. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-BxPKSyZo.mjs +26 -0
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DdbV-R1z.mjs +26 -0
  9. package/dist/pipeline-analytics/{hostInit-q1u97PmF.mjs → hostInit-BMCiXaWO.mjs} +2 -2
  10. package/dist/pipeline-analytics/index.js +340 -266
  11. package/dist/pipeline-analytics/index.mjs +339 -265
  12. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  13. package/package.json +1 -1
  14. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-DxapkUgD.mjs +0 -26
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-D7KeW_uB.mjs +0 -26
@@ -1,4 +1,4 @@
1
- import { $ as MediaFileKindEnum, $i as createEvent, $n as consumableLowPercentOf, $r as pickFaceRescoreIntervalMinutes, $t as OpsLogEntrySchema, A as DeclaredDevices, Ai as syncActiveAlarmProfileSettings, An as TrackSideSchema, Ar as isDetectionMacroClass, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as withSystemEventClasses, Bn as applyModeDelaysEdits, Br as isSystemDelivery, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as union, Ci as servedTemplateVars, Cn as TimelapseRuleInputSchema, Cr as gifFromMp4Args, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as subjectPathSampleFromBox, Dn as TrackPuritySchema, Dr as isAppLinkBase, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ei as subKindsOf, En as TrackPlacementSchema, Er as isAlertEpisodeSeverity, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventSummarySplitOf, Fn as alarmPanelCapability, Fr as isLabelSupplyStepId, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as isSourceCap, Gn as audioMetricsCapability, Gr as mayWriteToLocation, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Hn as applySecureOnArmEdit, Hr as isVectorIndexDimConflict, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as templateCarriesSummary, In as alertEpisodeId, Ir as isMediaOwnerType, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as BaseAddon, Jn as buildSubjectPath, Jr as normalizeNcMediaPolicy, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as DeviceType, Kn as audioModeOf, Kr as memoizePinnedFfmpeg, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as templateVarsFor, Ln as alertEpisodeKind, Lr as isRetiredClearKey, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as systemEventFamilyProblems, Mn as activateAlarmProfile, Mr as isEventKindLabelKey, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as systemEventFilterApplies, Nn as addonWidgetsSourceCapability, Nr as isEventOwnerType, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as subjectPathSvgPoints, On as TrackRelationKindSchema, Or as isAudioClassName, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as systemEventSummaryScheduleOf, Pn as alarmDeviceSecureOnArm, Pr as isKnownTemplateVar, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as RECORD_QUERY_MAX, Qn as cloneAlarmSettings, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as vectorDimFromBase64, Rn as alertEpisodePayload, Rr as isRootSuppliableStepId, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as tuple, Si as sensorRaisedOf, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as gifDeriveSpeed, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as EventCategory, Ti as storageOccupancyCapability, Tn as TimelapseRuleSchema, Tr as isAlertEpisodeOpen, Tt as NC_TANK_SYSTEM_EVENT_KINDS, U as LabelAttributionSchema, Ui as canonicalHash, Un as armRemovalReconcile, Ur as kebabToCamel, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as zoneAnalyticsCapability, Vn as applyNonBlockingEdit, Vr as isSystemEventSummaryRule, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as evaluateSensorEdge, Wn as assertTimelapseCadences, Wr as linkedStepOf, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as InvalidRecordError, Xn as calendarEventLocalSpan, Xr as occupancyScope, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as CamProfileSchema, Yn as buildViewerLink, Yr as notificationRulesCapability, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as RECORD_MAX_DATA_BYTES, Zn as classifyRetrainReview, Zr as parseAlertEpisodePayload, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as partialRecord, _i as ruleDeviceCompatCapQueries, _n as SceneMonitorSchema, _r as ensureAlarmProfiles, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as nodePin, ai as readTimelapseGeneratedAt, an as RetrainDismissedSchema, ar as derivePostTriggeredAction, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as string, bi as sceneMonitorCapability, bn as SetTrackRelationInputSchema, br as faceGalleryCapability, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as ZodObject, ci as resolveLocationMode, cn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, cr as describeSystemEventFamilyProblem, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as boolean, di as resolveNcMediaImage, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as customAction, ei as pipelineAnalyticsCapability, en as PackageLlmVerdictSchema, er as coreBlockIdFromAddonId, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as discriminatedUnion, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as object, gi as ruleBelongsToAlarm$1, gn as SCENE_RESET_RECAPTURES, gr as encodeVectorBase64, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as number, hi as ruleAutoTriggersAlarm, hn as SCENE_DIVERGED, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as isDeviceScopedCap, ii as readMotionTriggeredBy, in as RebuildObjectEmbeddingsRefusalSchema, ir as deriveDetailCropRect, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as systemEventDeliveryProblems, jn as TrackSourceSchema, jr as isDrawableSubjectPath, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as summarizeReview, kn as TrackRelationSchema, kr as isDefaultAlarmProfileId, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as _enum, li as resolveModeDelays, ln as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, lr as deviceAttentionKind, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as looseObject, mi as rootSupplyServes, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as defineRecordKinds, ni as projectSubjectPath, nn as RECORDING_EXPORT_MAX_READ_BYTES, nr as createAlarmProfile, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oi as renameAlarmProfile, on as RetrainOriginSchema, or as deriveRecordingMode, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as literal, pi as retrainRevisionConflictMessage, pn as SCENE_DEFAULT_QUIET_SECONDS, pr as embeddingRefusalOf, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as errMsg, qn as buildEventKindDescriptor, qr as ncMediaPolicyError, qt as NcSystemEventKindSchema, ra as hydrateSchema, ri as readDeviceStateFrom, rn as RECORDING_SOURCE_CAMSTACK_ADDON, rr as deleteAlarmProfile, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, sa as sleep$1, si as resolveClipSearchMinScore, sn as RetrainStatusSchema, sr as describeSystemEventDeliveryProblem, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as defineCustomActions, ti as plateGalleryCapability, tr as cosineSimilarity$1, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as array, ui as resolveModeNotifyPolicy, un as SCENE_DEFAULT_ANCHOR_THRESHOLD, ur as deviceRemovalHookCapability, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, va as record, vi as ruleGroupingApplies, vn as SetTrackPurityInputSchema, vr as eventKindLabel, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as unknown, wi as startMoverUnderClearance, wn as TimelapseRulePatchSchema, wr as isAlarmControllableCap, wt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, x as COCO_TO_MACRO, xa as templateLiteral, xi as secureActuationFor, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as failureContributionCapability, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as strictObject, yi as ruleKindOf, yn as SetTrackPurityResultSchema, yr as evictionPolicyOfLocation, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as viewerLinkBaseOf, zn as applyBlockingEdit, zr as isScheduleActive, zt as NcRuleTargetSchema } from "../clip-model-registry-SrfKJ0e8.mjs";
1
+ import { $ as MediaFileKindEnum, $i as RECORD_QUERY_MAX, $n as consumableFormOf, $t as OpsLogEntrySchema, A as DeclaredDevices, Ai as summarizeReview, An as TrackSideSchema, Ar as isDefaultAlarmProfileId, At as NcAlarmSensorsInputSchema, B as FULL_IMAGE_BBOX, Bi as viewerLinkBaseOf, Bn as applyModeDelaysEdits, Br as isScheduleActive, Bt as NcRuleUpdateInputSchema, C as ClearTrackRelationResultSchema, Ca as tuple, Ci as sensorRaisedOf, Cn as TimelapseRuleInputSchema, Cr as gifDeriveSpeed, Ct as NC_SNOOZE_MAX_MINUTES, D as DEFAULT_TIMELAPSE_PREVIEW_TEXT, Di as subKindsOf, Dn as TrackPuritySchema, Dr as isAlertEpisodeSeverity, Dt as NC_TEMPLATE_VARS, E as DEFAULT_EVENT_COLOR, Ea as EventCategory, Ei as storageOccupancyCapability, En as TrackPlacementSchema, Er as isAlertEpisodeOpen, Et as NC_TAXONOMY, F as EVENT_DENSITY_BATCH_MAX, Fi as systemEventSummaryScheduleOf, Fn as alarmPanelCapability, Fr as isKnownTemplateVar, Ft as NcRuleActionSchema, G as ListTrackRelationsResultSchema, Gi as evaluateSensorEdge, Gn as audioMetricsCapability, Gr as linkedStepOf, Gt as NcSnoozeSuppressedSchema, H as LABEL_SUPPLY_MACRO, Hi as zoneAnalyticsCapability, Hn as applySecureOnArmEdit, Hr as isSystemEventSummaryRule, Ht as NcSharedSequenceSchema, I as EVENT_KIND_BY_CAP, Ii as systemEventSummarySplitOf, In as alertEpisodeId, Ir as isLabelSupplyStepId, It as NcRuleInputSchema, J as MAX_BIRTH_DECISION_RECORDS, Ji as errMsg, Jn as buildSubjectPath, Jr as ncMediaPolicyError, Jt as NcTaxonomySchema, K as LockStateSchema, Ki as isSourceCap, Kn as audioModeOf, Kr as mayWriteToLocation, Kt as NcStateSummarySectionIdSchema, L as EVENT_OWNER_TYPES, Li as templateCarriesSummary, Ln as alertEpisodeKind, Lr as isMediaOwnerType, Lt as NcRulePatchSchema, M as EMBEDDING_MODEL_FAILED, Mi as systemEventDeliveryProblems, Mn as activateAlarmProfile, Mr as isDrawableSubjectPath, Mt as NcAlarmSettingsPatchSchema, N as EMBEDDING_REQUIREMENTS_FAILED, Ni as systemEventFamilyProblems, Nn as addonWidgetsSourceCapability, Nr as isEventKindLabelKey, Nt as NcConditionDescriptorSchema, O as DETECTION_MACRO_CLASSES, Oi as subjectPathSampleFromBox, On as TrackRelationKindSchema, Or as isAppLinkBase, Ot as NcAlarmConfigSchema, P as EMBEDDING_REQUIREMENTS_UNAVAILABLE, Pi as systemEventFilterApplies, Pn as alarmDeviceSecureOnArm, Pr as isEventOwnerType, Pt as NcConditionsSchema, Q as MOTION_CLOSE_AFTER_MS, Qi as RECORD_MAX_DATA_BYTES, Qn as cloneAlarmSettings, Qr as parseAlertEpisodePayload, Qt as OPS_LOG_DEFAULT_LIMIT, R as EXPORT_DENSE_MAX_RANGES, Ri as templateVarsFor, Rn as alertEpisodePayload, Rr as isRetiredClearKey, Rt as NcRuleSchema, S as ClearTrackRelationInputSchema, Sa as templateLiteral, Si as secureActuationFor, Sn as TRACK_RELATIONS_DEFAULT_LIMIT, Sr as failureContributionCapability, St as NC_SENSOR_TRANSITION_KINDS, T as DEFAULT_DETAIL_CROP_CONVENTION, Ta as unknown, Ti as startMoverUnderClearance, Tn as TimelapseRuleSchema, Tr as isAlarmControllableCap, Tt as NC_SYSTEM_EVENT_KIND_GROUP_SPECS, U as LabelAttributionSchema, Ui as DEFAULT_FIRST_SIGHTING_FRESHNESS_MS, Un as armRemovalReconcile, Ur as isVectorIndexDimConflict, Ut as NcSnoozeInputSchema, V as FailureCounters, Vi as withSystemEventClasses, Vn as applyNonBlockingEdit, Vr as isSystemDelivery, Vt as NcScheduleSchema, W as ListTrackRelationsInputSchema, Wi as canonicalHash, Wn as assertTimelapseCadences, Wr as kebabToCamel, Wt as NcSnoozeSchema, X as MAX_TRACK_PURITIES, Xi as CamProfileSchema, Xn as calendarEventLocalSpan, Xr as notificationRulesCapability, Xt as NcTemplatePreviewSchema, Y as MAX_BIRTH_LATENCY_PROXY_MS, Yi as BaseAddon, Yn as buildViewerLink, Yr as normalizeNcMediaPolicy, Yt as NcTemplatePreviewInputSchema, Z as MAX_TRACK_RELATIONS, Zi as InvalidRecordError, Zn as classifyRetrainReview, Zr as occupancyScope, Zt as NcTemplateVarDescriptorSchema, _ as BIRTH_DECISION_DEFAULT_LIMIT, _a as object, _i as ruleBelongsToAlarm$1, _n as SceneMonitorSchema, _r as encodeVectorBase64, _t as NC_CONSUMABLE_SYSTEM_EVENT_KINDS, a as CLIP_EMBEDDING_STEP_ID, aa as isDeviceScopedCap, ai as readMotionTriggeredBy, an as RetrainDismissedSchema, ar as deriveDetailCropRect, at as NC_ALARM_NOTIFY_FIELDS, b as BucketAccumulator, ba as strictObject, bi as ruleKindOf, bn as SetTrackRelationInputSchema, br as evictionPolicyOfLocation, bt as NC_GROUPING_MAX_SEC, c as readClusterModelPinStrict, ca as sleep$1, ci as resolveClipSearchMinScore, cn as SCENE_CONFIRM_DEFAULT_MAX_IMAGE_PX, cr as describeSystemEventDeliveryProblem, ct as NC_AUDIO_CONFIRM_HITS_DEFAULT, d as APPEARANCE_BLOCKS, da as array, di as resolveModeNotifyPolicy, dn as SCENE_DEFAULT_CHECK_INTERVAL_SEC, dr as deviceRemovalHookCapability, dt as NC_CLIP_PRE_ROLL_DEFAULT_SEC, ea as createEvent, ei as pickFaceRescoreIntervalMinutes, en as PackageLlmVerdictSchema, er as consumableLowPercentOf, et as MotionTriggeredBySchema, f as APPEARANCE_RESOLVES_AMBIGUITY_DEFAULT, fa as boolean, fi as resolveNcMediaImage, fn as SCENE_DEFAULT_OBSERVATION_SPACING_SEC, ft as NC_CLIP_RETENTION_MAX_SEC, g as ArchivedDebugNoteSchema, ga as number, gi as ruleAutoTriggersAlarm, gn as SCENE_RESET_RECAPTURES, gt as NC_CONSUMABLE_LOW_PERCENT_DEFAULT, h as AlarmArmModeSchema, ha as looseObject, hi as rootSupplyServes, hn as SCENE_DIVERGED, ht as NC_CONFIRM_DEFAULT_TIMEOUT_MS, ia as hydrateSchema, ii as readDeviceStateFrom, in as RebuildObjectEmbeddingsRefusalSchema, ir as deleteAlarmProfile, it as NC_ALARM_DELAY_MAX_SEC, j as DetectionSourceSchema, ji as syncActiveAlarmProfileSettings, jn as TrackSourceSchema, jr as isDetectionMacroClass, jt as NcAlarmSensorsSchema, k as DETECTION_PIPELINE_CAP_NAME, ki as subjectPathSvgPoints, kn as TrackRelationSchema, kr as isAudioClassName, kt as NcAlarmPostTriggeredActionSchema, l as resolveClusterModelPin, la as ZodObject, li as resolveLocationMode, ln as SCENE_CONFIRM_DEFAULT_TIMEOUT_MS, lr as describeSystemEventFamilyProblem, lt as NC_AUDIO_CONFIRM_WINDOW_SEC_DEFAULT, m as AUDIO_MACRO_LABELS, ma as literal, mi as retrainRevisionConflictMessage, mn as SCENE_DEFAULT_UNCOVERED_POLICY, mr as embeddingRefusalOf, mt as NC_CONFIRM_DEFAULT_MAX_IMAGE_PX, na as defineCustomActions, ni as plateGalleryCapability, nn as RECORDING_EXPORT_MAX_READ_BYTES, nr as cosineSimilarity$1, nt as NC_ALARM_DEFAULT_TRIGGERED_DURATION_SEC, o as CLUSTER_MODEL_OWNER_ADDON_ID, oa as nodePin, oi as readTimelapseGeneratedAt, on as RetrainOriginSchema, or as derivePostTriggeredAction, ot as NC_ALARM_SYSTEM_EVENT_KINDS, p as ARCHIVED_DEBUG_NOTES_DEFAULT_LIMIT, pa as discriminatedUnion, pn as SCENE_DEFAULT_QUIET_SECONDS, pt as NC_CONDITION_CATALOG, q as MAX_ARCHIVED_DEBUG_NOTES, qi as DeviceType, qn as buildEventKindDescriptor, qr as memoizePinnedFfmpeg, qt as NcSystemEventKindSchema, ra as defineRecordKinds, ri as projectSubjectPath, rn as RECORDING_SOURCE_CAMSTACK_ADDON, rr as createAlarmProfile, rt as NC_ALARM_DELAY_FIELDS, s as FACE_EMBEDDING_STEP_ID, si as renameAlarmProfile, sn as RetrainStatusSchema, sr as deriveRecordingMode, st as NC_ALARM_TRIGGERED_MAX_SEC, t as BUILTIN_CLIP_MODELS, ta as customAction, ti as pipelineAnalyticsCapability, tr as coreBlockIdFromAddonId, tt as NC_ALARM_DEFAULT_PROFILE_ID, u as APPEARANCE_BIN_COUNT, ua as _enum, ui as resolveModeDelays, un as SCENE_DEFAULT_ANCHOR_THRESHOLD, ur as deviceAttentionKind, ut as NC_CLIP_POST_ROLL_DEFAULT_SEC, v as BaseDevice, va as partialRecord, vi as ruleDeviceCompatCapQueries, vn as SetTrackPurityInputSchema, vr as ensureAlarmProfiles, vt as NC_DEFAULT_SNOOZE_MINUTES, w as CoverStateSchema, wa as union, wi as servedTemplateVars, wn as TimelapseRulePatchSchema, wr as gifFromMp4Args, wt as NC_STATE_SUMMARY_SECTION_IDS, x as COCO_TO_MACRO, xa as string, xi as sceneMonitorCapability, xn as TIMELAPSE_DENSE_FLOOR_SEC, xr as faceGalleryCapability, xt as NC_GROUPING_MIN_SEC, y as BirthDecisionRecordSchema, ya as record, yi as ruleGroupingApplies, yn as SetTrackPurityResultSchema, yr as eventKindLabel, yt as NC_GROUPING_DEFAULT_SEC, z as FIRST_LEVEL_MACRO_CLASSES, zi as vectorDimFromBase64, zn as applyBlockingEdit, zr as isRootSuppliableStepId, zt as NcRuleTargetSchema } from "../clip-model-registry-wZ4Nt5jo.mjs";
2
2
  import { t as __exportAll } from "../embedding-encoder/index.mjs";
3
3
  import * as fs$1 from "node:fs";
4
4
  import { createWriteStream, promises } from "node:fs";
@@ -9018,27 +9018,13 @@ var en_default = {
9018
9018
  "system.device-consumable-low.title": "Consumable low — {{deviceName}}",
9019
9019
  "system.device-consumable-low.body": "{{item}} on {{deviceName}} is at {{percentage}}%",
9020
9020
  "system.device-consumable-low.body.unmeasured": "{{item}} on {{deviceName}} needs replacing",
9021
+ "system.device-consumable-low.body.tank": "{{item}} on {{deviceName}} is nearly empty",
9022
+ "system.device-consumable-low.body.tank-full": "{{item}} on {{deviceName}} is full",
9023
+ "system.device-consumable-low.body.product": "{{item}} on {{deviceName}} is running out",
9024
+ "system.device-consumable-low.body.bin": "{{item}} on {{deviceName}} is full",
9021
9025
  "system.device-consumable-normal.title": "Consumable replaced — {{deviceName}}",
9022
9026
  "system.device-consumable-normal.body": "{{item}} on {{deviceName}} is back to {{percentage}}%",
9023
9027
  "system.device-consumable-normal.body.unmeasured": "{{item}} on {{deviceName}} is OK again",
9024
- "system.device-tank-low.title": "Tank low — {{deviceName}}",
9025
- "system.device-tank-full.title": "Tank full — {{deviceName}}",
9026
- "system.device-tank-normal.title": "Tank OK — {{deviceName}}",
9027
- "system.device-tank-low.body.cleanWater": "The clean water tank on {{deviceName}} is running low",
9028
- "system.device-tank-full.body.cleanWater": "The clean water tank on {{deviceName}} is full",
9029
- "system.device-tank-normal.body.cleanWater": "The clean water tank on {{deviceName}} is OK again",
9030
- "system.device-tank-low.body.dirtyWater": "The dirty water tank on {{deviceName}} is running low",
9031
- "system.device-tank-full.body.dirtyWater": "The dirty water tank on {{deviceName}} is full",
9032
- "system.device-tank-normal.body.dirtyWater": "The dirty water tank on {{deviceName}} is OK again",
9033
- "system.device-tank-low.body.detergent": "The detergent tank on {{deviceName}} is running low",
9034
- "system.device-tank-full.body.detergent": "The detergent tank on {{deviceName}} is full",
9035
- "system.device-tank-normal.body.detergent": "The detergent tank on {{deviceName}} is OK again",
9036
- "system.device-tank-low.body.dustBin": "The dust bin on {{deviceName}} is running low",
9037
- "system.device-tank-full.body.dustBin": "The dust bin on {{deviceName}} is full",
9038
- "system.device-tank-normal.body.dustBin": "The dust bin on {{deviceName}} is OK again",
9039
- "system.device-tank-low.body.item": "{{item}} on {{deviceName}} is running low",
9040
- "system.device-tank-full.body.item": "{{item}} on {{deviceName}} is full",
9041
- "system.device-tank-normal.body.item": "{{item}} on {{deviceName}} is OK again",
9042
9028
  "system.camera-online.title": "Device online",
9043
9029
  "system.camera-online.body": "Device {{deviceName}} is online",
9044
9030
  "system.camera-offline.title": "Device offline",
@@ -9193,8 +9179,7 @@ var en_default = {
9193
9179
  "summary.section.device-state": "Offline or off",
9194
9180
  "summary.section.streams": "Streams offline",
9195
9181
  "summary.section.battery": "Low batteries",
9196
- "summary.section.consumables": "Consumables to replace",
9197
- "summary.section.tanks": "Tanks low",
9182
+ "summary.section.consumables": "Consumables",
9198
9183
  "summary.section.detection-blind": "Detecting nothing",
9199
9184
  "summary.section.nodes": "Nodes with problems",
9200
9185
  "summary.section.addons": "Addons stopped",
@@ -9212,7 +9197,12 @@ var en_default = {
9212
9197
  "summary.line.addon-crash-loop": "{{nodeId}} — {{addons}} stopped after repeated crashes (since {{since}})",
9213
9198
  "summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
9214
9199
  "summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
9215
- "summary.line.tank-unread": "{{deviceName}} — the {{tank}} tank needs attention",
9200
+ "summary.line.consumable.part": "{{item}} {{percentage}}%",
9201
+ "summary.line.consumable.part.unmeasured": "{{item}} needs replacing",
9202
+ "summary.line.consumable.tank": "{{item}} nearly empty",
9203
+ "summary.line.consumable.tank-full": "{{item}} full",
9204
+ "summary.line.consumable.product": "{{item}} running out",
9205
+ "summary.line.consumable.bin": "{{item}} full",
9216
9206
  "calendar-events.title": "{{rule}}: {{count}} upcoming",
9217
9207
  "calendar-events.empty": "No events in the next {{horizonDays}} day(s).",
9218
9208
  "calendar-events.day.today": "Today — {{date}}",
@@ -9424,27 +9414,13 @@ var it_default = {
9424
9414
  "system.device-consumable-low.title": "Consumabile basso — {{deviceName}}",
9425
9415
  "system.device-consumable-low.body": "{{item}} su {{deviceName}} è al {{percentage}}%",
9426
9416
  "system.device-consumable-low.body.unmeasured": "{{item}} su {{deviceName}} va sostituito",
9417
+ "system.device-consumable-low.body.tank": "{{item}} di {{deviceName}} è quasi vuota",
9418
+ "system.device-consumable-low.body.tank-full": "{{item}} di {{deviceName}} è piena",
9419
+ "system.device-consumable-low.body.product": "{{item}} di {{deviceName}} è in esaurimento",
9420
+ "system.device-consumable-low.body.bin": "{{item}} di {{deviceName}} è pieno",
9427
9421
  "system.device-consumable-normal.title": "Consumabile sostituito — {{deviceName}}",
9428
9422
  "system.device-consumable-normal.body": "{{item}} su {{deviceName}} è tornato al {{percentage}}%",
9429
9423
  "system.device-consumable-normal.body.unmeasured": "{{item}} su {{deviceName}} è di nuovo a posto",
9430
- "system.device-tank-low.title": "Serbatoio quasi vuoto — {{deviceName}}",
9431
- "system.device-tank-full.title": "Serbatoio pieno — {{deviceName}}",
9432
- "system.device-tank-normal.title": "Serbatoio a posto — {{deviceName}}",
9433
- "system.device-tank-low.body.cleanWater": "Il serbatoio dell'acqua pulita di {{deviceName}} è quasi vuoto",
9434
- "system.device-tank-full.body.cleanWater": "Il serbatoio dell'acqua pulita di {{deviceName}} è pieno",
9435
- "system.device-tank-normal.body.cleanWater": "Il serbatoio dell'acqua pulita di {{deviceName}} è di nuovo a posto",
9436
- "system.device-tank-low.body.dirtyWater": "Il serbatoio dell'acqua sporca di {{deviceName}} è quasi vuoto",
9437
- "system.device-tank-full.body.dirtyWater": "Il serbatoio dell'acqua sporca di {{deviceName}} è pieno",
9438
- "system.device-tank-normal.body.dirtyWater": "Il serbatoio dell'acqua sporca di {{deviceName}} è di nuovo a posto",
9439
- "system.device-tank-low.body.detergent": "Il serbatoio del detergente di {{deviceName}} è quasi vuoto",
9440
- "system.device-tank-full.body.detergent": "Il serbatoio del detergente di {{deviceName}} è pieno",
9441
- "system.device-tank-normal.body.detergent": "Il serbatoio del detergente di {{deviceName}} è di nuovo a posto",
9442
- "system.device-tank-low.body.dustBin": "Il contenitore della polvere di {{deviceName}} è quasi vuoto",
9443
- "system.device-tank-full.body.dustBin": "Il contenitore della polvere di {{deviceName}} è pieno",
9444
- "system.device-tank-normal.body.dustBin": "Il contenitore della polvere di {{deviceName}} è di nuovo a posto",
9445
- "system.device-tank-low.body.item": "{{item}} di {{deviceName}} sta finendo",
9446
- "system.device-tank-full.body.item": "{{item}} di {{deviceName}} è pieno",
9447
- "system.device-tank-normal.body.item": "{{item}} di {{deviceName}} è di nuovo a posto",
9448
9424
  "system.camera-online.title": "Dispositivo online",
9449
9425
  "system.camera-online.body": "Il dispositivo {{deviceName}} è online",
9450
9426
  "system.camera-offline.title": "Dispositivo offline",
@@ -9599,8 +9575,7 @@ var it_default = {
9599
9575
  "summary.section.device-state": "Offline o spenti",
9600
9576
  "summary.section.streams": "Stream offline",
9601
9577
  "summary.section.battery": "Batterie scariche",
9602
- "summary.section.consumables": "Consumabili da sostituire",
9603
- "summary.section.tanks": "Serbatoi bassi",
9578
+ "summary.section.consumables": "Consumabili",
9604
9579
  "summary.section.detection-blind": "Senza detection",
9605
9580
  "summary.section.nodes": "Nodi con problemi",
9606
9581
  "summary.section.addons": "Addon fermati",
@@ -9618,7 +9593,12 @@ var it_default = {
9618
9593
  "summary.line.addon-crash-loop": "{{nodeId}} — {{addons}} fermati dopo crash ripetuti (da {{since}})",
9619
9594
  "summary.line.update": "{{packageName}} {{currentVersion}} → {{latestVersion}}",
9620
9595
  "summary.line.wrapper-update": "Container {{currentVersion}} → {{latestVersion}}",
9621
- "summary.line.tank-unread": "{{deviceName}} — il serbatoio {{tank}} richiede attenzione",
9596
+ "summary.line.consumable.part": "{{item}} {{percentage}}%",
9597
+ "summary.line.consumable.part.unmeasured": "{{item}} da sostituire",
9598
+ "summary.line.consumable.tank": "{{item}} quasi vuota",
9599
+ "summary.line.consumable.tank-full": "{{item}} piena",
9600
+ "summary.line.consumable.product": "{{item}} in esaurimento",
9601
+ "summary.line.consumable.bin": "{{item}} pieno",
9622
9602
  "calendar-events.title": "{{rule}}: {{count}} in arrivo",
9623
9603
  "calendar-events.empty": "Nessun evento nei prossimi {{horizonDays}} giorno/i.",
9624
9604
  "calendar-events.day.today": "Oggi — {{date}}",
@@ -11900,9 +11880,9 @@ function systemEventKeys(kind) {
11900
11880
  titleKey: `system.${kind}.title`,
11901
11881
  bodyKey: `system.${kind}.body.ignored`
11902
11882
  };
11903
- if (NC_TANK_SYSTEM_EVENT_KINDS.includes(kind)) return {
11883
+ if (kind === "device-consumable-low") return {
11904
11884
  titleKey: `system.${kind}.title`,
11905
- bodyKey: `system.${kind}.body.cleanWater`
11885
+ bodyKey: "system.device-consumable-low.body.tank"
11906
11886
  };
11907
11887
  if (kind === "rule-broken") return {
11908
11888
  titleKey: `system.${kind}.title`,
@@ -71475,6 +71455,25 @@ Object.freeze({
71475
71455
  "path": "reason",
71476
71456
  "valueType": "string"
71477
71457
  }],
71458
+ "running-state": [
71459
+ {
71460
+ "path": "running",
71461
+ "valueType": "boolean"
71462
+ },
71463
+ {
71464
+ "path": "since",
71465
+ "valueType": "number"
71466
+ },
71467
+ {
71468
+ "path": "source",
71469
+ "valueType": "enum",
71470
+ "enumValues": ["native", "assigned"]
71471
+ },
71472
+ {
71473
+ "path": "phase",
71474
+ "valueType": "string"
71475
+ }
71476
+ ],
71478
71477
  "script-runner": [
71479
71478
  {
71480
71479
  "path": "isRunning",
@@ -80982,83 +80981,6 @@ function deriveConsumableBand(input) {
80982
80981
  return value <= input.thresholdPercent ? "low" : "normal";
80983
80982
  }
80984
80983
  //#endregion
80985
- //#region src/notification-center/alerts/tank-band.ts
80986
- /**
80987
- * A TANK reduced to the only thing worth storing: its band (D642).
80988
- *
80989
- * A tank is a `supply` or `waste` item of a device's `consumables` slice
80990
- * (D915 — before it, the four vacuum tanks lived on `vacuum-control`): a
80991
- * vacuum's clean water, dirty water, detergent and dust bin, a dishwasher's
80992
- * salt and rinse aid. `consumable-routing.ts` narrows the item's status to the
80993
- * band vocabulary below. The operator asked for them to notify like
80994
- * consumables: a supply running low, a waste tank full.
80995
- *
80996
- * ## The verdict is the provider's
80997
- *
80998
- * A tank's `status` is the device's own sensor (`ok` | `low` | `full`),
80999
- * and it is the ONLY thing judged. The hub has no threshold for a tank level:
81000
- * the rule for thresholds is that they belong to whoever generates the event,
81001
- * and a tank that says `low` has already decided. A tank that reports only a
81002
- * `level` is therefore not judged at all (`null`) rather than judged against a
81003
- * number the hub would have to invent.
81004
- *
81005
- * ## `null` is the third answer, and it is not `normal`
81006
- *
81007
- * An unknown status says nothing — it neither opens nor closes an episode, for
81008
- * the reason `battery-band.ts` gives: a reading nobody took is not evidence
81009
- * that anything changed.
81010
- *
81011
- * Pure. No clock, no I/O.
81012
- */
81013
- /**
81014
- * The tanks with a sentence of their own (`system.device-tank-*.body.<key>`),
81015
- * by their item key — the four a vacuum reports. Any other tank (salt, rinse
81016
- * aid, …) is worded by its label (`body.item`).
81017
- */
81018
- var NC_TANK_KEYS = [
81019
- "cleanWater",
81020
- "dirtyWater",
81021
- "detergent",
81022
- "dustBin"
81023
- ];
81024
- function isNamedTankKey(key) {
81025
- return NC_TANK_KEYS.some((k) => k === key);
81026
- }
81027
- /**
81028
- * How long a tank change must HOLD before it counts (D642 review M2).
81029
- *
81030
- * A float switch sloshing at the edge of `low` / `full` flips its status back
81031
- * and forth while the robot moves; each flip is a durable transition, and
81032
- * without a hold each one is a raise and a recovery on the operator's phone.
81033
- * One minute is two reload ticks: long enough to outlast a slosh, short enough
81034
- * that a genuinely full dust bin is said within a minute and a half.
81035
- */
81036
- var NC_TANK_HOLD_MS = 6e4;
81037
- function deriveTankBand(reading) {
81038
- switch (reading.status) {
81039
- case "low": return "low";
81040
- case "full": return "full";
81041
- case "ok": return "normal";
81042
- case null: return null;
81043
- }
81044
- }
81045
- /** `tank:<deviceId>:<key>` — the subject of one tank's episode. */
81046
- function tankSubject(deviceId, key) {
81047
- return `tank:${String(deviceId)}:${key}`;
81048
- }
81049
- /** The parts of a tank subject, or `null` when it is not one. */
81050
- function parseTankSubject(subject) {
81051
- const match = /^tank:(\d+):(.+)$/.exec(subject);
81052
- if (match === null) return null;
81053
- const deviceId = Number(match[1]);
81054
- const key = match[2];
81055
- if (!Number.isSafeInteger(deviceId) || key === void 0) return null;
81056
- return {
81057
- deviceId,
81058
- key
81059
- };
81060
- }
81061
- //#endregion
81062
80984
  //#region src/notification-center/device-directory.ts
81063
80985
  /**
81064
80986
  * WHO a device id is — the mirrored directory the system-event intake reads.
@@ -81820,6 +81742,7 @@ function incomingFromConsumableBand(input) {
81820
81742
  textParams: {
81821
81743
  ...deviceTextParams(input.deviceId, input.identity),
81822
81744
  item: input.item.label,
81745
+ form: "part",
81823
81746
  ...percentage !== null ? { percentage: String(percentage) } : {}
81824
81747
  },
81825
81748
  deviceId: input.deviceId,
@@ -81827,29 +81750,33 @@ function incomingFromConsumableBand(input) {
81827
81750
  ...input.identity !== void 0 ? { deviceType: input.identity.type } : {}
81828
81751
  });
81829
81752
  }
81753
+ /** Instant body keys for a consumable form (D934). */
81754
+ function consumableLowBodyKey(form, attention, measured) {
81755
+ if (form === "part") return measured ? "system.device-consumable-low.body" : "system.device-consumable-low.body.unmeasured";
81756
+ if (form === "tank") return attention === "full" ? "system.device-consumable-low.body.tank-full" : "system.device-consumable-low.body.tank";
81757
+ if (form === "product") return "system.device-consumable-low.body.product";
81758
+ return "system.device-consumable-low.body.bin";
81759
+ }
81830
81760
  /**
81831
- * A TANK band, as a system-event subject (D642, D915).
81761
+ * A supply / waste item judged on the provider's verdict (D934).
81832
81762
  *
81833
- * Built like {@link incomingFromConsumableBand}: `tank:<deviceId>:<tank>` is
81834
- * one subject per tank (the dust bin's alert must not cancel the clean water's),
81835
- * and the recovery keeps it. For the four named vacuum tanks the BODY VARIANT
81836
- * names the tank in the reader's language — the params are frozen before the
81837
- * language is known, so the tank's name cannot be a param; `tank` carries the
81838
- * stable token for a custom template. Any other tank (a dishwasher's salt, …)
81839
- * is worded by the provider's label through `body.item`.
81763
+ * Subject `consumable:<deviceId>:<key>` — no `consumableLowPercent` so rules
81764
+ * at 15 % still hear salt and water.
81840
81765
  */
81841
- function incomingFromTankBand(input) {
81842
- const kind = input.band === "low" ? "device-tank-low" : input.band === "full" ? "device-tank-full" : "device-tank-normal";
81766
+ function incomingFromProviderConsumableBand(input) {
81767
+ const kind = input.band === "low" ? "device-consumable-low" : "device-consumable-normal";
81768
+ const bodyKey = input.band === "low" ? consumableLowBodyKey(input.item.form, input.attention, false) : "system.device-consumable-normal.body.unmeasured";
81843
81769
  return systemSubject({
81844
81770
  facts: input.facts,
81845
81771
  kind,
81846
- subject: tankSubject(input.deviceId, input.tank),
81772
+ subject: legacyConsumableSubject(input.deviceId, input.item.key),
81847
81773
  titleKey: `system.${kind}.title`,
81848
- bodyKey: isNamedTankKey(input.tank) ? `system.${kind}.body.${input.tank}` : `system.${kind}.body.item`,
81774
+ bodyKey,
81849
81775
  textParams: {
81850
81776
  ...deviceTextParams(input.deviceId, input.identity),
81851
- tank: input.tank,
81852
- item: input.label ?? input.tank
81777
+ item: input.item.label,
81778
+ form: input.item.form,
81779
+ attention: input.attention
81853
81780
  },
81854
81781
  deviceId: input.deviceId,
81855
81782
  ...input.identity !== void 0 ? { deviceType: input.identity.type } : {}
@@ -82734,13 +82661,43 @@ function deliveryForKind(kind) {
82734
82661
  return kind;
82735
82662
  }
82736
82663
  //#endregion
82737
- //#region src/notification-center/alerts/open-alert.ts
82664
+ //#region src/notification-center/alerts/tank-band.ts
82738
82665
  /**
82739
- * The raise of an episode whose reading has not arrived since the restart —
82740
- * worded with what the row KNOWS: no invented percentage (the binary battery
82741
- * sentence), the item key as its name, and a tank kind that only the filters
82742
- * read (the summary words an unmeasured tank without a band).
82666
+ * How long a tank change must HOLD before it counts (D642 review M2).
82667
+ *
82668
+ * A float switch sloshing at the edge of `low` / `full` flips its status back
82669
+ * and forth while the robot moves; each flip is a durable transition, and
82670
+ * without a hold each one is a raise and a recovery on the operator's phone.
82671
+ * One minute is two reload ticks: long enough to outlast a slosh, short enough
82672
+ * that a genuinely full dust bin is said within a minute and a half.
82743
82673
  */
82674
+ var NC_TANK_HOLD_MS = 6e4;
82675
+ function deriveTankBand(reading) {
82676
+ switch (reading.status) {
82677
+ case "low": return "low";
82678
+ case "full": return "full";
82679
+ case "ok": return "normal";
82680
+ case null: return null;
82681
+ }
82682
+ }
82683
+ /** `tank:<deviceId>:<key>` — the subject of one tank's episode. */
82684
+ function tankSubject(deviceId, key) {
82685
+ return `tank:${String(deviceId)}:${key}`;
82686
+ }
82687
+ /** The parts of a tank subject, or `null` when it is not one. */
82688
+ function parseTankSubject(subject) {
82689
+ const match = /^tank:(\d+):(.+)$/.exec(subject);
82690
+ if (match === null) return null;
82691
+ const deviceId = Number(match[1]);
82692
+ const key = match[2];
82693
+ if (!Number.isSafeInteger(deviceId) || key === void 0) return null;
82694
+ return {
82695
+ deviceId,
82696
+ key
82697
+ };
82698
+ }
82699
+ //#endregion
82700
+ //#region src/notification-center/alerts/open-alert.ts
82744
82701
  function unmeasuredEpisode(input) {
82745
82702
  const deviceId = alertSubjectDeviceId(input.subject);
82746
82703
  if (deviceId === void 0) return null;
@@ -82758,7 +82715,23 @@ function unmeasuredEpisode(input) {
82758
82715
  identity
82759
82716
  });
82760
82717
  const parts = parseConsumableSubject(input.subject);
82761
- if (input.family === "consumable" && parts !== null) return incomingFromConsumableBand({
82718
+ if (parts !== null && parts.legacy) {
82719
+ const attention = input.detail === "full" ? "full" : "low";
82720
+ const form = providerFormFromDetail(input.detail);
82721
+ return incomingFromProviderConsumableBand({
82722
+ facts,
82723
+ deviceId,
82724
+ band: "low",
82725
+ item: {
82726
+ key: parts.key,
82727
+ label: parts.key,
82728
+ form
82729
+ },
82730
+ attention,
82731
+ identity
82732
+ });
82733
+ }
82734
+ if (parts !== null) return incomingFromConsumableBand({
82762
82735
  facts,
82763
82736
  deviceId,
82764
82737
  band: "low",
@@ -82772,25 +82745,30 @@ function unmeasuredEpisode(input) {
82772
82745
  identity
82773
82746
  });
82774
82747
  const tank = parseTankSubject(input.subject);
82775
- if (tank === null || tank.deviceId !== deviceId) return null;
82776
- return incomingFromTankBand({
82777
- facts,
82778
- deviceId,
82779
- band: input.detail === "full" ? "full" : "low",
82780
- tank: tank.key,
82781
- identity
82782
- });
82748
+ if (tank !== null && tank.deviceId === deviceId) {
82749
+ const attention = input.detail === "full" ? "full" : "low";
82750
+ return incomingFromProviderConsumableBand({
82751
+ facts,
82752
+ deviceId,
82753
+ band: "low",
82754
+ item: {
82755
+ key: tank.key,
82756
+ label: tank.key,
82757
+ form: "tank"
82758
+ },
82759
+ attention,
82760
+ identity
82761
+ });
82762
+ }
82763
+ return null;
82764
+ }
82765
+ function providerFormFromDetail(detail) {
82766
+ if (detail === "full") return "bin";
82767
+ return "tank";
82783
82768
  }
82784
- /**
82785
- * One alerting row as an open episode, or `null` when it cannot be worded (the
82786
- * caller logs it). A legacy consumable row is skipped: it is adopted or
82787
- * retired first. A tank whose band the row remembers is worded by it
82788
- * (review M6), even without a reading.
82789
- */
82790
82769
  function openAlertOf(row, input) {
82791
82770
  const parts = parseConsumableSubject(row.subject);
82792
- if (parts?.legacy === true) return null;
82793
- const family = parts !== null ? "consumable" : row.subject.startsWith("tank:") ? "tank" : "battery";
82771
+ const family = parts !== null || row.subject.startsWith("tank:") ? "consumable" : "battery";
82794
82772
  const subject = (input.restated ?? unmeasuredEpisode({
82795
82773
  subject: row.subject,
82796
82774
  family,
@@ -82799,7 +82777,7 @@ function openAlertOf(row, input) {
82799
82777
  ...row.detail !== void 0 ? { detail: row.detail } : {}
82800
82778
  }))?.subject.systemEvent;
82801
82779
  if (subject === void 0) return null;
82802
- const measured = input.restated !== null || family === "tank" && row.detail !== void 0;
82780
+ const measured = input.restated !== null || row.detail !== void 0 || parts !== null && !parts.legacy && subject.textParams?.["percentage"] !== void 0;
82803
82781
  return {
82804
82782
  family,
82805
82783
  since: row.since,
@@ -82915,8 +82893,8 @@ var NcDeviceAlerts = class {
82915
82893
  * reminder sweep and for judging a threshold a rule gains later (D642).
82916
82894
  */
82917
82895
  lastConsumable = /* @__PURE__ */ new Map();
82918
- /** The last reading per tank SUBJECT, for the reminder sweep. */
82919
- lastTank = /* @__PURE__ */ new Map();
82896
+ /** Provider-judged items, keyed by episode subject (`consumable:<id>:<key>`). */
82897
+ lastProvider = /* @__PURE__ */ new Map();
82920
82898
  /**
82921
82899
  * Tank changes on HOLD (review M2): a band that would move the episode,
82922
82900
  * first seen at `since`. It counts once it has held for `NC_TANK_HOLD_MS`,
@@ -82988,6 +82966,15 @@ var NcDeviceAlerts = class {
82988
82966
  * genuine `replace` would then read as a transition out of a state nobody
82989
82967
  * observed.
82990
82968
  */
82969
+ /** One item from the consumables slice — every form (D934). */
82970
+ observeConsumableItem(deviceId, sliceItem) {
82971
+ this.closeLegacyTankEpisode(deviceId, sliceItem.key);
82972
+ if (sliceItem.wear !== null) {
82973
+ this.observeConsumable(deviceId, sliceItem.wear);
82974
+ return;
82975
+ }
82976
+ this.observeProviderConsumable(deviceId, sliceItem);
82977
+ }
82991
82978
  observeConsumable(deviceId, item) {
82992
82979
  this.lastConsumable.set(itemKeyOf(deviceId, item.key), {
82993
82980
  deviceId,
@@ -83004,31 +82991,51 @@ var NcDeviceAlerts = class {
83004
82991
  const settings = this.deps.settings();
83005
82992
  for (const thresholdPercent of thresholds) this.judgeConsumable(deviceId, item, thresholdPercent, settings);
83006
82993
  }
83007
- /**
83008
- * ONE tank — a `supply` / `waste` item of a device's `consumables` slice
83009
- * (D642; read from `consumables` since D915, `consumable-routing.ts`).
83010
- *
83011
- * The provider's verdict is the band (`tank-band.ts`); an unknown status
83012
- * leaves everything untouched, an open episode included.
83013
- */
83014
- observeTank(deviceId, tank) {
83015
- const band = deriveTankBand(tank);
82994
+ /** A supply / waste / tank-form item — provider verdict only (D934). */
82995
+ observeProviderConsumable(deviceId, sliceItem) {
82996
+ const band = deriveTankBand(providerTankReading(sliceItem));
83016
82997
  if (band === null) return;
83017
- const subject = tankSubject(deviceId, tank.key);
83018
- this.lastTank.set(subject, {
82998
+ const subject = legacyConsumableSubject(deviceId, sliceItem.key);
82999
+ const attention = band === "full" ? "full" : "low";
83000
+ this.lastProvider.set(subject, {
83019
83001
  deviceId,
83020
- tank
83002
+ item: sliceItem,
83003
+ attention
83021
83004
  });
83022
83005
  if (!this.tankChangeHeld(subject, deviceId, band)) return;
83023
- this.apply(subject, band !== "normal", this.deps.settings(), (facts, alerting) => incomingFromTankBand({
83006
+ this.apply(subject, band !== "normal", this.deps.settings(), (facts, alerting) => incomingFromProviderConsumableBand({
83024
83007
  facts,
83025
83008
  deviceId,
83026
- band: alerting ? band : "normal",
83027
- tank: tank.key,
83028
- ...tank.label !== void 0 ? { label: tank.label } : {},
83009
+ band: alerting ? "low" : "normal",
83010
+ item: {
83011
+ key: sliceItem.key,
83012
+ label: sliceItem.label,
83013
+ form: sliceItem.form
83014
+ },
83015
+ attention,
83029
83016
  identity: this.deps.lookup(deviceId)
83030
83017
  }), band === "normal" ? void 0 : band);
83031
83018
  }
83019
+ /** A leftover `tank:<deviceId>:<key>` row is closed silently when the item is seen as a consumable. */
83020
+ closeLegacyTankEpisode(deviceId, key) {
83021
+ const subject = tankSubject(deviceId, key);
83022
+ const row = this.deps.rows.get(subject);
83023
+ if (row === void 0 || !row.alerting) return;
83024
+ this.logger()?.info("legacy tank episode closed — the consumables cap owns this item now (D934)", {
83025
+ tags: { deviceId },
83026
+ meta: {
83027
+ subject,
83028
+ key
83029
+ }
83030
+ });
83031
+ this.deps.rows.put({
83032
+ ...row,
83033
+ alerting: false
83034
+ });
83035
+ this.lastProvider.delete(legacyConsumableSubject(deviceId, key));
83036
+ this.tankHold.delete(subject);
83037
+ this.tankHold.delete(legacyConsumableSubject(deviceId, key));
83038
+ }
83032
83039
  /**
83033
83040
  * Has this tank band held long enough to move its episode? A band that
83034
83041
  * would not move it (a repeat, or low ⇄ full inside one open episode) passes
@@ -83069,12 +83076,12 @@ var NcDeviceAlerts = class {
83069
83076
  const nowMs = this.now();
83070
83077
  for (const [subject, held] of [...this.tankHold]) {
83071
83078
  if (nowMs - held.since < NC_TANK_HOLD_MS) continue;
83072
- const reading = this.lastTank.get(subject);
83079
+ const reading = this.lastProvider.get(subject);
83073
83080
  if (reading === void 0) {
83074
83081
  this.tankHold.delete(subject);
83075
83082
  continue;
83076
83083
  }
83077
- this.observeTank(reading.deviceId, reading.tank);
83084
+ this.observeProviderConsumable(reading.deviceId, reading.item);
83078
83085
  }
83079
83086
  }
83080
83087
  /** One item against ONE threshold: its own episode, its own hysteresis. */
@@ -83198,14 +83205,14 @@ var NcDeviceAlerts = class {
83198
83205
  const subjects = this.deps.rows.all().map((row) => row.subject).filter((subject) => alertSubjectDeviceId(subject) === deviceId);
83199
83206
  for (const subject of subjects) {
83200
83207
  this.lastReading.delete(subject);
83201
- this.lastTank.delete(subject);
83208
+ this.lastProvider.delete(subject);
83202
83209
  this.offeredAt.delete(subject);
83203
83210
  await this.deps.rows.remove(subject);
83204
83211
  }
83205
83212
  this.lastReading.delete(`battery:${deviceId}`);
83206
83213
  for (const [key, reading] of this.lastConsumable) if (reading.deviceId === deviceId) this.lastConsumable.delete(key);
83207
83214
  for (const subject of this.tankHold.keys()) if (alertSubjectDeviceId(subject) === deviceId) this.tankHold.delete(subject);
83208
- for (const subject of this.lastTank.keys()) if (alertSubjectDeviceId(subject) === deviceId) this.lastTank.delete(subject);
83215
+ for (const subject of this.lastProvider.keys()) if (alertSubjectDeviceId(subject) === deviceId) this.lastProvider.delete(subject);
83209
83216
  return subjects.length;
83210
83217
  }
83211
83218
  /**
@@ -83282,8 +83289,23 @@ var NcDeviceAlerts = class {
83282
83289
  /** How to re-word a subject's alert from the last reading, or `null`. */
83283
83290
  restatementOf(subject) {
83284
83291
  const parts = parseConsumableSubject(subject);
83292
+ if (parts !== null && parts.legacy) {
83293
+ const provider = this.lastProvider.get(subject);
83294
+ if (provider !== void 0) return (facts) => incomingFromProviderConsumableBand({
83295
+ facts,
83296
+ deviceId: provider.deviceId,
83297
+ band: "low",
83298
+ item: {
83299
+ key: provider.item.key,
83300
+ label: provider.item.label,
83301
+ form: provider.item.form
83302
+ },
83303
+ attention: provider.attention,
83304
+ identity: this.deps.lookup(provider.deviceId)
83305
+ });
83306
+ }
83285
83307
  const consumable = parts === null || parts.legacy ? void 0 : this.lastConsumable.get(itemKeyOf(parts.deviceId, parts.key));
83286
- if (parts !== null && consumable !== void 0) return (facts) => incomingFromConsumableBand({
83308
+ if (parts !== null && !parts.legacy && consumable !== void 0) return (facts) => incomingFromConsumableBand({
83287
83309
  facts,
83288
83310
  deviceId: consumable.deviceId,
83289
83311
  band: "low",
@@ -83291,16 +83313,6 @@ var NcDeviceAlerts = class {
83291
83313
  thresholdPercent: parts.thresholdPercent,
83292
83314
  identity: this.deps.lookup(consumable.deviceId)
83293
83315
  });
83294
- const tank = this.lastTank.get(subject);
83295
- const tankBand = tank === void 0 ? null : deriveTankBand(tank.tank);
83296
- if (tank !== void 0 && (tankBand === "low" || tankBand === "full")) return (facts) => incomingFromTankBand({
83297
- facts,
83298
- deviceId: tank.deviceId,
83299
- band: tankBand,
83300
- tank: tank.tank.key,
83301
- ...tank.tank.label !== void 0 ? { label: tank.tank.label } : {},
83302
- identity: this.deps.lookup(tank.deviceId)
83303
- });
83304
83316
  const battery = this.lastReading.get(subject);
83305
83317
  if (battery !== void 0) return (facts) => incomingFromBatteryBand({
83306
83318
  facts,
@@ -83433,8 +83445,52 @@ function tagsOf(subject) {
83433
83445
  const deviceId = alertSubjectDeviceId(subject);
83434
83446
  return deviceId !== void 0 ? { tags: { deviceId } } : {};
83435
83447
  }
83448
+ function providerTankReading(item) {
83449
+ const base = {
83450
+ key: item.key,
83451
+ label: item.label,
83452
+ level: item.level,
83453
+ status: null
83454
+ };
83455
+ const status = item.status;
83456
+ if (item.kind === "supply") switch (status) {
83457
+ case "low":
83458
+ case "empty":
83459
+ case "replace": return {
83460
+ ...base,
83461
+ status: "low"
83462
+ };
83463
+ case "ok":
83464
+ case "full": return {
83465
+ ...base,
83466
+ status: "ok"
83467
+ };
83468
+ default: return base;
83469
+ }
83470
+ if (item.kind === "waste") switch (status) {
83471
+ case "full":
83472
+ case "replace": return {
83473
+ ...base,
83474
+ status: "full"
83475
+ };
83476
+ case "ok":
83477
+ case "low":
83478
+ case "empty": return {
83479
+ ...base,
83480
+ status: "ok"
83481
+ };
83482
+ default: return base;
83483
+ }
83484
+ return base;
83485
+ }
83436
83486
  //#endregion
83437
83487
  //#region src/notification-center/alerts/consumable-routing.ts
83488
+ /**
83489
+ * The `consumables` slice — one vocabulary for every form (D934).
83490
+ *
83491
+ * Every item is a consumable reading: wear parts judged at the rule's threshold,
83492
+ * supply and waste judged on the provider's verdict (held like a float switch).
83493
+ */
83438
83494
  function wearStatus(status) {
83439
83495
  switch (status) {
83440
83496
  case "replace":
@@ -83444,59 +83500,44 @@ function wearStatus(status) {
83444
83500
  default: return null;
83445
83501
  }
83446
83502
  }
83447
- function supplyStatus(status) {
83448
- switch (status) {
83449
- case "low":
83450
- case "empty":
83451
- case "replace": return "low";
83452
- case "ok":
83453
- case "full": return "ok";
83454
- default: return null;
83455
- }
83503
+ function consumableKindOf(raw) {
83504
+ return typeof raw.kind === "string" ? raw.kind : void 0;
83456
83505
  }
83457
- function wasteStatus(status) {
83458
- switch (status) {
83459
- case "full":
83460
- case "replace": return "full";
83461
- case "ok":
83462
- case "low":
83463
- case "empty": return "ok";
83464
- default: return null;
83465
- }
83506
+ function consumableFormOfRaw(raw) {
83507
+ const kind = consumableKindOf(raw);
83508
+ const formField = typeof raw.form === "string" ? raw.form : void 0;
83509
+ return consumableFormOf({
83510
+ ...kind === "wear" || kind === "supply" || kind === "waste" ? { kind } : {},
83511
+ ...formField === "part" || formField === "tank" || formField === "product" || formField === "bin" ? { form: formField } : {}
83512
+ });
83466
83513
  }
83467
- /** Which family judges this item, and the reading that family reads. */
83468
- function routeConsumableItem(raw) {
83469
- const { key, label, level } = raw;
83470
- if (raw.kind === "supply" || raw.kind === "waste") return {
83471
- family: "tank",
83472
- tank: {
83473
- key,
83474
- label,
83475
- level,
83476
- status: raw.kind === "supply" ? supplyStatus(raw.status) : wasteStatus(raw.status)
83477
- }
83478
- };
83514
+ /** Parse one slice entry into the unified consumable item shape. */
83515
+ function parseConsumableSliceItem(raw) {
83516
+ const kind = consumableKindOf(raw);
83517
+ const form = consumableFormOfRaw(raw);
83518
+ const status = typeof raw.status === "string" ? raw.status : null;
83519
+ const wear = kind === void 0 || kind === "wear" ? {
83520
+ key: raw.key,
83521
+ label: raw.label,
83522
+ level: raw.level,
83523
+ status: wearStatus(status)
83524
+ } : null;
83479
83525
  return {
83480
- family: "consumable",
83481
- item: {
83482
- key,
83483
- label,
83484
- level,
83485
- status: wearStatus(raw.status)
83486
- }
83526
+ key: raw.key,
83527
+ label: raw.label,
83528
+ level: raw.level,
83529
+ status,
83530
+ kind,
83531
+ form,
83532
+ wear
83487
83533
  };
83488
83534
  }
83489
83535
  function isRecord$1(value) {
83490
83536
  return typeof value === "object" && value !== null && !Array.isArray(value);
83491
83537
  }
83492
83538
  /**
83493
- * A `device.state-changed` carrying a `consumables` slice, routed by family;
83494
- * `null` for any other event, a malformed slice or an empty list.
83495
- *
83496
- * An item is kept only when it carries a usable `key` — the subject is built
83497
- * from it. `level` and `status` are each optional; an item with NEITHER is
83498
- * still returned, because deciding that it has told us nothing belongs to the
83499
- * band derivation and not to a parser (D393: absent is not zero).
83539
+ * A `device.state-changed` carrying a `consumables` slice; `null` for any other
83540
+ * event, a malformed slice or an empty list.
83500
83541
  */
83501
83542
  function consumablesSliceOf(event) {
83502
83543
  if (event.category !== "device.state-changed") return null;
@@ -83507,8 +83548,7 @@ function consumablesSliceOf(event) {
83507
83548
  if (typeof deviceId !== "number" || !isRecord$1(slice)) return null;
83508
83549
  const rawItems = slice["items"];
83509
83550
  if (!Array.isArray(rawItems)) return null;
83510
- const consumables = [];
83511
- const tanks = [];
83551
+ const items = [];
83512
83552
  for (const entry of rawItems) {
83513
83553
  if (!isRecord$1(entry)) continue;
83514
83554
  const key = entry["key"];
@@ -83517,21 +83557,20 @@ function consumablesSliceOf(event) {
83517
83557
  const level = entry["level"];
83518
83558
  const status = entry["status"];
83519
83559
  const kind = entry["kind"];
83520
- const routed = routeConsumableItem({
83560
+ const form = entry["form"];
83561
+ items.push(parseConsumableSliceItem({
83521
83562
  key,
83522
83563
  label: typeof label === "string" && label.length > 0 ? label : key,
83523
83564
  level: typeof level === "number" && Number.isFinite(level) ? level : null,
83524
83565
  status: typeof status === "string" ? status : null,
83525
- kind: typeof kind === "string" ? kind : void 0
83526
- });
83527
- if (routed.family === "tank") tanks.push(routed.tank);
83528
- else consumables.push(routed.item);
83566
+ kind: typeof kind === "string" ? kind : void 0,
83567
+ form: typeof form === "string" ? form : void 0
83568
+ }));
83529
83569
  }
83530
- if (consumables.length === 0 && tanks.length === 0) return null;
83570
+ if (items.length === 0) return null;
83531
83571
  return {
83532
83572
  deviceId,
83533
- consumables,
83534
- tanks
83573
+ items
83535
83574
  };
83536
83575
  }
83537
83576
  //#endregion
@@ -85179,8 +85218,6 @@ var NcDeviceStateCache = class {
85179
85218
  * a job that COMPLETED, and the state it leaves behind is not a condition that
85180
85219
  * is still true. The ADR records why it is excluded rather than approximated.
85181
85220
  */
85182
- /** One source: the cap's Zod enum (D698 LOW-6). */
85183
- var NC_STATE_SUMMARY_SECTION_IDS = NcStateSummarySectionIdSchema.options;
85184
85221
  /** The kind groups that have no section, and why — said on the preview. */
85185
85222
  var NC_STATE_SUMMARY_EXCLUDED_GROUPS = { "backups-exports": "a backup or an export is a completed job, not a state that is still true" };
85186
85223
  function isStateSectionId(value) {
@@ -85192,7 +85229,6 @@ var NC_STATE_SECTION_VAR = {
85192
85229
  streams: "summaryStreams",
85193
85230
  battery: "summaryBattery",
85194
85231
  consumables: "summaryConsumables",
85195
- tanks: "summaryTanks",
85196
85232
  "detection-blind": "summaryDetection",
85197
85233
  nodes: "summaryNodes",
85198
85234
  addons: "summaryAddons",
@@ -85210,11 +85246,13 @@ function sectionKinds(section) {
85210
85246
  * direction, only the event did.
85211
85247
  */
85212
85248
  function sectionsOfRule(condition) {
85213
- return NC_STATE_SUMMARY_SECTION_IDS.filter((section) => sectionKinds(section).some((kind) => condition.kinds.includes(kind)));
85249
+ const kinds = condition.kinds;
85250
+ return NC_STATE_SUMMARY_SECTION_IDS.filter((section) => sectionKinds(section).some((kind) => kinds.includes(kind)));
85214
85251
  }
85215
85252
  /** The kind groups the rule selected that have no section. */
85216
85253
  function excludedGroupsOfRule(condition) {
85217
- return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((group) => NC_STATE_SUMMARY_EXCLUDED_GROUPS[group.id] !== void 0 && group.kinds.some((kind) => condition.kinds.includes(kind))).map((group) => group.id);
85254
+ const kinds = condition.kinds;
85255
+ return NC_SYSTEM_EVENT_KIND_GROUP_SPECS.filter((group) => NC_STATE_SUMMARY_EXCLUDED_GROUPS[group.id] !== void 0 && group.kinds.some((kind) => kinds.includes(kind))).map((group) => group.id);
85218
85256
  }
85219
85257
  /**
85220
85258
  * The condition the summary judges its items with: the rule's own, with its
@@ -89961,6 +89999,12 @@ function classCounts(members) {
89961
89999
  * params go last because they are the row's own truth about a system event and
89962
90000
  * outrank the live vocabulary for exactly those names.
89963
90001
  */
90002
+ /** Intake-only keys for body variant selection — not operator template vars (D934). */
90003
+ function systemEventParamsForTemplate(params) {
90004
+ if (params === void 0) return {};
90005
+ const { form: _form, attention: _attention, ...rest } = params;
90006
+ return rest;
90007
+ }
89964
90008
  function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
89965
90009
  const subject = entry.payload.subject;
89966
90010
  const occupancy = subject.occupancy;
@@ -90008,7 +90052,7 @@ function buildTemplateVars(texts, entry, deviceName, zoneLabels, timeZone) {
90008
90052
  latestVersion: typeof systemEvent?.data["latestVersion"] === "string" ? systemEvent.data["latestVersion"] : "",
90009
90053
  ...systemEvent?.deviceId !== void 0 ? { deviceName: String(systemEvent.deviceId) } : {},
90010
90054
  ...systemEvent?.nodeId !== void 0 && (systemEvent.kind === "node-online" || systemEvent.kind === "node-offline") ? { nodeName: systemEvent.nodeId } : {},
90011
- ...systemEvent?.textParams
90055
+ ...systemEventParamsForTemplate(systemEvent?.textParams)
90012
90056
  };
90013
90057
  }
90014
90058
  /**
@@ -99471,15 +99515,30 @@ function deviceParams(deviceId, identity) {
99471
99515
  deviceName: name.length > 0 ? name : String(deviceId)
99472
99516
  };
99473
99517
  }
99474
- function alertItem(section, open) {
99518
+ function consumableSummaryLineKey(params, measured) {
99519
+ const form = params["form"] ?? "part";
99520
+ if (form === "part") return measured && params["percentage"] !== void 0 ? "summary.line.consumable.part" : "summary.line.consumable.part.unmeasured";
99521
+ if (form === "tank") return params["attention"] === "full" ? "summary.line.consumable.tank-full" : "summary.line.consumable.tank";
99522
+ if (form === "product") return "summary.line.consumable.product";
99523
+ return "summary.line.consumable.bin";
99524
+ }
99525
+ function alertItem(section, open, lookup) {
99475
99526
  const params = open.subject.textParams ?? {};
99476
- const unreadTank = open.family === "tank" && !open.measured;
99527
+ const lineKey = section === "consumables" ? consumableSummaryLineKey(params, open.measured) : open.subject.bodyKey ?? "";
99528
+ const lineParams = section === "consumables" ? (({ deviceName: _deviceName, ...rest }) => rest)(params) : params;
99529
+ const deviceId = open.subject.deviceId;
99530
+ const identity = deviceId !== void 0 ? lookup(deviceId) : void 0;
99531
+ const deviceName = params["deviceName"] ?? (identity !== void 0 && identity.name.length > 0 ? identity.name : deviceId !== void 0 ? String(deviceId) : void 0);
99477
99532
  return {
99478
99533
  section,
99479
99534
  subject: open.subject,
99480
- lineKey: unreadTank ? "summary.line.tank-unread" : open.subject.bodyKey ?? "",
99481
- params,
99482
- since: open.since
99535
+ lineKey,
99536
+ params: lineParams,
99537
+ since: open.since,
99538
+ ...section === "consumables" && deviceId !== void 0 && deviceName !== void 0 ? { device: {
99539
+ id: deviceId,
99540
+ name: deviceName
99541
+ } } : {}
99483
99542
  };
99484
99543
  }
99485
99544
  function livenessItem(row, lookup) {
@@ -99608,7 +99667,7 @@ function stateItem(row, lookup, nodes) {
99608
99667
  * (`parentDeviceId` set) still owns those alerts — filtering every accessory
99609
99668
  * here dropped every tank line of the 2026-10-05 evening summary for
99610
99669
  * deviceId 4385 while the episode was open and the instant path had already
99611
- * held `device-tank-low` for that rule.
99670
+ * held a legacy tank kind for that rule.
99612
99671
  */
99613
99672
  function stillTrue(item, ports) {
99614
99673
  const deviceId = item.subject.deviceId;
@@ -99648,16 +99707,16 @@ function readSection(section, ports, nowMs) {
99648
99707
  }
99649
99708
  /** Which ports a section needs, and why each one is unavailable. */
99650
99709
  function readRaw(section, ports, nowMs) {
99651
- if (section === "battery" || section === "consumables" || section === "tanks") {
99710
+ if (section === "battery" || section === "consumables") {
99652
99711
  const alerts = ports.alerts();
99653
99712
  if (alerts === null) return {
99654
99713
  answered: false,
99655
99714
  reason: UNREAD_ALERTS
99656
99715
  };
99657
- const family = section === "battery" ? "battery" : section === "tanks" ? "tank" : "consumable";
99716
+ const family = section === "battery" ? "battery" : "consumable";
99658
99717
  return {
99659
99718
  answered: true,
99660
- items: alerts.filter((open) => open.family === family).map((open) => alertItem(section, open))
99719
+ items: alerts.filter((open) => open.family === family).map((open) => alertItem(section, open, ports.lookup))
99661
99720
  };
99662
99721
  }
99663
99722
  const state = ports.state();
@@ -99817,11 +99876,12 @@ function wordsOf(input, section) {
99817
99876
  /** One bullet per item; an item with a `node` is listed under its node's heading (D708). */
99818
99877
  function linesOf(input, items) {
99819
99878
  const bullet = (item) => `• ${lineOf(input, item)}`;
99820
- if (!items.some((item) => item.node !== void 0)) return items.map(bullet).join("\n");
99821
- return groupByNode(items, input.texts.language).flatMap((group) => [input.texts.text({
99879
+ if (items.some((item) => item.node !== void 0)) return groupByNode(items, input.texts.language).flatMap((group) => [input.texts.text({
99822
99880
  key: "summary.node.heading",
99823
99881
  vars: { node: group.node.name }
99824
99882
  }), ...group.items.map(bullet)]).join("\n");
99883
+ if (items.some((item) => item.device !== void 0)) return groupByDevice(items, input.texts.language).flatMap((group) => [`${group.device.name}:`, ...group.items.map(bullet)]).join("\n");
99884
+ return items.map(bullet).join("\n");
99825
99885
  }
99826
99886
  /**
99827
99887
  * The items cut per node, in a STABLE order: the hub first, then by name, then
@@ -99846,6 +99906,21 @@ var HUB_HEADING = {
99846
99906
  name: "hub",
99847
99907
  isHub: true
99848
99908
  };
99909
+ /** Consumables items under each device, devices ordered by name (D934). */
99910
+ function groupByDevice(items, language) {
99911
+ const groups = /* @__PURE__ */ new Map();
99912
+ for (const item of items) {
99913
+ const device = item.device;
99914
+ if (device === void 0) continue;
99915
+ const group = groups.get(device.id);
99916
+ if (group === void 0) groups.set(device.id, {
99917
+ device,
99918
+ items: [item]
99919
+ });
99920
+ else group.items.push(item);
99921
+ }
99922
+ return [...groups.values()].toSorted((a, b) => a.device.name.localeCompare(b.device.name, language));
99923
+ }
99849
99924
  function stateSummaryText(input) {
99850
99925
  const sectionVars = {};
99851
99926
  const sections = [];
@@ -103912,8 +103987,7 @@ var NotificationCenter = class {
103912
103987
  }
103913
103988
  const consumables = consumablesSliceOf(event);
103914
103989
  if (consumables !== null) {
103915
- for (const item of consumables.consumables) this.deviceAlerts.observeConsumable(consumables.deviceId, item);
103916
- for (const tank of consumables.tanks) this.deviceAlerts.observeTank(consumables.deviceId, tank);
103990
+ for (const item of consumables.items) this.deviceAlerts.observeConsumableItem(consumables.deviceId, item);
103917
103991
  return;
103918
103992
  }
103919
103993
  const lookup = this.deviceDirectory.lookup();