@camstack/addon-pipeline-orchestrator 1.2.626 → 1.2.627

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/index.mjs CHANGED
@@ -43223,6 +43223,649 @@ var _macroLookup = /* @__PURE__ */ new Map();
43223
43223
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
43224
43224
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
43225
43225
  /**
43226
+ * Palette entry ids (D939). Audio is the one gradient; `audio-*` leaves are
43227
+ * not entries. `heatmapScrub` is a mark, not an event kind.
43228
+ */
43229
+ var PALETTE_ENTRY_IDS = [
43230
+ "motion",
43231
+ "audio",
43232
+ "recording",
43233
+ "live",
43234
+ "heatmapScrub",
43235
+ "person",
43236
+ "vehicle",
43237
+ "animal",
43238
+ "package",
43239
+ "sensor",
43240
+ "control",
43241
+ "face",
43242
+ "plate",
43243
+ "car",
43244
+ "truck",
43245
+ "bus",
43246
+ "motorcycle",
43247
+ "bicycle",
43248
+ "airplane",
43249
+ "boat",
43250
+ "train",
43251
+ "bird",
43252
+ "dog",
43253
+ "cat",
43254
+ "horse",
43255
+ "sheep",
43256
+ "cow",
43257
+ "elephant",
43258
+ "bear",
43259
+ "zebra",
43260
+ "giraffe",
43261
+ "package-delivered",
43262
+ "package-picked-up",
43263
+ "suitcase",
43264
+ "backpack",
43265
+ "handbag",
43266
+ "contact",
43267
+ "motion-sensor",
43268
+ "smoke",
43269
+ "flood",
43270
+ "gas",
43271
+ "carbon-monoxide",
43272
+ "vibration",
43273
+ "tamper",
43274
+ "presence",
43275
+ "enum-sensor",
43276
+ "device-event",
43277
+ "lock",
43278
+ "cover",
43279
+ "switch",
43280
+ "siren",
43281
+ "button",
43282
+ "doorbell"
43283
+ ];
43284
+ var AUDIO_ENTRY_ID = "audio";
43285
+ /** Six rungs, quiet → loud. The opaque end is the entry's hex. */
43286
+ var AUDIO_GRADIENT_ALPHAS = [
43287
+ .3,
43288
+ .42,
43289
+ .55,
43290
+ .7,
43291
+ .85,
43292
+ 1
43293
+ ];
43294
+ var PALETTE_PRESET_IDS = [
43295
+ "camstack",
43296
+ "high-contrast",
43297
+ "okabe-ito"
43298
+ ];
43299
+ function isPaletteEntryId(id) {
43300
+ return PALETTE_ENTRY_IDS.includes(id);
43301
+ }
43302
+ function isPalettePresetId(id) {
43303
+ return PALETTE_PRESET_IDS.includes(id);
43304
+ }
43305
+ /**
43306
+ * CIEDE2000 and WCAG contrast for the display palette (D939).
43307
+ *
43308
+ * One implementation, shared by the hub validator, the admin editor and
43309
+ * (phase 2) the viewer. The arithmetic matches the viewer's `src/theme/cie.ts`.
43310
+ */
43311
+ /** Page backgrounds the contrast rule is measured against. */
43312
+ var PALETTE_BACKGROUNDS = {
43313
+ dark: "#0c0a09",
43314
+ light: "#fafaf9"
43315
+ };
43316
+ /** Marks that are not event kinds and share the raised distance floor. */
43317
+ var PALETTE_MARK_PAIR = ["live", "heatmapScrub"];
43318
+ var HEX = /^#[0-9a-fA-F]{6}$/;
43319
+ function srgbChannel(hex, start) {
43320
+ return Number.parseInt(hex.slice(start, start + 2), 16);
43321
+ }
43322
+ function srgbToLin(channel) {
43323
+ const x = channel / 255;
43324
+ return x <= .04045 ? x / 12.92 : ((x + .055) / 1.055) ** 2.4;
43325
+ }
43326
+ function relativeLuminance(hex) {
43327
+ const r = srgbToLin(srgbChannel(hex, 1));
43328
+ const g = srgbToLin(srgbChannel(hex, 3));
43329
+ const b = srgbToLin(srgbChannel(hex, 5));
43330
+ return .2126 * r + .7152 * g + .0722 * b;
43331
+ }
43332
+ /** WCAG contrast ratio of two `#rrggbb` colours. Non-hex returns 0. */
43333
+ function contrastRatio(a, b) {
43334
+ if (!HEX.test(a) || !HEX.test(b)) return 0;
43335
+ const hi = Math.max(relativeLuminance(a), relativeLuminance(b));
43336
+ const lo = Math.min(relativeLuminance(a), relativeLuminance(b));
43337
+ return (hi + .05) / (lo + .05);
43338
+ }
43339
+ function xyzToLab(hex) {
43340
+ const r = srgbToLin(srgbChannel(hex, 1));
43341
+ const g = srgbToLin(srgbChannel(hex, 3));
43342
+ const bl = srgbToLin(srgbChannel(hex, 5));
43343
+ const x = .4124564 * r + .3575761 * g + .1804375 * bl;
43344
+ const y = .2126729 * r + .7151522 * g + .072175 * bl;
43345
+ const z = .0193339 * r + .119192 * g + .9503041 * bl;
43346
+ const f = (t) => t > .008856 ? Math.cbrt(t) : 7.787 * t + 16 / 116;
43347
+ const fx = f(x / .95047);
43348
+ const fy = f(y);
43349
+ const fz = f(z / 1.08883);
43350
+ return {
43351
+ L: 116 * fy - 16,
43352
+ a: 500 * (fx - fy),
43353
+ b: 200 * (fy - fz)
43354
+ };
43355
+ }
43356
+ function hueDeg(ap, b) {
43357
+ if (ap === 0 && b === 0) return 0;
43358
+ const ang = Math.atan2(b, ap) * 180 / Math.PI;
43359
+ return ang < 0 ? ang + 360 : ang;
43360
+ }
43361
+ /** CIEDE2000. Non-hex returns 0 so a bad swatch fails the distinctness rule. */
43362
+ function deltaE00(hex1, hex2) {
43363
+ if (!HEX.test(hex1) || !HEX.test(hex2)) return 0;
43364
+ const lab1 = xyzToLab(hex1);
43365
+ const lab2 = xyzToLab(hex2);
43366
+ const cab7 = ((Math.hypot(lab1.a, lab1.b) + Math.hypot(lab2.a, lab2.b)) / 2) ** 7;
43367
+ const g = .5 * (1 - Math.sqrt(cab7 / (cab7 + 25 ** 7)));
43368
+ const a1p = (1 + g) * lab1.a;
43369
+ const a2p = (1 + g) * lab2.a;
43370
+ const c1p = Math.hypot(a1p, lab1.b);
43371
+ const c2p = Math.hypot(a2p, lab2.b);
43372
+ const h1p = hueDeg(a1p, lab1.b);
43373
+ const h2p = hueDeg(a2p, lab2.b);
43374
+ let dhp = 0;
43375
+ if (c1p * c2p !== 0) {
43376
+ dhp = h2p - h1p;
43377
+ if (dhp > 180) dhp -= 360;
43378
+ else if (dhp < -180) dhp += 360;
43379
+ }
43380
+ const dHp = 2 * Math.sqrt(c1p * c2p) * Math.sin(dhp * Math.PI / 360);
43381
+ const lbp = (lab1.L + lab2.L) / 2;
43382
+ const cbp = (c1p + c2p) / 2;
43383
+ let hbp = h1p + h2p;
43384
+ if (c1p * c2p !== 0) {
43385
+ if (Math.abs(h1p - h2p) > 180) hbp += hbp < 360 ? 360 : -360;
43386
+ hbp /= 2;
43387
+ }
43388
+ const t = 1 - .17 * Math.cos((hbp - 30) * Math.PI / 180) + .24 * Math.cos(2 * hbp * Math.PI / 180) + .32 * Math.cos((3 * hbp + 6) * Math.PI / 180) - .2 * Math.cos((4 * hbp - 63) * Math.PI / 180);
43389
+ const dTheta = 30 * Math.exp(-(((hbp - 275) / 25) ** 2));
43390
+ const cbp7 = cbp ** 7;
43391
+ const rc = 2 * Math.sqrt(cbp7 / (cbp7 + 25 ** 7));
43392
+ const sl = 1 + .015 * (lbp - 50) ** 2 / Math.sqrt(20 + (lbp - 50) ** 2);
43393
+ const sc = 1 + .045 * cbp;
43394
+ const sh = 1 + .015 * cbp * t;
43395
+ const rt = -Math.sin(2 * dTheta * Math.PI / 180) * rc;
43396
+ const dLp = lab2.L - lab1.L;
43397
+ const dCp = c2p - c1p;
43398
+ return Math.sqrt((dLp / sl) ** 2 + (dCp / sc) ** 2 + (dHp / sh) ** 2 + rt * (dCp / sc) * (dHp / sh));
43399
+ }
43400
+ /** Frozen palette tables (D939). The spec refuses a table that breaks the rule. */
43401
+ var CAMSTACK_HEX = {
43402
+ motion: "#d946ef",
43403
+ audio: "#059eb8",
43404
+ recording: "#ef4444",
43405
+ live: "#8d7a00",
43406
+ heatmapScrub: "#4b32ff",
43407
+ person: "#11a64c",
43408
+ vehicle: "#3b82f6",
43409
+ animal: "#ea6a09",
43410
+ package: "#924667",
43411
+ sensor: "#624bbc",
43412
+ control: "#944d24",
43413
+ face: "#ed4f8d",
43414
+ plate: "#4b7027",
43415
+ car: "#9684cf",
43416
+ truck: "#0f9b89",
43417
+ bus: "#a734a9",
43418
+ motorcycle: "#c1133a",
43419
+ bicycle: "#0e6a9b",
43420
+ airplane: "#7c9928",
43421
+ boat: "#b17933",
43422
+ train: "#ad6ca3",
43423
+ bird: "#0f7c55",
43424
+ dog: "#c82700",
43425
+ cat: "#7268f2",
43426
+ horse: "#cb795e",
43427
+ sheep: "#b8636b",
43428
+ cow: "#cb2969",
43429
+ elephant: "#1158d2",
43430
+ bear: "#8d5e00",
43431
+ zebra: "#0097d6",
43432
+ giraffe: "#8f61bb",
43433
+ "package-delivered": "#cd3f9b",
43434
+ "package-picked-up": "#388a08",
43435
+ suitcase: "#666800",
43436
+ backpack: "#ec5022",
43437
+ handbag: "#a76ef8",
43438
+ contact: "#ab5a49",
43439
+ "motion-sensor": "#b95d1a",
43440
+ smoke: "#dd3858",
43441
+ flood: "#b31e7d",
43442
+ gas: "#5b70b5",
43443
+ "carbon-monoxide": "#7b4e8e",
43444
+ vibration: "#e65bba",
43445
+ tamper: "#98464b",
43446
+ presence: "#3a9468",
43447
+ "enum-sensor": "#7885f8",
43448
+ "device-event": "#8c43c8",
43449
+ lock: "#649653",
43450
+ cover: "#c67597",
43451
+ switch: "#7a812a",
43452
+ siren: "#ac5d7f",
43453
+ button: "#b276cd",
43454
+ doorbell: "#0087ab"
43455
+ };
43456
+ var CAMSTACK_FALLBACK = [
43457
+ "#b34dc7",
43458
+ "#1d7d34",
43459
+ "#db6b6c",
43460
+ "#a43d29",
43461
+ "#0e6ee6",
43462
+ "#29a10b",
43463
+ "#a98731",
43464
+ "#0181c6"
43465
+ ];
43466
+ var HIGH_CONTRAST_HEX = {
43467
+ motion: "#964360",
43468
+ audio: "#23a132",
43469
+ recording: "#2494ca",
43470
+ live: "#bd7e2b",
43471
+ heatmapScrub: "#ad6eea",
43472
+ person: "#62611e",
43473
+ vehicle: "#f4534c",
43474
+ animal: "#1556cd",
43475
+ package: "#9c4415",
43476
+ sensor: "#066c48",
43477
+ control: "#f2479f",
43478
+ face: "#139d8a",
43479
+ plate: "#909024",
43480
+ car: "#8e35ab",
43481
+ truck: "#bd0b2b",
43482
+ bus: "#7a88ca",
43483
+ motorcycle: "#e26725",
43484
+ bicycle: "#a6679b",
43485
+ airplane: "#7c5720",
43486
+ boat: "#467a1b",
43487
+ train: "#6f65a4",
43488
+ bird: "#cb717f",
43489
+ dog: "#c62490",
43490
+ cat: "#b36850",
43491
+ horse: "#1872ac",
43492
+ sheep: "#927939",
43493
+ cow: "#428a5c",
43494
+ elephant: "#d41160",
43495
+ bear: "#799453",
43496
+ zebra: "#a85455",
43497
+ giraffe: "#746be8",
43498
+ "package-delivered": "#824a80",
43499
+ "package-picked-up": "#614ac1",
43500
+ suitcase: "#2f77f3",
43501
+ backpack: "#fa476d",
43502
+ handbag: "#c14cbe",
43503
+ contact: "#c83921",
43504
+ "motion-sensor": "#a07ebd",
43505
+ smoke: "#a25b13",
43506
+ flood: "#526bab",
43507
+ gas: "#924938",
43508
+ "carbon-monoxide": "#bb7e53",
43509
+ vibration: "#b66182",
43510
+ tamper: "#3c6931",
43511
+ presence: "#6c851d",
43512
+ "enum-sensor": "#ad2d48",
43513
+ "device-event": "#5e9b04",
43514
+ lock: "#00a062",
43515
+ cover: "#ad8624",
43516
+ switch: "#d05259",
43517
+ siren: "#cd67b3",
43518
+ button: "#c9746a",
43519
+ doorbell: "#338eeb"
43520
+ };
43521
+ var HIGH_CONTRAST_FALLBACK = [
43522
+ "#817f3b",
43523
+ "#a0733e",
43524
+ "#8658b2",
43525
+ "#956301",
43526
+ "#bc652d",
43527
+ "#647331",
43528
+ "#a14c9e",
43529
+ "#d14e71"
43530
+ ];
43531
+ var OKABE_ITO_HEX = {
43532
+ motion: "#b88128",
43533
+ audio: "#1096c8",
43534
+ recording: "#009e73",
43535
+ live: "#9c8c22",
43536
+ heatmapScrub: "#0072b2",
43537
+ person: "#d55e00",
43538
+ vehicle: "#c0739f",
43539
+ animal: "#226d18",
43540
+ package: "#91446c",
43541
+ sensor: "#217b56",
43542
+ control: "#648647",
43543
+ face: "#c22998",
43544
+ plate: "#fa4575",
43545
+ car: "#956301",
43546
+ truck: "#2f77f3",
43547
+ bus: "#7a4e86",
43548
+ motorcycle: "#f54597",
43549
+ bicycle: "#bb3e75",
43550
+ airplane: "#e054c0",
43551
+ boat: "#457942",
43552
+ train: "#867afa",
43553
+ bird: "#88914c",
43554
+ dog: "#1556cd",
43555
+ cat: "#71702c",
43556
+ horse: "#9b4fa4",
43557
+ sheep: "#c0559d",
43558
+ cow: "#d14f65",
43559
+ elephant: "#98470f",
43560
+ bear: "#595896",
43561
+ zebra: "#96790e",
43562
+ giraffe: "#cb717b",
43563
+ "package-delivered": "#139d8a",
43564
+ "package-picked-up": "#906ead",
43565
+ suitcase: "#d02939",
43566
+ backpack: "#d41160",
43567
+ handbag: "#f64f54",
43568
+ contact: "#7258d2",
43569
+ "motion-sensor": "#b66182",
43570
+ smoke: "#627abb",
43571
+ flood: "#b86ae4",
43572
+ gas: "#a95261",
43573
+ "carbon-monoxide": "#c65d3c",
43574
+ vibration: "#7a40b7",
43575
+ tamper: "#914b34",
43576
+ presence: "#9481c3",
43577
+ "enum-sensor": "#ac6d47",
43578
+ "device-event": "#7b6fce",
43579
+ lock: "#c67662",
43580
+ cover: "#a6564d",
43581
+ switch: "#ad2c4e",
43582
+ siren: "#348e08",
43583
+ button: "#d27303",
43584
+ doorbell: "#55770f"
43585
+ };
43586
+ var OKABE_ITO_FALLBACK = [
43587
+ "#9a3784",
43588
+ "#a7392c",
43589
+ "#409d50",
43590
+ "#6987e7",
43591
+ "#ee5b3d",
43592
+ "#c0430d",
43593
+ "#af79b3",
43594
+ "#4b6628"
43595
+ ];
43596
+ /**
43597
+ * The display palette (D939). One table of colours, three presets, one
43598
+ * validator. Taxonomy colours and every consumer read this — there is no
43599
+ * second hex table.
43600
+ */
43601
+ var paletteHexSchema = string().regex(/^#[0-9a-f]{6}$/);
43602
+ var paletteSwatchSchema = discriminatedUnion("form", [object({
43603
+ form: literal("solid"),
43604
+ hex: paletteHexSchema
43605
+ }), object({
43606
+ form: literal("gradient"),
43607
+ hex: paletteHexSchema,
43608
+ alphas: array(number().gt(0).lte(1)).length(AUDIO_GRADIENT_ALPHAS.length)
43609
+ })]);
43610
+ var paletteReadySchema = object({
43611
+ status: literal("ready"),
43612
+ presetId: _enum(PALETTE_PRESET_IDS),
43613
+ customized: boolean(),
43614
+ source: _enum(["stored", "unconfigured"]),
43615
+ swatches: record(string(), paletteSwatchSchema),
43616
+ fallbacks: array(paletteHexSchema).length(8)
43617
+ });
43618
+ var paletteResultSchema = discriminatedUnion("status", [paletteReadySchema, object({
43619
+ status: literal("unknown"),
43620
+ reason: string()
43621
+ })]);
43622
+ var setPaletteInputSchema = union([object({ presetId: string() }), object({
43623
+ presetId: string(),
43624
+ overrides: record(string(), paletteSwatchSchema)
43625
+ })]);
43626
+ var storedPaletteSchema = object({
43627
+ presetId: string(),
43628
+ overrides: record(string(), paletteSwatchSchema)
43629
+ });
43630
+ function solid(hex) {
43631
+ return {
43632
+ form: "solid",
43633
+ hex
43634
+ };
43635
+ }
43636
+ function audioGradient(hex) {
43637
+ return {
43638
+ form: "gradient",
43639
+ hex,
43640
+ alphas: [...AUDIO_GRADIENT_ALPHAS]
43641
+ };
43642
+ }
43643
+ function swatchesFrom(hexes) {
43644
+ const out = {};
43645
+ for (const id of PALETTE_ENTRY_IDS) {
43646
+ const hex = hexes[id];
43647
+ if (hex === void 0) throw new Error(`palette table missing ${id}`);
43648
+ out[id] = id === "audio" ? audioGradient(hex) : solid(hex);
43649
+ }
43650
+ return out;
43651
+ }
43652
+ function preset(id, name, hexes, fallbacks) {
43653
+ return {
43654
+ id,
43655
+ name,
43656
+ swatches: swatchesFrom(hexes),
43657
+ fallbacks
43658
+ };
43659
+ }
43660
+ var DISPLAY_PRESETS = [
43661
+ preset("camstack", "CamStack", CAMSTACK_HEX, CAMSTACK_FALLBACK),
43662
+ preset("high-contrast", "High contrast", HIGH_CONTRAST_HEX, HIGH_CONTRAST_FALLBACK),
43663
+ preset("okabe-ito", "Colour-blind safe", OKABE_ITO_HEX, OKABE_ITO_FALLBACK)
43664
+ ];
43665
+ function presetById(id) {
43666
+ return DISPLAY_PRESETS.find((item) => item.id === id);
43667
+ }
43668
+ function bundledCamstack() {
43669
+ const presetRow = presetById("camstack");
43670
+ if (presetRow === void 0) throw new Error("CamStack preset missing");
43671
+ return readyOf(presetRow, "unconfigured", false);
43672
+ }
43673
+ function readyOf(row, source, customized) {
43674
+ return {
43675
+ status: "ready",
43676
+ presetId: row.id,
43677
+ customized,
43678
+ source,
43679
+ swatches: row.swatches,
43680
+ fallbacks: [...row.fallbacks]
43681
+ };
43682
+ }
43683
+ function namedColours(swatches, fallbacks) {
43684
+ const rows = [];
43685
+ for (const id of PALETTE_ENTRY_IDS) {
43686
+ const swatch = swatches[id];
43687
+ if (swatch !== void 0) rows.push({
43688
+ name: id,
43689
+ hex: swatch.hex
43690
+ });
43691
+ }
43692
+ fallbacks.forEach((hex, index) => {
43693
+ rows.push({
43694
+ name: `fallback-${index}`,
43695
+ hex
43696
+ });
43697
+ });
43698
+ return rows;
43699
+ }
43700
+ function gradientViolations(swatches) {
43701
+ const audio = swatches[AUDIO_ENTRY_ID];
43702
+ if (audio === void 0) return [];
43703
+ if (audio.form !== "gradient") return [{
43704
+ code: "bad-gradient",
43705
+ entry: AUDIO_ENTRY_ID
43706
+ }];
43707
+ return AUDIO_GRADIENT_ALPHAS.every((alpha, index) => audio.alphas[index] === alpha) ? [] : [{
43708
+ code: "bad-gradient",
43709
+ entry: AUDIO_ENTRY_ID
43710
+ }];
43711
+ }
43712
+ function isMarkPair(left, right) {
43713
+ const marks = PALETTE_MARK_PAIR;
43714
+ return marks.includes(left) && marks.includes(right);
43715
+ }
43716
+ /** Refuse a palette whose entries are not pairwise distinct on both themes. */
43717
+ function validatePalette(swatches, fallbacks) {
43718
+ const violations = [];
43719
+ for (const id of PALETTE_ENTRY_IDS) if (swatches[id] === void 0) violations.push({
43720
+ code: "missing-entry",
43721
+ entry: id
43722
+ });
43723
+ for (const key of Object.keys(swatches)) if (!isPaletteEntryId(key)) violations.push({
43724
+ code: "unknown-entry",
43725
+ entry: key
43726
+ });
43727
+ if (fallbacks.length !== 8) violations.push({ code: "bad-fallbacks" });
43728
+ violations.push(...gradientViolations(swatches));
43729
+ const rows = namedColours(swatches, fallbacks);
43730
+ for (let i = 0; i < rows.length; i++) for (let j = i + 1; j < rows.length; j++) {
43731
+ const left = rows[i];
43732
+ const right = rows[j];
43733
+ if (left === void 0 || right === void 0) continue;
43734
+ const deltaE = deltaE00(left.hex, right.hex);
43735
+ if (deltaE < (isMarkPair(left.name, right.name) ? 20 : 7)) violations.push({
43736
+ code: "too-close",
43737
+ left: left.name,
43738
+ right: right.name,
43739
+ deltaE
43740
+ });
43741
+ }
43742
+ for (const row of rows) for (const background of ["dark", "light"]) {
43743
+ const contrast = contrastRatio(row.hex, PALETTE_BACKGROUNDS[background]);
43744
+ if (contrast < 3) violations.push({
43745
+ code: "low-contrast",
43746
+ entry: row.name,
43747
+ background,
43748
+ contrast
43749
+ });
43750
+ }
43751
+ return violations.length === 0 ? { ok: true } : {
43752
+ ok: false,
43753
+ violations
43754
+ };
43755
+ }
43756
+ function formatPaletteViolation(violation) {
43757
+ switch (violation.code) {
43758
+ case "too-close": return `${violation.left} vs ${violation.right} (ΔE ${violation.deltaE.toFixed(1)})`;
43759
+ case "low-contrast": return `${violation.entry} on ${violation.background} (${violation.contrast.toFixed(1)}:1)`;
43760
+ case "missing-entry": return `${violation.entry} is missing`;
43761
+ case "unknown-entry": return `${violation.entry} is not a palette entry`;
43762
+ case "bad-gradient": return `${violation.entry} gradient is not the six canonical steps`;
43763
+ case "bad-fallbacks": return "fallback list must hold eight colours";
43764
+ case "unknown-preset": return `${violation.presetId} is not a preset`;
43765
+ }
43766
+ }
43767
+ function formatPaletteViolations(violations) {
43768
+ return violations.map(formatPaletteViolation).join("; ");
43769
+ }
43770
+ var PaletteRejectedError = class extends Error {
43771
+ violations;
43772
+ constructor(violations) {
43773
+ super(formatPaletteViolations(violations));
43774
+ this.name = "PaletteRejectedError";
43775
+ this.violations = violations;
43776
+ }
43777
+ };
43778
+ function hashFallback(kind, fallbacks) {
43779
+ let hash = 0;
43780
+ for (let i = 0; i < kind.length; i++) hash = hash * 31 + (kind.codePointAt(i) ?? 0) >>> 0;
43781
+ const hex = fallbacks[hash % fallbacks.length];
43782
+ if (hex === void 0) throw new Error("display palette has no fallback colour");
43783
+ return hex;
43784
+ }
43785
+ /** Colour a kind paints with. Audio leaves share the audio gradient's hex. */
43786
+ function paintHex(swatches, fallbacks, kind) {
43787
+ const key = kind.trim().toLowerCase();
43788
+ if (key.startsWith("audio-")) {
43789
+ const audio = swatches[AUDIO_ENTRY_ID];
43790
+ return audio !== void 0 ? audio.hex : hashFallback(key, fallbacks);
43791
+ }
43792
+ const direct = swatches[key];
43793
+ if (direct !== void 0) return direct.hex;
43794
+ return hashFallback(key, fallbacks);
43795
+ }
43796
+ function mergeOverrides(base, overrides) {
43797
+ return {
43798
+ ...base,
43799
+ ...overrides
43800
+ };
43801
+ }
43802
+ function overridesOf(input) {
43803
+ return "overrides" in input ? input.overrides : {};
43804
+ }
43805
+ /** The palette a `setPalette` input would store. Invalid input is a refusal. */
43806
+ function paletteFromInput(input) {
43807
+ const row = presetById(input.presetId);
43808
+ if (row === void 0 || !isPalettePresetId(input.presetId)) throw new PaletteRejectedError([{
43809
+ code: "unknown-preset",
43810
+ presetId: input.presetId
43811
+ }]);
43812
+ const overrides = overridesOf(input);
43813
+ const swatches = mergeOverrides(row.swatches, overrides);
43814
+ const verdict = validatePalette(swatches, row.fallbacks);
43815
+ if (!verdict.ok) throw new PaletteRejectedError(verdict.violations);
43816
+ const customized = Object.keys(overrides).length > 0;
43817
+ return {
43818
+ status: "ready",
43819
+ presetId: row.id,
43820
+ customized,
43821
+ source: "stored",
43822
+ swatches,
43823
+ fallbacks: [...row.fallbacks]
43824
+ };
43825
+ }
43826
+ function isEmptyRecord(value) {
43827
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0;
43828
+ }
43829
+ /**
43830
+ * Interpret a settings blob. A missing row is the bundled preset and says so.
43831
+ * A blob that does not parse, names an unknown preset, or fails the rule is
43832
+ * `unknown` — never the default wearing the stored preset's name.
43833
+ */
43834
+ function paletteFromStored(value) {
43835
+ if (value === void 0 || value === null || isEmptyRecord(value)) return bundledCamstack();
43836
+ const parsed = storedPaletteSchema.safeParse(value);
43837
+ if (!parsed.success) return {
43838
+ status: "unknown",
43839
+ reason: "stored palette is unreadable"
43840
+ };
43841
+ const row = presetById(parsed.data.presetId);
43842
+ if (row === void 0) return {
43843
+ status: "unknown",
43844
+ reason: `unknown preset ${parsed.data.presetId}`
43845
+ };
43846
+ const swatches = mergeOverrides(row.swatches, parsed.data.overrides);
43847
+ const verdict = validatePalette(swatches, row.fallbacks);
43848
+ if (!verdict.ok) return {
43849
+ status: "unknown",
43850
+ reason: formatPaletteViolations(verdict.violations)
43851
+ };
43852
+ const customized = Object.keys(parsed.data.overrides).length > 0;
43853
+ return {
43854
+ status: "ready",
43855
+ presetId: row.id,
43856
+ customized,
43857
+ source: "stored",
43858
+ swatches,
43859
+ fallbacks: [...row.fallbacks]
43860
+ };
43861
+ }
43862
+ function storedFromReady(ready, overrides) {
43863
+ return {
43864
+ presetId: ready.presetId,
43865
+ overrides: { ...overrides }
43866
+ };
43867
+ }
43868
+ /**
43226
43869
  * Unified event-kind taxonomy — THE single source of truth for
43227
43870
  * `kind → { parentKind, category, level, color, iconId, labelKey, label,
43228
43871
  * icon }`.
@@ -43236,109 +43879,88 @@ for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set
43236
43879
  * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
43237
43880
  * - the COCO / audio class maps (macro ↔ sub relationships)
43238
43881
  *
43239
- * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
43240
- * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
43241
- * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
43242
- * color or an icon: they read this dictionary (server descriptors carry the
43243
- * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
43882
+ * The DATA (serializable — parentKind / iconId / labelKey) lives here in
43883
+ * `@camstack/types`. Colour is NOT a second table: each entry's `color` is
43884
+ * the bundled CamStack display palette (D939). The effective cluster palette
43885
+ * is the `display-palette` capability. The UI-side mapping `iconId → lucide
43886
+ * component` and `labelKey → t()` lives in `@camstack/ui-library`.
43244
43887
  *
43245
43888
  * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
43246
43889
  */
43247
- var TAXONOMY_COLORS = {
43248
- motion: "#f59e0b",
43249
- audio: "#06b6d4",
43250
- person: "#22c55e",
43251
- vehicle: "#3b82f6",
43252
- animal: "#f97316",
43253
- package: "#a855f7",
43254
- sensor: "#8b5cf6",
43255
- control: "#10b981",
43256
- genericDetection: "#64748b"
43257
- };
43258
- var DETECTION_SUB_COLORS = {
43259
- car: "#f59e0b",
43260
- truck: "#d97706",
43261
- bus: "#b45309",
43262
- motorcycle: "#eab308",
43263
- bicycle: "#ca8a04",
43264
- airplane: "#60a5fa",
43265
- boat: "#2563eb",
43266
- train: "#1d4ed8",
43267
- bird: "#14b8a6",
43268
- dog: "#84cc16",
43269
- cat: "#f97316",
43270
- horse: "#a16207",
43271
- sheep: "#a3a3a3",
43272
- cow: "#78716c",
43273
- elephant: "#6b7280",
43274
- bear: "#7c2d12",
43275
- zebra: "#404040",
43276
- giraffe: "#d4a373"
43277
- };
43890
+ var BUNDLED_PALETTE = bundledCamstack();
43891
+ function kindColor(kind) {
43892
+ return paintHex(BUNDLED_PALETTE.swatches, BUNDLED_PALETTE.fallbacks, kind);
43893
+ }
43894
+ function firstFallback(fallbacks) {
43895
+ const hex = fallbacks[0];
43896
+ if (hex === void 0) throw new Error("display palette has no fallback colour");
43897
+ return hex;
43898
+ }
43899
+ firstFallback(BUNDLED_PALETTE.fallbacks);
43278
43900
  function titleCase(id) {
43279
43901
  return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
43280
43902
  }
43281
43903
  var entries = /* @__PURE__ */ new Map();
43282
- function macro(kind, category, color, iconId, label) {
43904
+ function macro(kind, category, iconId, label) {
43283
43905
  entries.set(kind, {
43284
43906
  kind,
43285
43907
  parentKind: null,
43286
43908
  level: "macro",
43287
43909
  category,
43288
- color,
43910
+ color: kindColor(kind),
43289
43911
  iconId,
43290
43912
  labelKey: `eventKind.${kind}`,
43291
43913
  label
43292
43914
  });
43293
43915
  }
43294
- function sub(kind, parentKind, category, color, iconId, label) {
43916
+ function sub(kind, parentKind, category, iconId, label) {
43295
43917
  entries.set(kind, {
43296
43918
  kind,
43297
43919
  parentKind,
43298
43920
  level: "sub",
43299
43921
  category,
43300
- color,
43922
+ color: kindColor(kind),
43301
43923
  iconId,
43302
43924
  labelKey: `eventKind.${kind}`,
43303
43925
  label
43304
43926
  });
43305
43927
  }
43306
- macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
43307
- macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
43308
- macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
43309
- macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
43310
- macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
43311
- macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
43312
- macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
43313
- macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
43928
+ macro("motion", "motion", "motion", "Motion");
43929
+ macro("audio", "audio", "audio", "Audio");
43930
+ macro("person", "detection", "person", "Person");
43931
+ macro("vehicle", "detection", "vehicle", "Vehicle");
43932
+ macro("animal", "detection", "animal", "Animal");
43933
+ macro("package", "package", "package", "Package");
43934
+ macro("sensor", "sensor", "sensor", "Sensor");
43935
+ macro("control", "control", "control", "Control");
43314
43936
  for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
43315
43937
  if (macroClass !== "vehicle" && macroClass !== "animal") continue;
43316
43938
  if (entries.has(cocoClass)) continue;
43317
- sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
43318
- }
43319
- sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
43320
- sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
43321
- sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
43322
- sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
43323
- sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
43324
- sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
43325
- sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
43326
- sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
43327
- sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
43328
- sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
43329
- sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
43330
- sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
43331
- sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
43332
- sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
43333
- sub("cover", "control", "control", "#0ea5e9", "door", "Cover");
43334
- sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
43335
- sub("siren", "control", "control", "#dc2626", "siren", "Siren");
43336
- sub("button", "control", "control", "#10b981", "button", "Button");
43337
- sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
43939
+ sub(cocoClass, macroClass, "detection", cocoClass, titleCase(cocoClass));
43940
+ }
43941
+ sub("package-delivered", "package", "package", "package", "Package delivered");
43942
+ sub("package-picked-up", "package", "package", "package", "Package picked up");
43943
+ sub("contact", "sensor", "sensor", "door", "Contact");
43944
+ sub("motion-sensor", "sensor", "sensor", "pir", "Motion sensor");
43945
+ sub("smoke", "sensor", "sensor", "smoke", "Smoke");
43946
+ sub("flood", "sensor", "sensor", "water", "Water leak");
43947
+ sub("gas", "sensor", "sensor", "gas", "Gas");
43948
+ sub("carbon-monoxide", "sensor", "sensor", "smoke", "Carbon monoxide");
43949
+ sub("vibration", "sensor", "sensor", "vibration", "Vibration");
43950
+ sub("tamper", "sensor", "sensor", "tamper", "Tamper");
43951
+ sub("presence", "sensor", "sensor", "presence", "Presence");
43952
+ sub("enum-sensor", "sensor", "sensor", "generic", "Sensor state");
43953
+ sub("device-event", "sensor", "sensor", "button", "Device event");
43954
+ sub("lock", "control", "control", "lock", "Lock");
43955
+ sub("cover", "control", "control", "door", "Cover");
43956
+ sub("switch", "control", "control", "switch", "Switch");
43957
+ sub("siren", "control", "control", "siren", "Siren");
43958
+ sub("button", "control", "control", "button", "Button");
43959
+ sub("doorbell", "control", "control", "doorbell", "Doorbell");
43338
43960
  for (const l of AUDIO_MACRO_LABELS) {
43339
43961
  const kind = `audio-${l.id}`;
43340
43962
  if (entries.has(kind)) continue;
43341
- sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
43963
+ sub(kind, "audio", "audio", kind, l.name);
43342
43964
  }
43343
43965
  /** The complete taxonomy dictionary, keyed by kind. */
43344
43966
  var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
@@ -56971,6 +57593,39 @@ DeviceType.Camera, method(object({ deviceId: number() }), DayNightOptionsSchema)
56971
57593
  auth: "admin"
56972
57594
  });
56973
57595
  /**
57596
+ * display-palette — the cluster's one colour table (D939).
57597
+ *
57598
+ * Hub singleton. `getPalette` reports `unknown` when the settings store
57599
+ * cannot be read; it does not pretend the bundled preset was stored.
57600
+ * `setPalette` refuses a palette that fails the ΔE / contrast rule and names
57601
+ * the pairs.
57602
+ */
57603
+ var presetSummarySchema = object({
57604
+ id: string(),
57605
+ name: string(),
57606
+ swatches: record(string(), paletteSwatchSchema),
57607
+ fallbacks: array(string())
57608
+ });
57609
+ var displayPaletteCapability = {
57610
+ name: "display-palette",
57611
+ scope: "system",
57612
+ mode: "singleton",
57613
+ methods: {
57614
+ getPalette: method(_void(), paletteResultSchema, {
57615
+ auth: "protected",
57616
+ role: "viewer"
57617
+ }),
57618
+ listPresets: method(_void(), array(presetSummarySchema).readonly(), {
57619
+ auth: "protected",
57620
+ role: "viewer"
57621
+ }),
57622
+ setPalette: method(setPaletteInputSchema, paletteReadySchema, {
57623
+ kind: "mutation",
57624
+ auth: "admin"
57625
+ })
57626
+ }
57627
+ };
57628
+ /**
56974
57629
  * Generic device-level status snapshot. Auto-registered by `BaseDevice`
56975
57630
  * for every device, regardless of provider — the kernel needs a uniform
56976
57631
  * cap-keyed slice for the basic device flags every consumer expects to
@@ -68704,6 +69359,27 @@ Object.freeze({
68704
69359
  access: "create",
68705
69360
  role: "viewer"
68706
69361
  },
69362
+ "displayPalette.getPalette": {
69363
+ capName: "display-palette",
69364
+ capScope: "system",
69365
+ addonId: null,
69366
+ access: "view",
69367
+ role: "viewer"
69368
+ },
69369
+ "displayPalette.listPresets": {
69370
+ capName: "display-palette",
69371
+ capScope: "system",
69372
+ addonId: null,
69373
+ access: "view",
69374
+ role: "viewer"
69375
+ },
69376
+ "displayPalette.setPalette": {
69377
+ capName: "display-palette",
69378
+ capScope: "system",
69379
+ addonId: null,
69380
+ access: "create",
69381
+ role: "admin"
69382
+ },
68707
69383
  "doorbell.getStatus": {
68708
69384
  capName: "doorbell",
68709
69385
  capScope: "device",
@@ -79160,6 +79836,7 @@ var SYSTEM_CAP_VOCABULARY = {
79160
79836
  "device-provider": system(INTERNAL),
79161
79837
  "device-removal-hook": system(INTERNAL),
79162
79838
  "device-state": system("the runtime-state mirror every trigger already reads through"),
79839
+ "display-palette": system("the cluster display palette — one colour table for every client, not a device action"),
79163
79840
  "embedding-encoder": system(ML),
79164
79841
  "face-gallery": system("the face gallery — curated on its own page"),
79165
79842
  "failure-contribution": system(INTERNAL),
@@ -103036,6 +103713,112 @@ function deriveRuntimeSettings(config) {
103036
103713
  };
103037
103714
  }
103038
103715
  //#endregion
103716
+ //#region src/display-palette-provider.ts
103717
+ /**
103718
+ * Display palette provider (D939, D940). The pipeline orchestrator owns
103719
+ * this cluster setting. The document is one key in the addon's store, so
103720
+ * a write does not replace the orchestrator's other settings. An
103721
+ * unreadable store is `unknown`, never the bundled preset.
103722
+ */
103723
+ /** Key inside the orchestrator's addon store. Not a settings-schema field. */
103724
+ var DISPLAY_PALETTE_STORE_KEY = "displayPalette";
103725
+ function createDisplayPaletteProvider(settings, logger) {
103726
+ return {
103727
+ getPalette: async () => readPalette(settings, logger),
103728
+ listPresets: async () => DISPLAY_PRESETS.map((row) => ({
103729
+ id: row.id,
103730
+ name: row.name,
103731
+ swatches: row.swatches,
103732
+ fallbacks: [...row.fallbacks]
103733
+ })),
103734
+ setPalette: async (input) => writePalette(settings, logger, input)
103735
+ };
103736
+ }
103737
+ async function readPalette(settings, logger) {
103738
+ if (settings === void 0) {
103739
+ logger.warn("display-palette: settings store cannot be read three ways");
103740
+ return {
103741
+ status: "unknown",
103742
+ reason: "settings store cannot be read"
103743
+ };
103744
+ }
103745
+ const readKey = settings.readAddonStoreKeyResult;
103746
+ if (readKey !== void 0) return readByKey(settings, readKey, logger);
103747
+ const read = settings.readAddonStoreResult;
103748
+ if (read === void 0) {
103749
+ logger.warn("display-palette: settings store cannot be read three ways");
103750
+ return {
103751
+ status: "unknown",
103752
+ reason: "settings store cannot be read"
103753
+ };
103754
+ }
103755
+ return readByStore(settings, read, logger);
103756
+ }
103757
+ async function readByKey(settings, readKey, logger) {
103758
+ let result;
103759
+ try {
103760
+ result = await readKey.call(settings, DISPLAY_PALETTE_STORE_KEY);
103761
+ } catch (err) {
103762
+ return readThrew(logger, err);
103763
+ }
103764
+ if (result.kind === "unreadable") {
103765
+ logger.warn("display-palette: settings unreadable", { meta: { error: result.error } });
103766
+ return {
103767
+ status: "unknown",
103768
+ reason: result.error ?? "settings store cannot be read"
103769
+ };
103770
+ }
103771
+ if (result.kind === "absent") return bundledCamstack();
103772
+ return interpretStored(logger, result.value);
103773
+ }
103774
+ async function readByStore(settings, read, logger) {
103775
+ let result;
103776
+ try {
103777
+ result = await read.call(settings);
103778
+ } catch (err) {
103779
+ return readThrew(logger, err);
103780
+ }
103781
+ if (result.kind === "failed") {
103782
+ logger.warn("display-palette: settings unreadable", { meta: { error: result.error } });
103783
+ return {
103784
+ status: "unknown",
103785
+ reason: result.error
103786
+ };
103787
+ }
103788
+ if (result.kind === "missing") return bundledCamstack();
103789
+ return interpretStored(logger, result.value[DISPLAY_PALETTE_STORE_KEY]);
103790
+ }
103791
+ function readThrew(logger, err) {
103792
+ const error = err instanceof Error ? err.message : String(err);
103793
+ logger.warn("display-palette: settings read threw", { meta: { error } });
103794
+ return {
103795
+ status: "unknown",
103796
+ reason: "settings store read failed"
103797
+ };
103798
+ }
103799
+ function interpretStored(logger, value) {
103800
+ const palette = paletteFromStored(value);
103801
+ if (palette.status === "unknown") logger.warn("display-palette: stored palette is not usable", { meta: { reason: palette.reason } });
103802
+ return palette;
103803
+ }
103804
+ async function writePalette(settings, logger, input) {
103805
+ let ready;
103806
+ try {
103807
+ ready = paletteFromInput(input);
103808
+ } catch (err) {
103809
+ if (err instanceof PaletteRejectedError) logger.info("display-palette: refused", { meta: { reason: err.message } });
103810
+ throw err;
103811
+ }
103812
+ const write = settings?.writeAddonStore;
103813
+ if (settings === void 0 || write === void 0) {
103814
+ logger.warn("display-palette: settings store cannot be written");
103815
+ throw new Error("display-palette: settings store cannot be written");
103816
+ }
103817
+ const overrides = "overrides" in input ? input.overrides : {};
103818
+ await write.call(settings, { [DISPLAY_PALETTE_STORE_KEY]: storedFromReady(ready, overrides) });
103819
+ return ready;
103820
+ }
103821
+ //#endregion
103039
103822
  //#region src/viewer-ui-provider.ts
103040
103823
  /**
103041
103824
  * viewer-ui provider — the pipeline-orchestrator serves the CamStack viewer web
@@ -104492,6 +105275,10 @@ var PipelineOrchestratorAddon = class PipelineOrchestratorAddon extends BaseAddo
104492
105275
  {
104493
105276
  capability: viewerUiCapability,
104494
105277
  provider: createViewerUiProvider()
105278
+ },
105279
+ {
105280
+ capability: displayPaletteCapability,
105281
+ provider: createDisplayPaletteProvider(this.ctx.settings, this.ctx.logger)
104495
105282
  }
104496
105283
  ],
104497
105284
  customActions: pipelineOrchestratorActions,