@nyaruka/temba-components 0.138.6 → 0.140.0

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 (196) hide show
  1. package/.github/workflows/cla.yml +1 -1
  2. package/.github/workflows/copilot-setup-steps.yml +6 -1
  3. package/CHANGELOG.md +26 -0
  4. package/demo/data/flows/sample-flow.json +24 -0
  5. package/dist/locales/es.js +5 -5
  6. package/dist/locales/es.js.map +1 -1
  7. package/dist/locales/fr.js +5 -5
  8. package/dist/locales/fr.js.map +1 -1
  9. package/dist/locales/locale-codes.js +2 -11
  10. package/dist/locales/locale-codes.js.map +1 -1
  11. package/dist/locales/pt.js +5 -5
  12. package/dist/locales/pt.js.map +1 -1
  13. package/dist/temba-components.js +1112 -882
  14. package/dist/temba-components.js.map +1 -1
  15. package/out-tsc/src/display/Chat.js +10 -7
  16. package/out-tsc/src/display/Chat.js.map +1 -1
  17. package/out-tsc/src/display/Dropdown.js +3 -1
  18. package/out-tsc/src/display/Dropdown.js.map +1 -1
  19. package/out-tsc/src/display/FloatingTab.js +25 -32
  20. package/out-tsc/src/display/FloatingTab.js.map +1 -1
  21. package/out-tsc/src/display/Thumbnail.js +163 -5
  22. package/out-tsc/src/display/Thumbnail.js.map +1 -1
  23. package/out-tsc/src/flow/CanvasMenu.js +5 -3
  24. package/out-tsc/src/flow/CanvasMenu.js.map +1 -1
  25. package/out-tsc/src/flow/CanvasNode.js +70 -29
  26. package/out-tsc/src/flow/CanvasNode.js.map +1 -1
  27. package/out-tsc/src/flow/Editor.js +290 -239
  28. package/out-tsc/src/flow/Editor.js.map +1 -1
  29. package/out-tsc/src/flow/NodeEditor.js +118 -10
  30. package/out-tsc/src/flow/NodeEditor.js.map +1 -1
  31. package/out-tsc/src/flow/Plumber.js +757 -403
  32. package/out-tsc/src/flow/Plumber.js.map +1 -1
  33. package/out-tsc/src/flow/StickyNote.js +13 -4
  34. package/out-tsc/src/flow/StickyNote.js.map +1 -1
  35. package/out-tsc/src/flow/actions/audio-player.js +112 -0
  36. package/out-tsc/src/flow/actions/audio-player.js.map +1 -0
  37. package/out-tsc/src/flow/actions/enter_flow.js +43 -0
  38. package/out-tsc/src/flow/actions/enter_flow.js.map +1 -0
  39. package/out-tsc/src/flow/actions/play_audio.js +57 -4
  40. package/out-tsc/src/flow/actions/play_audio.js.map +1 -1
  41. package/out-tsc/src/flow/actions/say_msg.js +86 -3
  42. package/out-tsc/src/flow/actions/say_msg.js.map +1 -1
  43. package/out-tsc/src/flow/config.js +11 -3
  44. package/out-tsc/src/flow/config.js.map +1 -1
  45. package/out-tsc/src/flow/nodes/shared-rules.js +1 -1
  46. package/out-tsc/src/flow/nodes/shared-rules.js.map +1 -1
  47. package/out-tsc/src/flow/nodes/terminal.js +7 -0
  48. package/out-tsc/src/flow/nodes/terminal.js.map +1 -0
  49. package/out-tsc/src/flow/nodes/wait_for_audio.js +77 -0
  50. package/out-tsc/src/flow/nodes/wait_for_audio.js.map +1 -0
  51. package/out-tsc/src/flow/nodes/wait_for_dial.js +151 -0
  52. package/out-tsc/src/flow/nodes/wait_for_dial.js.map +1 -0
  53. package/out-tsc/src/flow/nodes/wait_for_digits.js +61 -1
  54. package/out-tsc/src/flow/nodes/wait_for_digits.js.map +1 -1
  55. package/out-tsc/src/flow/nodes/wait_for_menu.js +173 -2
  56. package/out-tsc/src/flow/nodes/wait_for_menu.js.map +1 -1
  57. package/out-tsc/src/flow/operators.js +21 -5
  58. package/out-tsc/src/flow/operators.js.map +1 -1
  59. package/out-tsc/src/flow/types.js.map +1 -1
  60. package/out-tsc/src/flow/utils.js +213 -65
  61. package/out-tsc/src/flow/utils.js.map +1 -1
  62. package/out-tsc/src/form/ArrayEditor.js +4 -2
  63. package/out-tsc/src/form/ArrayEditor.js.map +1 -1
  64. package/out-tsc/src/form/FieldRenderer.js +49 -0
  65. package/out-tsc/src/form/FieldRenderer.js.map +1 -1
  66. package/out-tsc/src/interfaces.js +2 -0
  67. package/out-tsc/src/interfaces.js.map +1 -1
  68. package/out-tsc/src/layout/Dialog.js +52 -7
  69. package/out-tsc/src/layout/Dialog.js.map +1 -1
  70. package/out-tsc/src/list/TicketList.js +4 -1
  71. package/out-tsc/src/list/TicketList.js.map +1 -1
  72. package/out-tsc/src/live/TembaChart.js.map +1 -1
  73. package/out-tsc/src/locales/es.js +5 -5
  74. package/out-tsc/src/locales/es.js.map +1 -1
  75. package/out-tsc/src/locales/fr.js +5 -5
  76. package/out-tsc/src/locales/fr.js.map +1 -1
  77. package/out-tsc/src/locales/locale-codes.js +2 -11
  78. package/out-tsc/src/locales/locale-codes.js.map +1 -1
  79. package/out-tsc/src/locales/pt.js +5 -5
  80. package/out-tsc/src/locales/pt.js.map +1 -1
  81. package/out-tsc/src/simulator/Simulator.js +10 -3
  82. package/out-tsc/src/simulator/Simulator.js.map +1 -1
  83. package/out-tsc/src/store/AppState.js +89 -3
  84. package/out-tsc/src/store/AppState.js.map +1 -1
  85. package/out-tsc/test/actions/play_audio.test.js +118 -0
  86. package/out-tsc/test/actions/play_audio.test.js.map +1 -0
  87. package/out-tsc/test/actions/say_msg.test.js +158 -0
  88. package/out-tsc/test/actions/say_msg.test.js.map +1 -0
  89. package/out-tsc/test/nodes/wait_for_audio.test.js +156 -0
  90. package/out-tsc/test/nodes/wait_for_audio.test.js.map +1 -0
  91. package/out-tsc/test/nodes/wait_for_dial.test.js +336 -0
  92. package/out-tsc/test/nodes/wait_for_dial.test.js.map +1 -0
  93. package/out-tsc/test/nodes/wait_for_digits.test.js +198 -84
  94. package/out-tsc/test/nodes/wait_for_digits.test.js.map +1 -1
  95. package/out-tsc/test/nodes/wait_for_menu.test.js +340 -0
  96. package/out-tsc/test/nodes/wait_for_menu.test.js.map +1 -0
  97. package/out-tsc/test/temba-floating-tab.test.js +4 -6
  98. package/out-tsc/test/temba-floating-tab.test.js.map +1 -1
  99. package/out-tsc/test/temba-flow-collision.test.js +473 -220
  100. package/out-tsc/test/temba-flow-collision.test.js.map +1 -1
  101. package/out-tsc/test/temba-flow-editor.test.js +0 -2
  102. package/out-tsc/test/temba-flow-editor.test.js.map +1 -1
  103. package/out-tsc/test/temba-flow-plumber-connections.test.js +83 -84
  104. package/out-tsc/test/temba-flow-plumber-connections.test.js.map +1 -1
  105. package/out-tsc/test/temba-flow-plumber.test.js +102 -93
  106. package/out-tsc/test/temba-flow-plumber.test.js.map +1 -1
  107. package/out-tsc/test/temba-node-type-selector.test.js +6 -6
  108. package/out-tsc/test/temba-node-type-selector.test.js.map +1 -1
  109. package/package.json +1 -1
  110. package/screenshots/truth/actions/play_audio/editor/expression-url.png +0 -0
  111. package/screenshots/truth/actions/play_audio/editor/static-url.png +0 -0
  112. package/screenshots/truth/actions/play_audio/render/expression-url.png +0 -0
  113. package/screenshots/truth/actions/play_audio/render/static-url.png +0 -0
  114. package/screenshots/truth/actions/say_msg/editor/multiline-text.png +0 -0
  115. package/screenshots/truth/actions/say_msg/editor/simple-text.png +0 -0
  116. package/screenshots/truth/actions/say_msg/editor/text-with-audio-url.png +0 -0
  117. package/screenshots/truth/actions/say_msg/render/multiline-text.png +0 -0
  118. package/screenshots/truth/actions/say_msg/render/simple-text.png +0 -0
  119. package/screenshots/truth/actions/say_msg/render/text-with-audio-url.png +0 -0
  120. package/screenshots/truth/editor/router.png +0 -0
  121. package/screenshots/truth/editor/wait.png +0 -0
  122. package/screenshots/truth/nodes/wait_for_audio/editor/basic-audio-wait.png +0 -0
  123. package/screenshots/truth/nodes/wait_for_audio/render/basic-audio-wait.png +0 -0
  124. package/screenshots/truth/nodes/wait_for_dial/editor/basic-dial.png +0 -0
  125. package/screenshots/truth/nodes/wait_for_dial/editor/dial-with-limits.png +0 -0
  126. package/screenshots/truth/nodes/wait_for_dial/render/basic-dial.png +0 -0
  127. package/screenshots/truth/nodes/wait_for_dial/render/dial-with-limits.png +0 -0
  128. package/screenshots/truth/nodes/wait_for_digits/editor/basic-digits-wait.png +0 -0
  129. package/screenshots/truth/nodes/wait_for_digits/editor/digits-with-rules.png +0 -0
  130. package/screenshots/truth/nodes/wait_for_digits/render/basic-digits-wait.png +0 -0
  131. package/screenshots/truth/nodes/wait_for_digits/render/digits-with-rules.png +0 -0
  132. package/screenshots/truth/nodes/wait_for_menu/editor/menu-with-digits.png +0 -0
  133. package/screenshots/truth/nodes/wait_for_menu/render/menu-with-digits.png +0 -0
  134. package/screenshots/truth/nodes/wait_for_response/editor/basic-wait.png +0 -0
  135. package/screenshots/truth/nodes/wait_for_response/editor/custom-result-name.png +0 -0
  136. package/screenshots/truth/nodes/wait_for_response/editor/no-timeout.png +0 -0
  137. package/screenshots/truth/nodes/wait_for_response/editor/short-timeout.png +0 -0
  138. package/screenshots/truth/nodes/wait_for_response/render/basic-wait.png +0 -0
  139. package/screenshots/truth/nodes/wait_for_response/render/custom-result-name.png +0 -0
  140. package/screenshots/truth/nodes/wait_for_response/render/no-timeout.png +0 -0
  141. package/screenshots/truth/nodes/wait_for_response/render/short-timeout.png +0 -0
  142. package/src/display/Chat.ts +13 -7
  143. package/src/display/Dropdown.ts +3 -1
  144. package/src/display/FloatingTab.ts +24 -33
  145. package/src/display/Thumbnail.ts +162 -2
  146. package/src/flow/CanvasMenu.ts +8 -3
  147. package/src/flow/CanvasNode.ts +75 -30
  148. package/src/flow/Editor.ts +336 -288
  149. package/src/flow/NodeEditor.ts +137 -9
  150. package/src/flow/Plumber.ts +1011 -457
  151. package/src/flow/StickyNote.ts +14 -4
  152. package/src/flow/actions/audio-player.ts +127 -0
  153. package/src/flow/actions/enter_flow.ts +44 -0
  154. package/src/flow/actions/play_audio.ts +64 -5
  155. package/src/flow/actions/say_msg.ts +94 -4
  156. package/src/flow/config.ts +11 -3
  157. package/src/flow/nodes/shared-rules.ts +1 -1
  158. package/src/flow/nodes/terminal.ts +9 -0
  159. package/src/flow/nodes/wait_for_audio.ts +88 -0
  160. package/src/flow/nodes/wait_for_dial.ts +176 -0
  161. package/src/flow/nodes/wait_for_digits.ts +86 -2
  162. package/src/flow/nodes/wait_for_menu.ts +209 -3
  163. package/src/flow/operators.ts +23 -5
  164. package/src/flow/types.ts +23 -1
  165. package/src/flow/utils.ts +238 -81
  166. package/src/form/ArrayEditor.ts +4 -2
  167. package/src/form/FieldRenderer.ts +64 -1
  168. package/src/interfaces.ts +3 -1
  169. package/src/layout/Dialog.ts +53 -7
  170. package/src/list/TicketList.ts +4 -1
  171. package/src/live/TembaChart.ts +1 -1
  172. package/src/locales/es.ts +13 -18
  173. package/src/locales/fr.ts +13 -18
  174. package/src/locales/locale-codes.ts +2 -11
  175. package/src/locales/pt.ts +13 -18
  176. package/src/simulator/Simulator.ts +13 -3
  177. package/src/store/AppState.ts +105 -1
  178. package/src/store/flow-definition.d.ts +2 -0
  179. package/test/actions/play_audio.test.ts +155 -0
  180. package/test/actions/say_msg.test.ts +196 -0
  181. package/test/nodes/wait_for_audio.test.ts +182 -0
  182. package/test/nodes/wait_for_dial.test.ts +382 -0
  183. package/test/nodes/wait_for_digits.test.ts +233 -109
  184. package/test/nodes/wait_for_menu.test.ts +383 -0
  185. package/test/temba-floating-tab.test.ts +4 -6
  186. package/test/temba-flow-collision.test.ts +495 -293
  187. package/test/temba-flow-editor.test.ts +0 -2
  188. package/test/temba-flow-plumber-connections.test.ts +97 -97
  189. package/test/temba-flow-plumber.test.ts +116 -103
  190. package/test/temba-node-type-selector.test.ts +6 -6
  191. package/screenshots/truth/nodes/wait_for_digits/editor/phone-number-collection.png +0 -0
  192. package/screenshots/truth/nodes/wait_for_digits/editor/single-digit-with-timeout.png +0 -0
  193. package/screenshots/truth/nodes/wait_for_digits/editor/verification-code.png +0 -0
  194. package/screenshots/truth/nodes/wait_for_digits/render/phone-number-collection.png +0 -0
  195. package/screenshots/truth/nodes/wait_for_digits/render/single-digit-with-timeout.png +0 -0
  196. package/screenshots/truth/nodes/wait_for_digits/render/verification-code.png +0 -0
@@ -20,9 +20,10 @@ import {
20
20
  } from './types';
21
21
  import { CustomEventType } from '../interfaces';
22
22
  import { generateUUID } from '../utils';
23
+ import { formatIssueMessage } from './utils';
23
24
  import { FieldRenderer } from '../form/FieldRenderer';
24
25
  import { renderMarkdownInline } from '../markdown';
25
- import { AppState, fromStore, zustand } from '../store/AppState';
26
+ import { AppState, FlowIssue, fromStore, zustand } from '../store/AppState';
26
27
  import { getStore } from '../store/Store';
27
28
 
28
29
  export class NodeEditor extends RapidElement {
@@ -55,6 +56,19 @@ export class NodeEditor extends RapidElement {
55
56
  margin-top: 15px;
56
57
  }
57
58
 
59
+ .issue-warning {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 8px;
63
+ color: var(--color-error, tomato);
64
+ font-size: 13px;
65
+ cursor: pointer;
66
+ }
67
+
68
+ .issue-warning:hover .issue-text {
69
+ text-decoration: underline;
70
+ }
71
+
58
72
  .form-actions {
59
73
  display: flex;
60
74
  gap: 10px;
@@ -112,12 +126,29 @@ export class NodeEditor extends RapidElement {
112
126
  color: var(--color-label, #777);
113
127
  }
114
128
 
129
+ .form-row-inline-label {
130
+ font-size: 20px;
131
+ font-weight: 300;
132
+ color: #ccc;
133
+ min-width: 20px;
134
+ text-align: center;
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: center;
138
+ user-select: none;
139
+ }
140
+
115
141
  .form-row-help {
116
142
  font-size: 12px;
117
143
  color: #666;
118
144
  margin-top: 6px;
119
145
  }
120
146
 
147
+ .form-text {
148
+ color: #666;
149
+ font-size: 13px;
150
+ }
151
+
121
152
  .form-group {
122
153
  border: 1px solid #e0e0e0;
123
154
  border-radius: 6px;
@@ -381,6 +412,9 @@ export class NodeEditor extends RapidElement {
381
412
  @property({ type: Object })
382
413
  nodeUI?: NodeUI;
383
414
 
415
+ @property({ attribute: false })
416
+ dialogOrigin?: { x: number; y: number };
417
+
384
418
  @property({ type: Boolean })
385
419
  isOpen: boolean = false;
386
420
 
@@ -411,6 +445,12 @@ export class NodeEditor extends RapidElement {
411
445
  @fromStore(zustand, (state: AppState) => state.flowDefinition)
412
446
  private flowDefinition!: FlowDefinition;
413
447
 
448
+ @fromStore(zustand, (state: AppState) => state.issuesByNode)
449
+ private issuesByNode!: Map<string, FlowIssue[]>;
450
+
451
+ @fromStore(zustand, (state: AppState) => state.issuesByAction)
452
+ private issuesByAction!: Map<string, FlowIssue[]>;
453
+
414
454
  connectedCallback(): void {
415
455
  super.connectedCallback();
416
456
  this.initializeFormData();
@@ -447,7 +487,13 @@ export class NodeEditor extends RapidElement {
447
487
  private initializeFormData(): void {
448
488
  const nodeConfig = this.getNodeConfig();
449
489
 
450
- if ((!nodeConfig || nodeConfig.type === 'execute_actions') && this.action) {
490
+ // Temporary: terminal nodes defer to action configs, same as execute_actions
491
+ if (
492
+ (!nodeConfig ||
493
+ nodeConfig.type === 'execute_actions' ||
494
+ nodeConfig.type === 'terminal') &&
495
+ this.action
496
+ ) {
451
497
  // Action editing mode - use action config
452
498
  const actionConfig = ACTION_CONFIG[this.action.type];
453
499
 
@@ -597,8 +643,13 @@ export class NodeEditor extends RapidElement {
597
643
  if (this.node && this.nodeUI) {
598
644
  const nodeConfig = this.getNodeConfig();
599
645
 
600
- // For execute_actions nodes, defer to action editing if an action is selected
601
- if (this.nodeUI.type === 'execute_actions' && this.action) {
646
+ // Temporary: terminal nodes defer to action configs for editing, same as execute_actions
647
+ // For execute_actions/terminal nodes, defer to action editing if an action is selected
648
+ if (
649
+ (this.nodeUI.type === 'execute_actions' ||
650
+ this.nodeUI.type === ('terminal' as any)) &&
651
+ this.action
652
+ ) {
602
653
  return ACTION_CONFIG[this.action.type] || null;
603
654
  }
604
655
 
@@ -1462,6 +1513,11 @@ export class NodeEditor extends RapidElement {
1462
1513
  } else if (fieldName && config.type === 'message-editor') {
1463
1514
  // Special handling for message editor
1464
1515
  this.handleMessageEditorChange(fieldName, e);
1516
+ } else if (fieldName && config.type === 'media') {
1517
+ // Extract URL from media picker's attachment
1518
+ const picker = e.target as any;
1519
+ const url = picker.attachments?.[0]?.url || '';
1520
+ this.handleNewFieldChange(fieldName, url);
1465
1521
  } else {
1466
1522
  // Default handling for most field types
1467
1523
  this.handleFormFieldChange(fieldName, e);
@@ -1575,6 +1631,12 @@ export class NodeEditor extends RapidElement {
1575
1631
  case 'group':
1576
1632
  return this.renderGroup(item, config, renderedFields);
1577
1633
 
1634
+ case 'spacer':
1635
+ return html``;
1636
+
1637
+ case 'text':
1638
+ return html`<div class="form-text">${item.text}</div>`;
1639
+
1578
1640
  default:
1579
1641
  return html``;
1580
1642
  }
@@ -1585,7 +1647,14 @@ export class NodeEditor extends RapidElement {
1585
1647
  config: ActionConfig | NodeConfig,
1586
1648
  renderedFields: Set<string>
1587
1649
  ): TemplateResult {
1588
- const { items, gap = '1rem', label, helpText } = rowConfig;
1650
+ const {
1651
+ items,
1652
+ gap = '1rem',
1653
+ label,
1654
+ helpText,
1655
+ inlineLabels,
1656
+ marginBottom
1657
+ } = rowConfig;
1589
1658
 
1590
1659
  // Collect all fields from this row for width calculations
1591
1660
  const fieldsInRow = this.collectFieldsFromItems(items);
@@ -1619,8 +1688,18 @@ export class NodeEditor extends RapidElement {
1619
1688
  });
1620
1689
 
1621
1690
  const rowContent = html`
1622
- <div class="form-row" style="display: flex; gap: ${gap};">
1691
+ <div
1692
+ class="form-row"
1693
+ style="display: flex; gap: ${gap};${marginBottom
1694
+ ? ` margin-bottom: ${marginBottom};`
1695
+ : ''}"
1696
+ >
1623
1697
  ${items.map((item) => {
1698
+ // Spacer items render as empty flex children
1699
+ if (typeof item !== 'string' && item.type === 'spacer') {
1700
+ return html`<div style="flex: 1 1 0;"></div>`;
1701
+ }
1702
+
1624
1703
  // Get the field name from the item
1625
1704
  const fieldName =
1626
1705
  typeof item === 'string'
@@ -1641,10 +1720,22 @@ export class NodeEditor extends RapidElement {
1641
1720
  renderedFields
1642
1721
  );
1643
1722
 
1723
+ // When inlineLabels is provided, render the label inline to the left
1724
+ const inlineLabel =
1725
+ inlineLabels && fieldName ? inlineLabels[fieldName] : null;
1726
+
1644
1727
  // Wrap in a div with flex style if we have a flex style
1645
- return flexStyle
1646
- ? html`<div style="${flexStyle}">${itemContent}</div>`
1647
- : itemContent;
1728
+ if (flexStyle) {
1729
+ return inlineLabel
1730
+ ? html`<div
1731
+ style="${flexStyle} display: flex; align-items: center; gap: 0.35rem;"
1732
+ >
1733
+ <span class="form-row-inline-label">${inlineLabel}</span>
1734
+ <div style="flex: 1 1 0; min-width: 0;">${itemContent}</div>
1735
+ </div>`
1736
+ : html`<div style="${flexStyle}">${itemContent}</div>`;
1737
+ }
1738
+ return itemContent;
1648
1739
  })}
1649
1740
  </div>
1650
1741
  `;
@@ -2038,6 +2129,40 @@ export class NodeEditor extends RapidElement {
2038
2129
  `;
2039
2130
  }
2040
2131
 
2132
+ private handleIssueClick(issue: FlowIssue): void {
2133
+ this.fireCustomEvent(CustomEventType.ShowIssue, { issue });
2134
+ }
2135
+
2136
+ private renderIssueWarnings(): TemplateResult | string {
2137
+ const issues: FlowIssue[] = [];
2138
+
2139
+ // Check for action-level issues
2140
+ if (this.action && this.issuesByAction?.has(this.action.uuid)) {
2141
+ issues.push(...this.issuesByAction.get(this.action.uuid));
2142
+ }
2143
+
2144
+ // Check for node-level issues (issues without action_uuid)
2145
+ if (this.node && this.issuesByNode?.has(this.node.uuid)) {
2146
+ issues.push(...this.issuesByNode.get(this.node.uuid));
2147
+ }
2148
+
2149
+ if (issues.length === 0) return '';
2150
+
2151
+ return html`
2152
+ ${issues.map(
2153
+ (issue) => html`
2154
+ <div
2155
+ class="issue-warning"
2156
+ @click=${() => this.handleIssueClick(issue)}
2157
+ >
2158
+ <temba-icon name="alert_warning" size="1.2"></temba-icon>
2159
+ <span class="issue-text">${formatIssueMessage(issue)}</span>
2160
+ </div>
2161
+ `
2162
+ )}
2163
+ `;
2164
+ }
2165
+
2041
2166
  render(): TemplateResult {
2042
2167
  if (!this.isOpen) {
2043
2168
  return html``;
@@ -2061,6 +2186,8 @@ export class NodeEditor extends RapidElement {
2061
2186
  <temba-dialog
2062
2187
  header="${header}"
2063
2188
  .open="${this.isOpen}"
2189
+ .originX=${this.dialogOrigin?.x ?? null}
2190
+ .originY=${this.dialogOrigin?.y ?? null}
2064
2191
  @temba-button-clicked=${this.handleDialogButtonClick}
2065
2192
  primaryButtonName="Save"
2066
2193
  cancelButtonName="Cancel"
@@ -2072,6 +2199,7 @@ export class NodeEditor extends RapidElement {
2072
2199
  ${this.getNodeConfig()?.router?.configurable
2073
2200
  ? this.renderRouterSection()
2074
2201
  : null}
2202
+ ${this.renderIssueWarnings()}
2075
2203
  </div>
2076
2204
 
2077
2205
  <div slot="gutter">${this.renderGutter()}</div>