@camstack/addon-training 0.1.109 → 0.1.110

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{MotionZonesSettings-BFubqBUi.mjs → MotionZonesSettings-D4s4e-dn.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-sNWgI2Ak.mjs → PrivacyMaskSettings-BtdwrmHQ.mjs} +2 -2
  4. package/dist/{SceneMonitorEditor-CyQQi49c.mjs → SceneMonitorEditor-DOX6WQaZ.mjs} +2 -2
  5. package/dist/_stub.js +9 -9
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-CoE4OqSm.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-C3SgLMLF.mjs} +3 -3
  7. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CmVZHQ-k.mjs +26 -0
  8. package/dist/{dist-BkNG49yZ.js → dist-BSSYC9T8.js} +285 -3
  9. package/dist/{dist-DW0wmCpB.mjs → dist-De0e9-YH.mjs} +285 -3
  10. package/dist/{hostInit-C5xfMSma.mjs → hostInit-DjLtjT-_.mjs} +2 -2
  11. package/dist/index.js +1 -1
  12. package/dist/orchestrator/training.addon.js +1 -1
  13. package/dist/orchestrator/training.addon.mjs +1 -1
  14. package/dist/{player-overlays-DPQZJVmM.mjs → player-overlays-C_B-yc3g.mjs} +1 -1
  15. package/dist/providers/modal/training-modal.addon.js +1 -1
  16. package/dist/providers/modal/training-modal.addon.mjs +1 -1
  17. package/dist/remoteEntry.js +1 -1
  18. package/dist/{responsive-Dq7_CDVT.mjs → responsive-YANRyril.mjs} +1 -1
  19. package/dist/{scene-monitor-copy-B6AKXb0r.mjs → scene-monitor-copy-B0w92y1z.mjs} +1 -1
  20. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-CLjAuQ-T.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-CosY3aLX.mjs} +1 -1
  21. package/package.json +1 -1
  22. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-L5NIyvFm.mjs +0 -26
@@ -5621,7 +5621,7 @@ var ZodIssueCode = {
5621
5621
  var ZodFirstPartyTypeKind;
5622
5622
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5623
5623
  //#endregion
5624
- //#region ../types/dist/sleep-6eG9C8Xl.mjs
5624
+ //#region ../types/dist/sleep-63gM_1JR.mjs
5625
5625
  /**
5626
5626
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5627
5627
  * window to float samples (D455).
@@ -9149,6 +9149,7 @@ var CAP_NAMES = Object.freeze([
9149
9149
  "recording-signal",
9150
9150
  "records",
9151
9151
  "remote",
9152
+ "remote-layout-components",
9152
9153
  "running-state",
9153
9154
  "scene-monitor",
9154
9155
  "script-runner",
@@ -58599,6 +58600,30 @@ object({
58599
58600
  at: number()
58600
58601
  }).nullable()
58601
58602
  });
58603
+ var RemoteButtonShapeSchema = _enum([
58604
+ "rect",
58605
+ "rounded",
58606
+ "round",
58607
+ "pill"
58608
+ ]);
58609
+ var RemoteButtonToneSchema = _enum([
58610
+ "neutral",
58611
+ "accent",
58612
+ "danger",
58613
+ "red",
58614
+ "green",
58615
+ "yellow",
58616
+ "blue"
58617
+ ]);
58618
+ var RemoteGroupFrameSchema = _enum([
58619
+ "none",
58620
+ "panel",
58621
+ "divider",
58622
+ "dpad",
58623
+ "rocker-v",
58624
+ "rocker-h",
58625
+ "keypad"
58626
+ ]);
58602
58627
  var RemoteLayoutButtonSchema = object({
58603
58628
  id: string().min(1).max(40),
58604
58629
  /** 0-based grid position. */
@@ -58608,12 +58633,94 @@ var RemoteLayoutButtonSchema = object({
58608
58633
  colSpan: number().int().min(1).max(24),
58609
58634
  icon: string().min(1).max(60).optional(),
58610
58635
  label: string().min(1).max(40).optional(),
58611
- role: RemoteRoleSchema
58636
+ role: RemoteRoleSchema,
58637
+ /** Absent = the plain rendering every layout had before shapes existed. */
58638
+ shape: RemoteButtonShapeSchema.optional(),
58639
+ tone: RemoteButtonToneSchema.optional(),
58640
+ /** The group this button belongs to; it must lie inside that group's rectangle. */
58641
+ groupId: string().min(1).max(40).optional()
58612
58642
  });
58643
+ /**
58644
+ * A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
58645
+ * titled panel, a section divider, a D-pad ring. `componentId` names the palette
58646
+ * entry it came from (informational: the layout is self-contained).
58647
+ */
58648
+ var RemoteLayoutGroupSchema = object({
58649
+ id: string().min(1).max(40),
58650
+ componentId: string().min(1).max(80),
58651
+ frame: RemoteGroupFrameSchema,
58652
+ row: number().int().min(0).max(24),
58653
+ col: number().int().min(0).max(24),
58654
+ rowSpan: number().int().min(1).max(24),
58655
+ colSpan: number().int().min(1).max(24),
58656
+ title: string().min(1).max(40).optional()
58657
+ });
58658
+ function rectsOverlap(a, b) {
58659
+ return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
58660
+ }
58661
+ function rectContains(outer, inner) {
58662
+ return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
58663
+ }
58664
+ /**
58665
+ * The group rules of a layout: inside the grid, no two groups overlap, a
58666
+ * member lies inside its group, a divider holds nothing, and a button that is
58667
+ * not a member never sits inside someone else's rectangle (a group moves as
58668
+ * one thing, so a stray key under it would be left behind).
58669
+ */
58670
+ function refineGroups(grid, ctx) {
58671
+ const groups = grid.groups ?? [];
58672
+ const byId = /* @__PURE__ */ new Map();
58673
+ const fail = (message, path) => {
58674
+ ctx.addIssue({
58675
+ code: "custom",
58676
+ message,
58677
+ path
58678
+ });
58679
+ };
58680
+ groups.forEach((group, index) => {
58681
+ if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
58682
+ "groups",
58683
+ index,
58684
+ "id"
58685
+ ]);
58686
+ byId.set(group.id, group);
58687
+ if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
58688
+ for (let other = 0; other < index; other += 1) {
58689
+ const earlier = groups[other];
58690
+ if (earlier !== void 0 && rectsOverlap(earlier, group)) {
58691
+ fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
58692
+ return;
58693
+ }
58694
+ }
58695
+ });
58696
+ grid.buttons.forEach((button, index) => {
58697
+ if (button.groupId !== void 0) {
58698
+ const group = byId.get(button.groupId);
58699
+ if (group === void 0) {
58700
+ fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
58701
+ "buttons",
58702
+ index,
58703
+ "groupId"
58704
+ ]);
58705
+ return;
58706
+ }
58707
+ if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
58708
+ "buttons",
58709
+ index,
58710
+ "groupId"
58711
+ ]);
58712
+ if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
58713
+ return;
58714
+ }
58715
+ const foreign = groups.find((group) => rectsOverlap(group, button));
58716
+ if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
58717
+ });
58718
+ }
58613
58719
  object({
58614
58720
  columns: number().int().min(1).max(24),
58615
58721
  rows: number().int().min(1).max(24),
58616
- buttons: array(RemoteLayoutButtonSchema).max(200)
58722
+ buttons: array(RemoteLayoutButtonSchema).max(200),
58723
+ groups: array(RemoteLayoutGroupSchema).max(50).optional()
58617
58724
  }).superRefine((grid, ctx) => {
58618
58725
  const ids = /* @__PURE__ */ new Set();
58619
58726
  const taken = /* @__PURE__ */ new Map();
@@ -58650,6 +58757,7 @@ object({
58650
58757
  taken.set(cell, button.id);
58651
58758
  }
58652
58759
  });
58760
+ refineGroups(grid, ctx);
58653
58761
  });
58654
58762
  var RemoteBoundCommandSchema = object({
58655
58763
  role: RemoteRoleSchema,
@@ -58781,6 +58889,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
58781
58889
  kind: "mutation",
58782
58890
  auth: "admin"
58783
58891
  });
58892
+ var RemoteComponentCategorySchema = _enum([
58893
+ "power",
58894
+ "navigation",
58895
+ "volume",
58896
+ "numbers",
58897
+ "media",
58898
+ "colour",
58899
+ "apps",
58900
+ "layout"
58901
+ ]);
58902
+ /** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
58903
+ var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
58904
+ var RemoteComponentButtonSchema = object({
58905
+ /** Joined to the instance id when the component is inserted. */
58906
+ idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
58907
+ row: number().int().min(0).max(24),
58908
+ col: number().int().min(0).max(24),
58909
+ rowSpan: number().int().min(1).max(24),
58910
+ colSpan: number().int().min(1).max(24),
58911
+ role: RemoteRoleSchema,
58912
+ icon: string().min(1).max(60).optional(),
58913
+ label: string().min(1).max(40).optional(),
58914
+ shape: RemoteButtonShapeSchema.optional(),
58915
+ tone: RemoteButtonToneSchema.optional()
58916
+ });
58917
+ var RemoteLayoutComponentSchema = object({
58918
+ id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
58919
+ name: string().min(1).max(60),
58920
+ nameI18n: object({
58921
+ en: string().min(1).max(60),
58922
+ it: string().min(1).max(60)
58923
+ }).optional(),
58924
+ /** A display icon key, resolved by the UI like a button icon. */
58925
+ icon: string().min(1).max(60),
58926
+ category: RemoteComponentCategorySchema,
58927
+ size: object({
58928
+ rows: number().int().min(1).max(24),
58929
+ cols: number().int().min(1).max(24)
58930
+ }),
58931
+ /** The frame drawn around the whole component, or null for loose buttons. */
58932
+ group: object({
58933
+ frame: RemoteGroupFrameSchema,
58934
+ title: string().min(1).max(40).optional()
58935
+ }).nullable(),
58936
+ buttons: array(RemoteComponentButtonSchema).max(40)
58937
+ }).superRefine((component, ctx) => {
58938
+ const suffixes = /* @__PURE__ */ new Set();
58939
+ component.buttons.forEach((button, index) => {
58940
+ if (suffixes.has(button.idSuffix)) ctx.addIssue({
58941
+ code: "custom",
58942
+ message: `button "${button.idSuffix}" is listed twice`,
58943
+ path: [
58944
+ "buttons",
58945
+ index,
58946
+ "idSuffix"
58947
+ ]
58948
+ });
58949
+ suffixes.add(button.idSuffix);
58950
+ if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
58951
+ ctx.addIssue({
58952
+ code: "custom",
58953
+ message: `button "${button.idSuffix}" leaves the component`,
58954
+ path: ["buttons", index]
58955
+ });
58956
+ return;
58957
+ }
58958
+ for (let other = 0; other < index; other += 1) {
58959
+ const earlier = component.buttons[other];
58960
+ if (earlier !== void 0 && rectsOverlap(earlier, button)) {
58961
+ ctx.addIssue({
58962
+ code: "custom",
58963
+ message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
58964
+ path: ["buttons", index]
58965
+ });
58966
+ return;
58967
+ }
58968
+ }
58969
+ });
58970
+ if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
58971
+ code: "custom",
58972
+ message: "a divider component holds no buttons",
58973
+ path: ["buttons"]
58974
+ });
58975
+ });
58976
+ method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
58784
58977
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
58785
58978
  kind: "mutation",
58786
58979
  auth: "protected"
@@ -67818,6 +68011,87 @@ var trainingCapability = {
67818
68011
  })
67819
68012
  }
67820
68013
  };
68014
+ var btn = (idSuffix, row, col, init) => ({
68015
+ idSuffix,
68016
+ row,
68017
+ col,
68018
+ rowSpan: 1,
68019
+ colSpan: 1,
68020
+ ...init
68021
+ });
68022
+ function rocker(id, name, it, frame, title, buttons, size) {
68023
+ return {
68024
+ id,
68025
+ name,
68026
+ nameI18n: {
68027
+ en: name,
68028
+ it
68029
+ },
68030
+ icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
68031
+ category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
68032
+ size,
68033
+ group: title === null ? { frame } : {
68034
+ frame,
68035
+ title
68036
+ },
68037
+ buttons
68038
+ };
68039
+ }
68040
+ btn("power", 0, 0, {
68041
+ role: "power",
68042
+ shape: "round",
68043
+ tone: "danger"
68044
+ }), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
68045
+ role: "ok",
68046
+ shape: "round",
68047
+ tone: "accent"
68048
+ }), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
68049
+ role: "volume_up",
68050
+ shape: "pill"
68051
+ }), btn("down", 1, 0, {
68052
+ role: "volume_down",
68053
+ shape: "pill"
68054
+ })], {
68055
+ rows: 2,
68056
+ cols: 1
68057
+ }), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
68058
+ role: "channel_up",
68059
+ shape: "pill"
68060
+ }), btn("down", 1, 0, {
68061
+ role: "channel_down",
68062
+ shape: "pill"
68063
+ })], {
68064
+ rows: 2,
68065
+ cols: 1
68066
+ }), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
68067
+ role: "custom:minus",
68068
+ label: "−",
68069
+ shape: "pill"
68070
+ }), btn("plus", 0, 1, {
68071
+ role: "custom:plus",
68072
+ label: "+",
68073
+ shape: "pill"
68074
+ })], {
68075
+ rows: 1,
68076
+ cols: 2
68077
+ }), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
68078
+ role: "custom:color_red",
68079
+ tone: "red"
68080
+ }), btn("green", 0, 1, {
68081
+ role: "custom:color_green",
68082
+ tone: "green"
68083
+ }), btn("yellow", 0, 2, {
68084
+ role: "custom:color_yellow",
68085
+ tone: "yellow"
68086
+ }), btn("blue", 0, 3, {
68087
+ role: "custom:color_blue",
68088
+ tone: "blue"
68089
+ }), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
68090
+ 1,
68091
+ 2,
68092
+ 3,
68093
+ 4
68094
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
67821
68095
  discriminatedUnion("kind", [object({
67822
68096
  kind: literal("raw"),
67823
68097
  timings: array(number().int()),
@@ -75682,6 +75956,13 @@ Object.freeze({
75682
75956
  access: "create",
75683
75957
  role: "admin"
75684
75958
  },
75959
+ "remoteLayoutComponents.listComponents": {
75960
+ capName: "remote-layout-components",
75961
+ capScope: "system",
75962
+ addonId: null,
75963
+ access: "view",
75964
+ role: "admin"
75965
+ },
75685
75966
  "runningState.getStatus": {
75686
75967
  capName: "running-state",
75687
75968
  capScope: "device",
@@ -81858,6 +82139,7 @@ var SYSTEM_CAP_VOCABULARY = {
81858
82139
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
81859
82140
  records: system(INTERNAL),
81860
82141
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
82142
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
81861
82143
  "server-management": system(CLUSTER),
81862
82144
  "settings-store": system(INTERNAL),
81863
82145
  "smtp-provider": system(INTERNAL),
@@ -5588,7 +5588,7 @@ var ZodIssueCode = {
5588
5588
  var ZodFirstPartyTypeKind;
5589
5589
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5590
5590
  //#endregion
5591
- //#region ../types/dist/sleep-6eG9C8Xl.mjs
5591
+ //#region ../types/dist/sleep-63gM_1JR.mjs
5592
5592
  /**
5593
5593
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5594
5594
  * window to float samples (D455).
@@ -9116,6 +9116,7 @@ var CAP_NAMES = Object.freeze([
9116
9116
  "recording-signal",
9117
9117
  "records",
9118
9118
  "remote",
9119
+ "remote-layout-components",
9119
9120
  "running-state",
9120
9121
  "scene-monitor",
9121
9122
  "script-runner",
@@ -58566,6 +58567,30 @@ object({
58566
58567
  at: number()
58567
58568
  }).nullable()
58568
58569
  });
58570
+ var RemoteButtonShapeSchema = _enum([
58571
+ "rect",
58572
+ "rounded",
58573
+ "round",
58574
+ "pill"
58575
+ ]);
58576
+ var RemoteButtonToneSchema = _enum([
58577
+ "neutral",
58578
+ "accent",
58579
+ "danger",
58580
+ "red",
58581
+ "green",
58582
+ "yellow",
58583
+ "blue"
58584
+ ]);
58585
+ var RemoteGroupFrameSchema = _enum([
58586
+ "none",
58587
+ "panel",
58588
+ "divider",
58589
+ "dpad",
58590
+ "rocker-v",
58591
+ "rocker-h",
58592
+ "keypad"
58593
+ ]);
58569
58594
  var RemoteLayoutButtonSchema = object({
58570
58595
  id: string().min(1).max(40),
58571
58596
  /** 0-based grid position. */
@@ -58575,12 +58600,94 @@ var RemoteLayoutButtonSchema = object({
58575
58600
  colSpan: number().int().min(1).max(24),
58576
58601
  icon: string().min(1).max(60).optional(),
58577
58602
  label: string().min(1).max(40).optional(),
58578
- role: RemoteRoleSchema
58603
+ role: RemoteRoleSchema,
58604
+ /** Absent = the plain rendering every layout had before shapes existed. */
58605
+ shape: RemoteButtonShapeSchema.optional(),
58606
+ tone: RemoteButtonToneSchema.optional(),
58607
+ /** The group this button belongs to; it must lie inside that group's rectangle. */
58608
+ groupId: string().min(1).max(40).optional()
58579
58609
  });
58610
+ /**
58611
+ * A rectangle of the grid that moves as ONE thing and is drawn with a frame: a
58612
+ * titled panel, a section divider, a D-pad ring. `componentId` names the palette
58613
+ * entry it came from (informational: the layout is self-contained).
58614
+ */
58615
+ var RemoteLayoutGroupSchema = object({
58616
+ id: string().min(1).max(40),
58617
+ componentId: string().min(1).max(80),
58618
+ frame: RemoteGroupFrameSchema,
58619
+ row: number().int().min(0).max(24),
58620
+ col: number().int().min(0).max(24),
58621
+ rowSpan: number().int().min(1).max(24),
58622
+ colSpan: number().int().min(1).max(24),
58623
+ title: string().min(1).max(40).optional()
58624
+ });
58625
+ function rectsOverlap(a, b) {
58626
+ return a.row < b.row + b.rowSpan && b.row < a.row + a.rowSpan && a.col < b.col + b.colSpan && b.col < a.col + a.colSpan;
58627
+ }
58628
+ function rectContains(outer, inner) {
58629
+ return inner.row >= outer.row && inner.col >= outer.col && inner.row + inner.rowSpan <= outer.row + outer.rowSpan && inner.col + inner.colSpan <= outer.col + outer.colSpan;
58630
+ }
58631
+ /**
58632
+ * The group rules of a layout: inside the grid, no two groups overlap, a
58633
+ * member lies inside its group, a divider holds nothing, and a button that is
58634
+ * not a member never sits inside someone else's rectangle (a group moves as
58635
+ * one thing, so a stray key under it would be left behind).
58636
+ */
58637
+ function refineGroups(grid, ctx) {
58638
+ const groups = grid.groups ?? [];
58639
+ const byId = /* @__PURE__ */ new Map();
58640
+ const fail = (message, path) => {
58641
+ ctx.addIssue({
58642
+ code: "custom",
58643
+ message,
58644
+ path
58645
+ });
58646
+ };
58647
+ groups.forEach((group, index) => {
58648
+ if (byId.has(group.id)) fail(`group id "${group.id}" is used twice`, [
58649
+ "groups",
58650
+ index,
58651
+ "id"
58652
+ ]);
58653
+ byId.set(group.id, group);
58654
+ if (group.row + group.rowSpan > grid.rows || group.col + group.colSpan > grid.columns) fail(`group "${group.id}" leaves the grid`, ["groups", index]);
58655
+ for (let other = 0; other < index; other += 1) {
58656
+ const earlier = groups[other];
58657
+ if (earlier !== void 0 && rectsOverlap(earlier, group)) {
58658
+ fail(`groups "${earlier.id}" and "${group.id}" overlap`, ["groups", index]);
58659
+ return;
58660
+ }
58661
+ }
58662
+ });
58663
+ grid.buttons.forEach((button, index) => {
58664
+ if (button.groupId !== void 0) {
58665
+ const group = byId.get(button.groupId);
58666
+ if (group === void 0) {
58667
+ fail(`button "${button.id}" names the unknown group "${button.groupId}"`, [
58668
+ "buttons",
58669
+ index,
58670
+ "groupId"
58671
+ ]);
58672
+ return;
58673
+ }
58674
+ if (group.frame === "divider") fail(`a divider group ("${group.id}") holds no buttons`, [
58675
+ "buttons",
58676
+ index,
58677
+ "groupId"
58678
+ ]);
58679
+ if (!rectContains(group, button)) fail(`button "${button.id}" lies outside its group "${group.id}"`, ["buttons", index]);
58680
+ return;
58681
+ }
58682
+ const foreign = groups.find((group) => rectsOverlap(group, button));
58683
+ if (foreign !== void 0) fail(`button "${button.id}" lies inside group "${foreign.id}" but is not a member`, ["buttons", index]);
58684
+ });
58685
+ }
58580
58686
  object({
58581
58687
  columns: number().int().min(1).max(24),
58582
58688
  rows: number().int().min(1).max(24),
58583
- buttons: array(RemoteLayoutButtonSchema).max(200)
58689
+ buttons: array(RemoteLayoutButtonSchema).max(200),
58690
+ groups: array(RemoteLayoutGroupSchema).max(50).optional()
58584
58691
  }).superRefine((grid, ctx) => {
58585
58692
  const ids = /* @__PURE__ */ new Set();
58586
58693
  const taken = /* @__PURE__ */ new Map();
@@ -58617,6 +58724,7 @@ object({
58617
58724
  taken.set(cell, button.id);
58618
58725
  }
58619
58726
  });
58727
+ refineGroups(grid, ctx);
58620
58728
  });
58621
58729
  var RemoteBoundCommandSchema = object({
58622
58730
  role: RemoteRoleSchema,
@@ -58748,6 +58856,91 @@ method(object({ kind: TemplateKindSchema.optional() }), array(TemplateRecordSche
58748
58856
  kind: "mutation",
58749
58857
  auth: "admin"
58750
58858
  });
58859
+ var RemoteComponentCategorySchema = _enum([
58860
+ "power",
58861
+ "navigation",
58862
+ "volume",
58863
+ "numbers",
58864
+ "media",
58865
+ "colour",
58866
+ "apps",
58867
+ "layout"
58868
+ ]);
58869
+ /** `<namespace>:<name>`, both lowercase slugs — `core:dpad`, `<addonId>:<name>`. */
58870
+ var COMPONENT_ID_RE = /^[a-z0-9][a-z0-9_.-]*:[a-z0-9][a-z0-9_-]*$/;
58871
+ var RemoteComponentButtonSchema = object({
58872
+ /** Joined to the instance id when the component is inserted. */
58873
+ idSuffix: string().regex(/^[a-z0-9][a-z0-9_-]{0,19}$/, { message: "a short lowercase slug" }),
58874
+ row: number().int().min(0).max(24),
58875
+ col: number().int().min(0).max(24),
58876
+ rowSpan: number().int().min(1).max(24),
58877
+ colSpan: number().int().min(1).max(24),
58878
+ role: RemoteRoleSchema,
58879
+ icon: string().min(1).max(60).optional(),
58880
+ label: string().min(1).max(40).optional(),
58881
+ shape: RemoteButtonShapeSchema.optional(),
58882
+ tone: RemoteButtonToneSchema.optional()
58883
+ });
58884
+ var RemoteLayoutComponentSchema = object({
58885
+ id: string().max(80).regex(COMPONENT_ID_RE, { message: "<namespace>:<name>, lowercase" }),
58886
+ name: string().min(1).max(60),
58887
+ nameI18n: object({
58888
+ en: string().min(1).max(60),
58889
+ it: string().min(1).max(60)
58890
+ }).optional(),
58891
+ /** A display icon key, resolved by the UI like a button icon. */
58892
+ icon: string().min(1).max(60),
58893
+ category: RemoteComponentCategorySchema,
58894
+ size: object({
58895
+ rows: number().int().min(1).max(24),
58896
+ cols: number().int().min(1).max(24)
58897
+ }),
58898
+ /** The frame drawn around the whole component, or null for loose buttons. */
58899
+ group: object({
58900
+ frame: RemoteGroupFrameSchema,
58901
+ title: string().min(1).max(40).optional()
58902
+ }).nullable(),
58903
+ buttons: array(RemoteComponentButtonSchema).max(40)
58904
+ }).superRefine((component, ctx) => {
58905
+ const suffixes = /* @__PURE__ */ new Set();
58906
+ component.buttons.forEach((button, index) => {
58907
+ if (suffixes.has(button.idSuffix)) ctx.addIssue({
58908
+ code: "custom",
58909
+ message: `button "${button.idSuffix}" is listed twice`,
58910
+ path: [
58911
+ "buttons",
58912
+ index,
58913
+ "idSuffix"
58914
+ ]
58915
+ });
58916
+ suffixes.add(button.idSuffix);
58917
+ if (button.row + button.rowSpan > component.size.rows || button.col + button.colSpan > component.size.cols) {
58918
+ ctx.addIssue({
58919
+ code: "custom",
58920
+ message: `button "${button.idSuffix}" leaves the component`,
58921
+ path: ["buttons", index]
58922
+ });
58923
+ return;
58924
+ }
58925
+ for (let other = 0; other < index; other += 1) {
58926
+ const earlier = component.buttons[other];
58927
+ if (earlier !== void 0 && rectsOverlap(earlier, button)) {
58928
+ ctx.addIssue({
58929
+ code: "custom",
58930
+ message: `buttons "${earlier.idSuffix}" and "${button.idSuffix}" overlap`,
58931
+ path: ["buttons", index]
58932
+ });
58933
+ return;
58934
+ }
58935
+ }
58936
+ });
58937
+ if (component.group?.frame === "divider" && component.buttons.length > 0) ctx.addIssue({
58938
+ code: "custom",
58939
+ message: "a divider component holds no buttons",
58940
+ path: ["buttons"]
58941
+ });
58942
+ });
58943
+ method(object({}), array(RemoteLayoutComponentSchema), { auth: "protected" });
58751
58944
  DeviceType.Button, method(object({ deviceId: number().int().nonnegative() }), _void(), {
58752
58945
  kind: "mutation",
58753
58946
  auth: "protected"
@@ -67785,6 +67978,87 @@ var trainingCapability = {
67785
67978
  })
67786
67979
  }
67787
67980
  };
67981
+ var btn = (idSuffix, row, col, init) => ({
67982
+ idSuffix,
67983
+ row,
67984
+ col,
67985
+ rowSpan: 1,
67986
+ colSpan: 1,
67987
+ ...init
67988
+ });
67989
+ function rocker(id, name, it, frame, title, buttons, size) {
67990
+ return {
67991
+ id,
67992
+ name,
67993
+ nameI18n: {
67994
+ en: name,
67995
+ it
67996
+ },
67997
+ icon: frame === "rocker-v" ? "chevrons-up-down" : "chevrons-left-right",
67998
+ category: id.includes("channel") || id.includes("volume") ? "volume" : "navigation",
67999
+ size,
68000
+ group: title === null ? { frame } : {
68001
+ frame,
68002
+ title
68003
+ },
68004
+ buttons
68005
+ };
68006
+ }
68007
+ btn("power", 0, 0, {
68008
+ role: "power",
68009
+ shape: "round",
68010
+ tone: "danger"
68011
+ }), btn("up", 0, 1, { role: "up" }), btn("left", 1, 0, { role: "left" }), btn("ok", 1, 1, {
68012
+ role: "ok",
68013
+ shape: "round",
68014
+ tone: "accent"
68015
+ }), btn("right", 1, 2, { role: "right" }), btn("down", 2, 1, { role: "down" }), rocker("core:rocker-volume-v", "Volume rocker", "Bilanciere volume", "rocker-v", "VOL", [btn("up", 0, 0, {
68016
+ role: "volume_up",
68017
+ shape: "pill"
68018
+ }), btn("down", 1, 0, {
68019
+ role: "volume_down",
68020
+ shape: "pill"
68021
+ })], {
68022
+ rows: 2,
68023
+ cols: 1
68024
+ }), rocker("core:rocker-channel-v", "Channel rocker", "Bilanciere canali", "rocker-v", "CH", [btn("up", 0, 0, {
68025
+ role: "channel_up",
68026
+ shape: "pill"
68027
+ }), btn("down", 1, 0, {
68028
+ role: "channel_down",
68029
+ shape: "pill"
68030
+ })], {
68031
+ rows: 2,
68032
+ cols: 1
68033
+ }), rocker("core:rocker-h", "Minus / plus rocker", "Bilanciere meno / più", "rocker-h", null, [btn("minus", 0, 0, {
68034
+ role: "custom:minus",
68035
+ label: "−",
68036
+ shape: "pill"
68037
+ }), btn("plus", 0, 1, {
68038
+ role: "custom:plus",
68039
+ label: "+",
68040
+ shape: "pill"
68041
+ })], {
68042
+ rows: 1,
68043
+ cols: 2
68044
+ }), [...Array.from({ length: 9 }, (_, i) => btn(`d${i + 1}`, Math.floor(i / 3), i % 3, { role: `digit_${i + 1}` })), btn("d0", 3, 1, { role: "digit_0" })], btn("red", 0, 0, {
68045
+ role: "custom:color_red",
68046
+ tone: "red"
68047
+ }), btn("green", 0, 1, {
68048
+ role: "custom:color_green",
68049
+ tone: "green"
68050
+ }), btn("yellow", 0, 2, {
68051
+ role: "custom:color_yellow",
68052
+ tone: "yellow"
68053
+ }), btn("blue", 0, 3, {
68054
+ role: "custom:color_blue",
68055
+ tone: "blue"
68056
+ }), btn("rewind", 0, 0, { role: "rewind" }), btn("play", 0, 1, { role: "play" }), btn("pause", 0, 2, { role: "pause" }), btn("forward", 0, 3, { role: "fast_forward" }), btn("stop", 0, 4, { role: "stop" }), [
68057
+ 1,
68058
+ 2,
68059
+ 3,
68060
+ 4
68061
+ ].map((n) => btn(`app${n}`, 0, n - 1, { role: `custom:app_slot_${n}` })), btn("back", 0, 0, { role: "back" }), btn("home", 0, 1, { role: "home" }), btn("menu", 0, 2, { role: "menu" });
67788
68062
  discriminatedUnion("kind", [object({
67789
68063
  kind: literal("raw"),
67790
68064
  timings: array(number().int()),
@@ -75649,6 +75923,13 @@ Object.freeze({
75649
75923
  access: "create",
75650
75924
  role: "admin"
75651
75925
  },
75926
+ "remoteLayoutComponents.listComponents": {
75927
+ capName: "remote-layout-components",
75928
+ capScope: "system",
75929
+ addonId: null,
75930
+ access: "view",
75931
+ role: "admin"
75932
+ },
75652
75933
  "runningState.getStatus": {
75653
75934
  capName: "running-state",
75654
75935
  capScope: "device",
@@ -81825,6 +82106,7 @@ var SYSTEM_CAP_VOCABULARY = {
81825
82106
  "recording-export": system("recording exports — requested from the timeline, not an automation step"),
81826
82107
  records: system(INTERNAL),
81827
82108
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
82109
+ "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
81828
82110
  "server-management": system(CLUSTER),
81829
82111
  "settings-store": system(INTERNAL),
81830
82112
  "smtp-provider": system(INTERNAL),