@camstack/system 1.2.243 → 1.2.245
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +315 -7
- package/dist/builtins/device-manager/device-manager.addon.mjs +315 -7
- package/dist/builtins/device-manager/device-meta-actions.d.ts +65 -0
- package/dist/builtins/device-manager/device-meta-types.d.ts +49 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +2 -2
- package/dist/builtins/snapshot/index.mjs +2 -2
- package/dist/builtins/snapshot/snapshot-courtesy.d.ts +12 -2
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-B4htkDVv.mjs → dist-4MS1GNc8.mjs} +206 -1
- package/dist/{dist-D6kqBW1G.js → dist-Cz6h6Nc3.js} +211 -0
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{retired-settings-keys-BPIyHR4_.js → retired-settings-keys-DBrIdOQp.js} +1 -1
- package/dist/{retired-settings-keys-C9w4b4TV.mjs → retired-settings-keys-D_5MkKII.mjs} +1 -1
- package/dist/{snapshot-courtesy-LoJNgHHZ.mjs → snapshot-courtesy-CZPvteq2.mjs} +34 -7
- package/dist/{snapshot-courtesy-oeTWCXPv.js → snapshot-courtesy-Cqs7GkOV.js} +34 -7
- package/package.json +1 -1
|
@@ -11433,6 +11433,81 @@ var LocationIconAssignmentSchema = zod.z.object({
|
|
|
11433
11433
|
function locationIconKey(label) {
|
|
11434
11434
|
return label.trim().toLowerCase();
|
|
11435
11435
|
}
|
|
11436
|
+
/**
|
|
11437
|
+
* One (location, description) assignment as it travels.
|
|
11438
|
+
*
|
|
11439
|
+
* Same key discipline as {@link LocationIconAssignmentSchema}, and for the same
|
|
11440
|
+
* reason: this lived for a while in the post-analysis addon's own settings,
|
|
11441
|
+
* keyed by a HAND-TYPED label matched exactly and case-sensitively. A rename in
|
|
11442
|
+
* the Locations page carried the icon and silently orphaned the description —
|
|
11443
|
+
* two authorities for one thing, joined by two different rules. Now both hang
|
|
11444
|
+
* off the location itself, on `locationIconKey`.
|
|
11445
|
+
*/
|
|
11446
|
+
var LocationDescriptionAssignmentSchema = zod.z.object({
|
|
11447
|
+
locationKey: zod.z.string(),
|
|
11448
|
+
description: zod.z.string()
|
|
11449
|
+
});
|
|
11450
|
+
/**
|
|
11451
|
+
* A SITE ZONE — a named part of the property that several locations belong to.
|
|
11452
|
+
*
|
|
11453
|
+
* "Zona notte" holds every bedroom; "Zona giorno" the living room and the
|
|
11454
|
+
* kitchen. A camera belongs to exactly one location, and a location to at most
|
|
11455
|
+
* one zone, so a camera's zone is derived and never stored on the device: the
|
|
11456
|
+
* whole point of this entity is that grouping rooms touches no device row.
|
|
11457
|
+
*
|
|
11458
|
+
* ## Why not `zone`
|
|
11459
|
+
*
|
|
11460
|
+
* Because `zone` is already the most loaded word in this product: 612 source
|
|
11461
|
+
* files and 24 Italian strings where it means a DETECTION zone drawn on a
|
|
11462
|
+
* camera. The two are unrelated — one is a polygon in a frame, the other is a
|
|
11463
|
+
* set of rooms — and a reader who confuses them will look for a camera's
|
|
11464
|
+
* bedrooms. So the code says `siteZone` everywhere, and the UI frees the plain
|
|
11465
|
+
* word by calling the drawn one what it actually is: a detection zone.
|
|
11466
|
+
*
|
|
11467
|
+
* ## The key
|
|
11468
|
+
*
|
|
11469
|
+
* `siteZoneKey(label)` — trimmed, lower-cased, the SAME derivation locations
|
|
11470
|
+
* use (`locationIconKey`). Membership and the icon both hang off it, so a
|
|
11471
|
+
* case-only rename keeps them, which is exactly the orphaning that cost us a
|
|
11472
|
+
* silently-dropped location description.
|
|
11473
|
+
*
|
|
11474
|
+
* The icon vocabulary is the location one, unchanged: a zone is drawn on the
|
|
11475
|
+
* same two surfaces by the same glyph map, so a second vocabulary would be a
|
|
11476
|
+
* second thing to keep drawable (`scripts/check-location-icons-drawable.ts`).
|
|
11477
|
+
*/
|
|
11478
|
+
/**
|
|
11479
|
+
* One zone as it travels: its key, the label the operator typed, its icon and
|
|
11480
|
+
* the locations inside it.
|
|
11481
|
+
*
|
|
11482
|
+
* The LABEL is carried alongside the key because the key is lossy on purpose —
|
|
11483
|
+
* a surface must show "Zona notte", not "zona notte". Locations are carried
|
|
11484
|
+
* here, rather than left to a second call, because every consumer wants both
|
|
11485
|
+
* at once and a viewer that asked twice could render a zone whose membership
|
|
11486
|
+
* it has not fetched yet.
|
|
11487
|
+
*
|
|
11488
|
+
* `icon: null` means nobody chose one, which is NOT the fallback pin: only one
|
|
11489
|
+
* of those two is a choice, and a picker has to draw them differently.
|
|
11490
|
+
*/
|
|
11491
|
+
var SiteZoneSchema = zod.z.object({
|
|
11492
|
+
zoneKey: zod.z.string(),
|
|
11493
|
+
label: zod.z.string(),
|
|
11494
|
+
icon: LocationIconIdSchema.nullable(),
|
|
11495
|
+
/** Folded location keys, sorted. A location listed here need not be
|
|
11496
|
+
* REGISTERED — devices carry labels nobody registered, and those rooms are
|
|
11497
|
+
* the ones that most want grouping. */
|
|
11498
|
+
locationKeys: zod.z.array(zod.z.string())
|
|
11499
|
+
});
|
|
11500
|
+
/**
|
|
11501
|
+
* The join key for a zone label: trimmed, lower-cased.
|
|
11502
|
+
*
|
|
11503
|
+
* Deliberately a separate export from `locationIconKey` even though the
|
|
11504
|
+
* derivation is identical — they key different maps, and a future change to one
|
|
11505
|
+
* must not silently re-key the other. Their agreement is a fact about today,
|
|
11506
|
+
* not a dependency.
|
|
11507
|
+
*/
|
|
11508
|
+
function siteZoneKey(label) {
|
|
11509
|
+
return label.trim().toLowerCase();
|
|
11510
|
+
}
|
|
11436
11511
|
var ProviderStatusSchema = zod.z.object({
|
|
11437
11512
|
connected: zod.z.boolean(),
|
|
11438
11513
|
deviceCount: zod.z.number(),
|
|
@@ -12233,6 +12308,94 @@ var deviceManagerCapability = {
|
|
|
12233
12308
|
kind: "mutation",
|
|
12234
12309
|
auth: "admin"
|
|
12235
12310
|
}),
|
|
12311
|
+
/** Every location that carries a DESCRIPTION — what the place LOOKS like,
|
|
12312
|
+
* in plain words, given to the vision model alongside the pictures so it
|
|
12313
|
+
* knows what it is looking at. A driveway of cobbles between a brick wall
|
|
12314
|
+
* and three parking spaces reads very differently from a back garden.
|
|
12315
|
+
*
|
|
12316
|
+
* Same additive shape and same join key as `listLocationIcons`, and that
|
|
12317
|
+
* is the point of it being here at all: this lived in the post-analysis
|
|
12318
|
+
* addon's own settings, keyed by a hand-typed label matched exactly and
|
|
12319
|
+
* case-sensitively, while the icon hung off the location itself. A rename
|
|
12320
|
+
* carried the icon and silently orphaned the description. One location,
|
|
12321
|
+
* one authority, one key.
|
|
12322
|
+
*
|
|
12323
|
+
* Absent means nobody wrote one — the digest then tells the model only the
|
|
12324
|
+
* location NAME, exactly as it did before descriptions existed. */
|
|
12325
|
+
listLocationDescriptions: method(zod.z.void(), zod.z.array(LocationDescriptionAssignmentSchema)),
|
|
12326
|
+
/** Set (or clear) a location's description. `description: null`, or a
|
|
12327
|
+
* blank string, removes it — an empty description and no description are
|
|
12328
|
+
* the same thing to every reader, and storing the first would send an
|
|
12329
|
+
* empty line to the model.
|
|
12330
|
+
*
|
|
12331
|
+
* Like `setLocationIcon`, the label need NOT be registered, and an
|
|
12332
|
+
* empty / whitespace-only LABEL throws. */
|
|
12333
|
+
setLocationDescription: method(zod.z.object({
|
|
12334
|
+
location: zod.z.string(),
|
|
12335
|
+
description: zod.z.string().max(400).nullable()
|
|
12336
|
+
}), zod.z.void(), {
|
|
12337
|
+
kind: "mutation",
|
|
12338
|
+
auth: "admin"
|
|
12339
|
+
}),
|
|
12340
|
+
/** Every SITE ZONE — a named part of the property that groups locations
|
|
12341
|
+
* ("Zona notte" holds the bedrooms). One call answers the whole picture:
|
|
12342
|
+
* key, the label as typed, the icon, and the locations inside.
|
|
12343
|
+
*
|
|
12344
|
+
* Together, because a surface that asked for membership separately could
|
|
12345
|
+
* draw a zone whose rooms it has not fetched — and the strip that renders
|
|
12346
|
+
* these puts zones BEFORE locations, so a half-known zone is the first
|
|
12347
|
+
* thing an operator sees.
|
|
12348
|
+
*
|
|
12349
|
+
* Nothing here touches a device: a camera's zone is derived from its
|
|
12350
|
+
* location, never stored on the row. Grouping rooms must not rewrite the
|
|
12351
|
+
* fleet. */
|
|
12352
|
+
listSiteZones: method(zod.z.void(), zod.z.array(SiteZoneSchema)),
|
|
12353
|
+
/** Create a zone, or set the icon of one that exists. `icon: null` clears
|
|
12354
|
+
* the icon and KEEPS the zone — a zone with no icon is a normal zone, and
|
|
12355
|
+
* conflating "no icon" with "no zone" would delete membership on a
|
|
12356
|
+
* mis-click.
|
|
12357
|
+
*
|
|
12358
|
+
* Idempotent, and the label is stored as typed: the key is folded, so
|
|
12359
|
+
* re-running this with different casing re-labels rather than duplicating. */
|
|
12360
|
+
setSiteZone: method(zod.z.object({
|
|
12361
|
+
name: zod.z.string(),
|
|
12362
|
+
icon: LocationIconIdSchema.nullable().optional()
|
|
12363
|
+
}), zod.z.void(), {
|
|
12364
|
+
kind: "mutation",
|
|
12365
|
+
auth: "admin"
|
|
12366
|
+
}),
|
|
12367
|
+
/** Delete a zone. Its LOCATIONS survive — they simply stop belonging to
|
|
12368
|
+
* anything. Deleting a grouping must never delete what was grouped. */
|
|
12369
|
+
removeSiteZone: method(zod.z.object({ name: zod.z.string() }), zod.z.void(), {
|
|
12370
|
+
kind: "mutation",
|
|
12371
|
+
auth: "admin"
|
|
12372
|
+
}),
|
|
12373
|
+
/** Rename a zone, carrying its icon AND its membership.
|
|
12374
|
+
*
|
|
12375
|
+
* It exists so that renaming is not delete-then-recreate, which would drop
|
|
12376
|
+
* both — the exact orphaning that lost a location's description when the
|
|
12377
|
+
* two hung off different keys. Renaming onto an existing zone MERGES into
|
|
12378
|
+
* it and the target keeps its own icon. */
|
|
12379
|
+
renameSiteZone: method(zod.z.object({
|
|
12380
|
+
from: zod.z.string(),
|
|
12381
|
+
to: zod.z.string()
|
|
12382
|
+
}), zod.z.void(), {
|
|
12383
|
+
kind: "mutation",
|
|
12384
|
+
auth: "admin"
|
|
12385
|
+
}),
|
|
12386
|
+
/** Put a location in a zone, or take it out with `zone: null`.
|
|
12387
|
+
*
|
|
12388
|
+
* At most one zone per location, by construction: this writes one key, so
|
|
12389
|
+
* there is no state in which a room is in two places at once. The zone
|
|
12390
|
+
* need not exist yet — it is created empty-labelled from this call, the
|
|
12391
|
+
* same way `setLocationIcon` does not require a registered location. */
|
|
12392
|
+
setLocationZone: method(zod.z.object({
|
|
12393
|
+
location: zod.z.string(),
|
|
12394
|
+
zone: zod.z.string().nullable()
|
|
12395
|
+
}), zod.z.void(), {
|
|
12396
|
+
kind: "mutation",
|
|
12397
|
+
auth: "admin"
|
|
12398
|
+
}),
|
|
12236
12399
|
/** Soft-disable / re-enable the device. Drivers consult
|
|
12237
12400
|
* `BaseDevice.disabled` to gate lifecycle hooks. */
|
|
12238
12401
|
setDisabled: method(zod.z.object({
|
|
@@ -39056,6 +39219,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
39056
39219
|
addonId: null,
|
|
39057
39220
|
access: "view"
|
|
39058
39221
|
},
|
|
39222
|
+
"deviceManager.listLocationDescriptions": {
|
|
39223
|
+
capName: "device-manager",
|
|
39224
|
+
capScope: "system",
|
|
39225
|
+
addonId: null,
|
|
39226
|
+
access: "view"
|
|
39227
|
+
},
|
|
39059
39228
|
"deviceManager.listLocationIcons": {
|
|
39060
39229
|
capName: "device-manager",
|
|
39061
39230
|
capScope: "system",
|
|
@@ -39074,6 +39243,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
39074
39243
|
addonId: null,
|
|
39075
39244
|
access: "view"
|
|
39076
39245
|
},
|
|
39246
|
+
"deviceManager.listSiteZones": {
|
|
39247
|
+
capName: "device-manager",
|
|
39248
|
+
capScope: "system",
|
|
39249
|
+
addonId: null,
|
|
39250
|
+
access: "view"
|
|
39251
|
+
},
|
|
39077
39252
|
"deviceManager.listWrappersForCap": {
|
|
39078
39253
|
capName: "device-manager",
|
|
39079
39254
|
capScope: "system",
|
|
@@ -39158,12 +39333,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
39158
39333
|
addonId: null,
|
|
39159
39334
|
access: "delete"
|
|
39160
39335
|
},
|
|
39336
|
+
"deviceManager.removeSiteZone": {
|
|
39337
|
+
capName: "device-manager",
|
|
39338
|
+
capScope: "system",
|
|
39339
|
+
addonId: null,
|
|
39340
|
+
access: "delete"
|
|
39341
|
+
},
|
|
39161
39342
|
"deviceManager.renameLocation": {
|
|
39162
39343
|
capName: "device-manager",
|
|
39163
39344
|
capScope: "system",
|
|
39164
39345
|
addonId: null,
|
|
39165
39346
|
access: "create"
|
|
39166
39347
|
},
|
|
39348
|
+
"deviceManager.renameSiteZone": {
|
|
39349
|
+
capName: "device-manager",
|
|
39350
|
+
capScope: "system",
|
|
39351
|
+
addonId: null,
|
|
39352
|
+
access: "create"
|
|
39353
|
+
},
|
|
39167
39354
|
"deviceManager.runDeviceAction": {
|
|
39168
39355
|
capName: "device-manager",
|
|
39169
39356
|
capScope: "system",
|
|
@@ -39206,12 +39393,24 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
39206
39393
|
addonId: null,
|
|
39207
39394
|
access: "create"
|
|
39208
39395
|
},
|
|
39396
|
+
"deviceManager.setLocationDescription": {
|
|
39397
|
+
capName: "device-manager",
|
|
39398
|
+
capScope: "system",
|
|
39399
|
+
addonId: null,
|
|
39400
|
+
access: "create"
|
|
39401
|
+
},
|
|
39209
39402
|
"deviceManager.setLocationIcon": {
|
|
39210
39403
|
capName: "device-manager",
|
|
39211
39404
|
capScope: "system",
|
|
39212
39405
|
addonId: null,
|
|
39213
39406
|
access: "create"
|
|
39214
39407
|
},
|
|
39408
|
+
"deviceManager.setLocationZone": {
|
|
39409
|
+
capName: "device-manager",
|
|
39410
|
+
capScope: "system",
|
|
39411
|
+
addonId: null,
|
|
39412
|
+
access: "create"
|
|
39413
|
+
},
|
|
39215
39414
|
"deviceManager.setMetadata": {
|
|
39216
39415
|
capName: "device-manager",
|
|
39217
39416
|
capScope: "system",
|
|
@@ -39242,6 +39441,12 @@ var METHOD_ACCESS_MAP = Object.freeze({
|
|
|
39242
39441
|
addonId: null,
|
|
39243
39442
|
access: "create"
|
|
39244
39443
|
},
|
|
39444
|
+
"deviceManager.setSiteZone": {
|
|
39445
|
+
capName: "device-manager",
|
|
39446
|
+
capScope: "system",
|
|
39447
|
+
addonId: null,
|
|
39448
|
+
access: "create"
|
|
39449
|
+
},
|
|
39245
39450
|
"deviceManager.setStreamProfileMap": {
|
|
39246
39451
|
capName: "device-manager",
|
|
39247
39452
|
capScope: "system",
|
|
@@ -47487,6 +47692,12 @@ Object.defineProperty(exports, "settingsStoreCapability", {
|
|
|
47487
47692
|
return settingsStoreCapability;
|
|
47488
47693
|
}
|
|
47489
47694
|
});
|
|
47695
|
+
Object.defineProperty(exports, "siteZoneKey", {
|
|
47696
|
+
enumerable: true,
|
|
47697
|
+
get: function() {
|
|
47698
|
+
return siteZoneKey;
|
|
47699
|
+
}
|
|
47700
|
+
});
|
|
47490
47701
|
Object.defineProperty(exports, "sleep", {
|
|
47491
47702
|
enumerable: true,
|
|
47492
47703
|
get: function() {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-Cz6h6Nc3.js");
|
|
4
4
|
const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
|
|
5
5
|
require("./builtins/alerts/index.js");
|
|
6
6
|
const require_formatter = require("./formatter-DqAKDlvN.js");
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $t as
|
|
2
|
+
import { $t as DEVICE_SETTINGS_CONTRIBUTION_METHODS, Cn as EventCategory$1, Ft as scopesAllowAddon, It as scopesAllowDeviceCap, Nt as resolveMethodAuth, Ot as procedureAuthKey, Qt as DATAPLANE_SECRET_HEADER$1, Tt as objectInputDeclaresAddonId, Xt as errMsg$1, _n as parseJsonObject, an as ReadinessTimeoutError, at as isArrayOutputSchema, bn as resolveCapMount, cn as asNumber, ct as isObjectInput, dn as emitDownForOwnedCaps, dt as isVoidInput, en as DEVICE_STATUS_METHOD, ft as kebabToCamel, g as METHOD_ACCESS_MAP, in as ReadinessRegistry, j as addonSettingsCapability, ln as asString$1, mt as lifecycleJobSchema, ot as isCollectionArrayMethod, p as LOG_LEVEL_RANK, pn as expandCapMethods, sn as asJsonObject$1, t as ALL_CAPABILITY_DEFINITIONS, tt as extractNestedAddonId, un as createEvent, v as RUNTIME_DEFAULTS, vn as parseJsonUnknown, vt as logLevelAtMost, xn as scopeKey, yn as readinessKey, yt as looseSchema } from "./dist-4MS1GNc8.mjs";
|
|
3
3
|
import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
|
|
4
4
|
import "./builtins/alerts/index.mjs";
|
|
5
5
|
import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
|
|
@@ -76,26 +76,53 @@ function escapeCourtesyText(value) {
|
|
|
76
76
|
* frame read the same, with a floor so a thumbnail stays legible.
|
|
77
77
|
*/
|
|
78
78
|
function buildCourtesySvg(spec) {
|
|
79
|
-
return buildStateSvg(spec
|
|
79
|
+
return buildStateSvg(spec);
|
|
80
80
|
}
|
|
81
81
|
/**
|
|
82
|
-
* The
|
|
83
|
-
*
|
|
82
|
+
* The state treatment for a frame that HAS a picture under it: a band along the
|
|
83
|
+
* bottom, and nothing else touched.
|
|
84
|
+
*
|
|
85
|
+
* It was a full-frame wash at 0.62 opacity, under a docblock claiming the frame
|
|
86
|
+
* "remains visible underneath". A 38 % view of a dark room is not a view of it,
|
|
87
|
+
* and the operator read the result as a placeholder rather than as their own
|
|
88
|
+
* camera: *"ma perché Waking? Dovremmo mostrare l'ultimo snapshot
|
|
89
|
+
* disponibile"* (Baby monitor, 2026-09-16).
|
|
90
|
+
*
|
|
91
|
+
* The marker still has to earn its place — a stale frame shown clean gets
|
|
92
|
+
* mistaken for live, which is the reason the treatment exists at all. A band
|
|
93
|
+
* says the same thing a wash said. Covering the picture was never the message.
|
|
84
94
|
*/
|
|
85
95
|
function buildCourtesyOverlaySvg(spec) {
|
|
86
|
-
|
|
96
|
+
const state = escapeCourtesyText(courtesyLabel(spec.reason));
|
|
97
|
+
const name = escapeCourtesyText(spec.deviceName);
|
|
98
|
+
const stateSize = Math.max(11, Math.round(spec.width / 34));
|
|
99
|
+
const bandHeight = Math.max(26, Math.round(stateSize * 2.2));
|
|
100
|
+
const bandTop = Math.max(0, spec.height - bandHeight);
|
|
101
|
+
const baselineY = Math.round(bandTop + bandHeight * .68);
|
|
102
|
+
const textStroke = Math.max(1, Math.round(spec.width / 320));
|
|
103
|
+
return [
|
|
104
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
|
|
105
|
+
`<rect x="0" y="${String(bandTop)}" width="${String(spec.width)}" height="${String(bandHeight)}" fill="${courtesyBackground(spec.reason)}" fill-opacity="0.82"/>`,
|
|
106
|
+
`<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
|
|
107
|
+
`<text x="50%" y="${String(baselineY)}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}`,
|
|
108
|
+
`<tspan fill="#9a9aa8"> · ${name}</tspan>`,
|
|
109
|
+
`</text></g></svg>`
|
|
110
|
+
].join("");
|
|
87
111
|
}
|
|
88
|
-
|
|
112
|
+
/**
|
|
113
|
+
* The GENERATED card: no picture exists, so this IS the picture. Opaque by
|
|
114
|
+
* necessity — anything translucent here shows black through it.
|
|
115
|
+
*/
|
|
116
|
+
function buildStateSvg(spec) {
|
|
89
117
|
const stateSize = Math.max(12, Math.round(spec.width / 12));
|
|
90
118
|
const nameSize = Math.max(9, Math.round(spec.width / 26));
|
|
91
119
|
const state = escapeCourtesyText(courtesyLabel(spec.reason));
|
|
92
120
|
const name = escapeCourtesyText(spec.deviceName);
|
|
93
121
|
const midY = spec.height / 2;
|
|
94
|
-
const washOpacity = overlay ? "0.62" : "1";
|
|
95
122
|
const textStroke = Math.max(1, Math.round(spec.width / 320));
|
|
96
123
|
return [
|
|
97
124
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
|
|
98
|
-
`<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="
|
|
125
|
+
`<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="1"/>`,
|
|
99
126
|
`<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
|
|
100
127
|
`<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
|
|
101
128
|
`<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
|
|
@@ -78,26 +78,53 @@ function escapeCourtesyText(value) {
|
|
|
78
78
|
* frame read the same, with a floor so a thumbnail stays legible.
|
|
79
79
|
*/
|
|
80
80
|
function buildCourtesySvg(spec) {
|
|
81
|
-
return buildStateSvg(spec
|
|
81
|
+
return buildStateSvg(spec);
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
|
-
* The
|
|
85
|
-
*
|
|
84
|
+
* The state treatment for a frame that HAS a picture under it: a band along the
|
|
85
|
+
* bottom, and nothing else touched.
|
|
86
|
+
*
|
|
87
|
+
* It was a full-frame wash at 0.62 opacity, under a docblock claiming the frame
|
|
88
|
+
* "remains visible underneath". A 38 % view of a dark room is not a view of it,
|
|
89
|
+
* and the operator read the result as a placeholder rather than as their own
|
|
90
|
+
* camera: *"ma perché Waking? Dovremmo mostrare l'ultimo snapshot
|
|
91
|
+
* disponibile"* (Baby monitor, 2026-09-16).
|
|
92
|
+
*
|
|
93
|
+
* The marker still has to earn its place — a stale frame shown clean gets
|
|
94
|
+
* mistaken for live, which is the reason the treatment exists at all. A band
|
|
95
|
+
* says the same thing a wash said. Covering the picture was never the message.
|
|
86
96
|
*/
|
|
87
97
|
function buildCourtesyOverlaySvg(spec) {
|
|
88
|
-
|
|
98
|
+
const state = escapeCourtesyText(courtesyLabel(spec.reason));
|
|
99
|
+
const name = escapeCourtesyText(spec.deviceName);
|
|
100
|
+
const stateSize = Math.max(11, Math.round(spec.width / 34));
|
|
101
|
+
const bandHeight = Math.max(26, Math.round(stateSize * 2.2));
|
|
102
|
+
const bandTop = Math.max(0, spec.height - bandHeight);
|
|
103
|
+
const baselineY = Math.round(bandTop + bandHeight * .68);
|
|
104
|
+
const textStroke = Math.max(1, Math.round(spec.width / 320));
|
|
105
|
+
return [
|
|
106
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
|
|
107
|
+
`<rect x="0" y="${String(bandTop)}" width="${String(spec.width)}" height="${String(bandHeight)}" fill="${courtesyBackground(spec.reason)}" fill-opacity="0.82"/>`,
|
|
108
|
+
`<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
|
|
109
|
+
`<text x="50%" y="${String(baselineY)}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}`,
|
|
110
|
+
`<tspan fill="#9a9aa8"> · ${name}</tspan>`,
|
|
111
|
+
`</text></g></svg>`
|
|
112
|
+
].join("");
|
|
89
113
|
}
|
|
90
|
-
|
|
114
|
+
/**
|
|
115
|
+
* The GENERATED card: no picture exists, so this IS the picture. Opaque by
|
|
116
|
+
* necessity — anything translucent here shows black through it.
|
|
117
|
+
*/
|
|
118
|
+
function buildStateSvg(spec) {
|
|
91
119
|
const stateSize = Math.max(12, Math.round(spec.width / 12));
|
|
92
120
|
const nameSize = Math.max(9, Math.round(spec.width / 26));
|
|
93
121
|
const state = escapeCourtesyText(courtesyLabel(spec.reason));
|
|
94
122
|
const name = escapeCourtesyText(spec.deviceName);
|
|
95
123
|
const midY = spec.height / 2;
|
|
96
|
-
const washOpacity = overlay ? "0.62" : "1";
|
|
97
124
|
const textStroke = Math.max(1, Math.round(spec.width / 320));
|
|
98
125
|
return [
|
|
99
126
|
`<svg xmlns="http://www.w3.org/2000/svg" width="${String(spec.width)}" height="${String(spec.height)}">`,
|
|
100
|
-
`<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="
|
|
127
|
+
`<rect width="100%" height="100%" fill="${courtesyBackground(spec.reason)}" fill-opacity="1"/>`,
|
|
101
128
|
`<g font-family="${COURTESY_FONT_STACK}" text-anchor="middle" paint-order="stroke" stroke="#111118" stroke-width="${String(textStroke)}">`,
|
|
102
129
|
`<text x="50%" y="${String(Math.round(midY))}" font-size="${String(stateSize)}" fill="#e8e8ee">${state}</text>`,
|
|
103
130
|
`<text x="50%" y="${String(Math.round(midY + stateSize))}" font-size="${String(nameSize)}" fill="#9a9aa8">${name}</text>`,
|