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