@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
@@ -9,7 +9,7 @@ import { getClasses } from '../utils';
9
9
  import { Plumber } from './Plumber';
10
10
  import { getStore } from '../store/Store';
11
11
  import { CustomEventType } from '../interfaces';
12
- import { AppState, fromStore, zustand } from '../store/AppState';
12
+ import { AppState, FlowIssue, fromStore, zustand } from '../store/AppState';
13
13
 
14
14
  const DRAG_THRESHOLD = 5;
15
15
 
@@ -49,6 +49,12 @@ export class CanvasNode extends RapidElement {
49
49
  @fromStore(zustand, (state: AppState) => state.getCurrentActivity())
50
50
  private activity!: any;
51
51
 
52
+ @fromStore(zustand, (state: AppState) => state.issuesByNode)
53
+ private issuesByNode!: Map<string, FlowIssue[]>;
54
+
55
+ @fromStore(zustand, (state: AppState) => state.issuesByAction)
56
+ private issuesByAction!: Map<string, FlowIssue[]>;
57
+
52
58
  // Track exits that are in "removing" state
53
59
  private exitRemovalTimeouts: Map<string, number> = new Map();
54
60
 
@@ -185,6 +191,12 @@ export class CanvasNode extends RapidElement {
185
191
  pointer-events: none !important;
186
192
  }
187
193
 
194
+ /* Issue indicators - hatched red title bar */
195
+ .action-content.has-issues .cn-title,
196
+ .node.has-issues > .router .cn-title {
197
+ background: repeating-linear-gradient(120deg, tomato, tomato 6px, #ff7056 0, #ff7056 18px) !important;
198
+ }
199
+
188
200
  .action.sortable {
189
201
  display: flex;
190
202
  align-items: stretch;
@@ -360,11 +372,7 @@ export class CanvasNode extends RapidElement {
360
372
  position: relative;
361
373
  box-shadow: 0 2px 2px rgba(0, 0, 0, .1);
362
374
  cursor: pointer;
363
- pointer-events: none;
364
- }
365
-
366
- .exit.jtk-connected {
367
- background: var(--color-connectors, #e6e6e6);
375
+ pointer-events: all;
368
376
  }
369
377
 
370
378
  .exit.connected {
@@ -376,11 +384,14 @@ export class CanvasNode extends RapidElement {
376
384
  background-color: var(--color-connectors, #e6e6e6);
377
385
  }
378
386
 
379
- .exit.connected.read-only, .exit.connected.read-only:hover {
380
- background-color: #fff;
387
+ .exit.read-only, .exit.read-only:hover {
381
388
  pointer-events: none !important;
382
389
  cursor: default;
383
390
  }
391
+
392
+ .exit.connected.read-only, .exit.connected.read-only:hover {
393
+ background-color: #fff;
394
+ }
384
395
 
385
396
  .exit.removing, .exit.removing:hover {
386
397
  background-color: var(--color-error);
@@ -549,14 +560,17 @@ export class CanvasNode extends RapidElement {
549
560
  // make our initial connections
550
561
  // We use setTimeout to allow for DOM updates to complete before querying for exits
551
562
  this.connectionTimeout = window.setTimeout(() => {
552
- for (const exit of this.node.exits) {
553
- this.plumber.makeSource(exit.uuid);
554
- if (exit.destination_uuid) {
555
- this.plumber.connectIds(
556
- this.node.uuid,
557
- exit.uuid,
558
- exit.destination_uuid
559
- );
563
+ // Terminal nodes have no visible exits
564
+ if (this.ui?.type !== 'terminal') {
565
+ for (const exit of this.node.exits) {
566
+ this.plumber.makeSource(exit.uuid);
567
+ if (exit.destination_uuid) {
568
+ this.plumber.connectIds(
569
+ this.node.uuid,
570
+ exit.uuid,
571
+ exit.destination_uuid
572
+ );
573
+ }
560
574
  }
561
575
  }
562
576
  // Note: revalidation is handled by plumber's processPendingConnections which calls repaintEverything
@@ -950,6 +964,11 @@ export class CanvasNode extends RapidElement {
950
964
  this.requestUpdate();
951
965
  }
952
966
 
967
+ private getTopCenter(el: Element): { x: number; y: number } {
968
+ const rect = el.getBoundingClientRect();
969
+ return { x: rect.left + rect.width / 2, y: rect.top };
970
+ }
971
+
953
972
  private handleActionMouseDown(event: MouseEvent, action: Action): void {
954
973
  // Don't handle clicks on the remove button, drag handle, or when action is in removing state
955
974
  const target = event.target as HTMLElement;
@@ -1003,10 +1022,18 @@ export class CanvasNode extends RapidElement {
1003
1022
  // Only fire the action edit event if we haven't dragged beyond the threshold
1004
1023
  // AND either there's no Editor parent (test case) or the Editor didn't drag the node
1005
1024
  if (distance <= DRAG_THRESHOLD && (!editor || !editorWasDragging)) {
1025
+ // Use top-center of the action element as the dialog origin
1026
+ const actionEl = event.currentTarget as Element;
1027
+ const origin = actionEl
1028
+ ? this.getTopCenter(actionEl)
1029
+ : { x: event.clientX, y: event.clientY };
1030
+
1006
1031
  // Fire event to request action editing
1007
1032
  this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1008
1033
  action,
1009
- nodeUuid: this.node.uuid
1034
+ nodeUuid: this.node.uuid,
1035
+ originX: origin.x,
1036
+ originY: origin.y
1010
1037
  });
1011
1038
  }
1012
1039
  }
@@ -1126,17 +1153,24 @@ export class CanvasNode extends RapidElement {
1126
1153
  // Using literal 5 instead of DRAG_THRESHOLD since it's not imported
1127
1154
  // Fire event to request node editing if the node has a router
1128
1155
  if (this.node.router) {
1156
+ // Use top-center of the node as the dialog origin
1157
+ const origin = this.getTopCenter(this);
1158
+
1129
1159
  // If router node has exactly one action, open the action editor directly
1130
1160
  if (this.node.actions && this.node.actions.length === 1) {
1131
1161
  this.fireCustomEvent(CustomEventType.ActionEditRequested, {
1132
1162
  action: this.node.actions[0],
1133
- nodeUuid: this.node.uuid
1163
+ nodeUuid: this.node.uuid,
1164
+ originX: origin.x,
1165
+ originY: origin.y
1134
1166
  });
1135
1167
  } else {
1136
1168
  // Otherwise open the node editor as before
1137
1169
  this.fireCustomEvent(CustomEventType.NodeEditRequested, {
1138
1170
  node: this.node,
1139
- nodeUI: this.ui
1171
+ nodeUI: this.ui,
1172
+ originX: origin.x,
1173
+ originY: origin.y
1140
1174
  });
1141
1175
  }
1142
1176
  }
@@ -1311,13 +1345,11 @@ export class CanvasNode extends RapidElement {
1311
1345
  const color = config.group
1312
1346
  ? ACTION_GROUP_METADATA[config.group]?.color
1313
1347
  : '#aaaaaa';
1348
+ const isTerminal = this.ui?.type === 'terminal';
1314
1349
  return html`<div class="cn-title" style="background:${color}">
1315
- ${this.ui?.type === 'execute_actions'
1316
- ? html`<temba-icon
1317
- class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1318
- name="sort"
1319
- ></temba-icon>`
1320
- : this.node?.actions?.length > 1
1350
+ ${isTerminal
1351
+ ? html`<div class="title-spacer"></div>`
1352
+ : this.ui?.type === 'execute_actions' || this.node?.actions?.length > 1
1321
1353
  ? html`<temba-icon
1322
1354
  class="drag-handle ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1323
1355
  name="sort"
@@ -1326,7 +1358,9 @@ export class CanvasNode extends RapidElement {
1326
1358
 
1327
1359
  <div class="name">${isRemoving ? 'Remove?' : config.name}</div>
1328
1360
  <div
1329
- class="remove-button ${this.isReadOnly() ? 'read-only-hidden' : ''}"
1361
+ class="remove-button ${isTerminal || this.isReadOnly()
1362
+ ? 'read-only-hidden'
1363
+ : ''}"
1330
1364
  @click=${(e: MouseEvent) =>
1331
1365
  this.handleActionRemoveClick(e, action, index)}
1332
1366
  title="Remove action"
@@ -1439,6 +1473,7 @@ export class CanvasNode extends RapidElement {
1439
1473
  const displayAction = this.getLocalizedAction(action);
1440
1474
 
1441
1475
  if (config) {
1476
+ const hasIssues = this.issuesByAction?.has(action.uuid);
1442
1477
  const classes = [
1443
1478
  'action',
1444
1479
  'sortable',
@@ -1453,7 +1488,7 @@ export class CanvasNode extends RapidElement {
1453
1488
 
1454
1489
  return html`<div class="${classes}" id="action-${index}">
1455
1490
  <div
1456
- class="action-content"
1491
+ class="action-content ${hasIssues ? 'has-issues' : ''}"
1457
1492
  @mousedown=${(e: MouseEvent) =>
1458
1493
  !isDisabled && this.handleActionMouseDown(e, action)}
1459
1494
  @mouseup=${(e: MouseEvent) =>
@@ -1629,13 +1664,19 @@ export class CanvasNode extends RapidElement {
1629
1664
  const activeCount =
1630
1665
  (this.activity?.nodes && this.activity.nodes[this.node.uuid]) || 0;
1631
1666
 
1667
+ // Check for node-level issues or action-level issues on any action in this node
1668
+ const nodeHasIssues =
1669
+ this.issuesByNode?.has(this.node.uuid) ||
1670
+ this.node.actions?.some((a) => this.issuesByAction?.has(a.uuid));
1671
+
1632
1672
  return html`
1633
1673
  <div
1634
1674
  id="${this.node.uuid}"
1635
1675
  class=${getClasses({
1636
1676
  node: true,
1637
1677
  'execute-actions': this.ui.type === 'execute_actions',
1638
- 'non-localizable': isNodeDisabled
1678
+ 'non-localizable': isNodeDisabled,
1679
+ 'has-issues': nodeHasIssues
1639
1680
  })}
1640
1681
  style="left:${this.ui.position.left}px;top:${this.ui.position.top}px"
1641
1682
  >
@@ -1644,7 +1685,9 @@ export class CanvasNode extends RapidElement {
1644
1685
  ${activeCount.toLocaleString()}
1645
1686
  </div>`
1646
1687
  : ''}
1647
- ${nodeConfig && nodeConfig.type !== 'execute_actions'
1688
+ ${nodeConfig &&
1689
+ nodeConfig.type !== 'execute_actions' &&
1690
+ nodeConfig.type !== 'terminal'
1648
1691
  ? html`<div class="router" style="position: relative;">
1649
1692
  <div
1650
1693
  @mousedown=${(e: MouseEvent) => this.handleNodeMouseDown(e)}
@@ -1662,7 +1705,7 @@ export class CanvasNode extends RapidElement {
1662
1705
  : null}
1663
1706
  </div>
1664
1707
  </div>`
1665
- : this.node.actions.length > 0
1708
+ : this.node.actions?.length > 0
1666
1709
  ? this.ui.type === 'execute_actions'
1667
1710
  ? html`<temba-sortable-list
1668
1711
  dragHandle="drag-handle"
@@ -1692,6 +1735,8 @@ export class CanvasNode extends RapidElement {
1692
1735
  ${this.renderRouter(this.node.router, this.ui)}
1693
1736
  ${this.renderCategories(this.node)}
1694
1737
  </div>`
1738
+ : this.ui.type === 'terminal'
1739
+ ? ''
1695
1740
  : html`<div class="action-exits">
1696
1741
  ${repeat(
1697
1742
  this.node.exits,