@repzo/widget-react-native 1.0.1 → 1.0.4

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 (97) hide show
  1. package/lib/commonjs/api/client.js +3 -1
  2. package/lib/commonjs/api/client.js.map +1 -1
  3. package/lib/commonjs/api/realtime.js +2 -0
  4. package/lib/commonjs/api/realtime.js.map +1 -1
  5. package/lib/commonjs/i18n.js +26 -0
  6. package/lib/commonjs/i18n.js.map +1 -1
  7. package/lib/commonjs/locales.js +5 -0
  8. package/lib/commonjs/locales.js.map +1 -1
  9. package/lib/commonjs/state/widget-store.js +111 -7
  10. package/lib/commonjs/state/widget-store.js.map +1 -1
  11. package/lib/commonjs/types.js +2 -0
  12. package/lib/commonjs/types.js.map +1 -1
  13. package/lib/commonjs/ui/WidgetProvider.js +17 -2
  14. package/lib/commonjs/ui/WidgetProvider.js.map +1 -1
  15. package/lib/commonjs/ui/components/TeamAvatars.js +63 -0
  16. package/lib/commonjs/ui/components/TeamAvatars.js.map +1 -0
  17. package/lib/commonjs/ui/components/icons.js +50 -0
  18. package/lib/commonjs/ui/components/icons.js.map +1 -1
  19. package/lib/commonjs/ui/screens/chat/ChatView.js +105 -6
  20. package/lib/commonjs/ui/screens/chat/ChatView.js.map +1 -1
  21. package/lib/commonjs/ui/screens/chat/ChatView.styles.js +51 -0
  22. package/lib/commonjs/ui/screens/chat/ChatView.styles.js.map +1 -1
  23. package/lib/module/api/client.js +3 -1
  24. package/lib/module/api/client.js.map +1 -1
  25. package/lib/module/api/realtime.js +2 -0
  26. package/lib/module/api/realtime.js.map +1 -1
  27. package/lib/module/i18n.js +25 -0
  28. package/lib/module/i18n.js.map +1 -1
  29. package/lib/module/locales.js +5 -0
  30. package/lib/module/locales.js.map +1 -1
  31. package/lib/module/state/widget-store.js +111 -7
  32. package/lib/module/state/widget-store.js.map +1 -1
  33. package/lib/module/types.js +2 -0
  34. package/lib/module/types.js.map +1 -1
  35. package/lib/module/ui/WidgetProvider.js +18 -3
  36. package/lib/module/ui/WidgetProvider.js.map +1 -1
  37. package/lib/module/ui/components/TeamAvatars.js +59 -0
  38. package/lib/module/ui/components/TeamAvatars.js.map +1 -0
  39. package/lib/module/ui/components/icons.js +49 -0
  40. package/lib/module/ui/components/icons.js.map +1 -1
  41. package/lib/module/ui/screens/chat/ChatView.js +106 -7
  42. package/lib/module/ui/screens/chat/ChatView.js.map +1 -1
  43. package/lib/module/ui/screens/chat/ChatView.styles.js +51 -0
  44. package/lib/module/ui/screens/chat/ChatView.styles.js.map +1 -1
  45. package/lib/typescript/commonjs/api/client.d.ts.map +1 -1
  46. package/lib/typescript/commonjs/api/realtime.d.ts.map +1 -1
  47. package/lib/typescript/commonjs/i18n.d.ts +7 -0
  48. package/lib/typescript/commonjs/i18n.d.ts.map +1 -1
  49. package/lib/typescript/commonjs/locales.d.ts.map +1 -1
  50. package/lib/typescript/commonjs/state/widget-store.d.ts +17 -0
  51. package/lib/typescript/commonjs/state/widget-store.d.ts.map +1 -1
  52. package/lib/typescript/commonjs/types.d.ts +14 -1
  53. package/lib/typescript/commonjs/types.d.ts.map +1 -1
  54. package/lib/typescript/commonjs/ui/WidgetProvider.d.ts.map +1 -1
  55. package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts +17 -0
  56. package/lib/typescript/commonjs/ui/components/TeamAvatars.d.ts.map +1 -0
  57. package/lib/typescript/commonjs/ui/components/icons.d.ts +2 -0
  58. package/lib/typescript/commonjs/ui/components/icons.d.ts.map +1 -1
  59. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts +1 -1
  60. package/lib/typescript/commonjs/ui/screens/chat/ChatView.d.ts.map +1 -1
  61. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts +51 -0
  62. package/lib/typescript/commonjs/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  63. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts +13 -0
  64. package/lib/typescript/commonjs/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  65. package/lib/typescript/module/api/client.d.ts.map +1 -1
  66. package/lib/typescript/module/api/realtime.d.ts.map +1 -1
  67. package/lib/typescript/module/i18n.d.ts +7 -0
  68. package/lib/typescript/module/i18n.d.ts.map +1 -1
  69. package/lib/typescript/module/locales.d.ts.map +1 -1
  70. package/lib/typescript/module/state/widget-store.d.ts +17 -0
  71. package/lib/typescript/module/state/widget-store.d.ts.map +1 -1
  72. package/lib/typescript/module/types.d.ts +14 -1
  73. package/lib/typescript/module/types.d.ts.map +1 -1
  74. package/lib/typescript/module/ui/WidgetProvider.d.ts.map +1 -1
  75. package/lib/typescript/module/ui/components/TeamAvatars.d.ts +17 -0
  76. package/lib/typescript/module/ui/components/TeamAvatars.d.ts.map +1 -0
  77. package/lib/typescript/module/ui/components/icons.d.ts +2 -0
  78. package/lib/typescript/module/ui/components/icons.d.ts.map +1 -1
  79. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts +1 -1
  80. package/lib/typescript/module/ui/screens/chat/ChatView.d.ts.map +1 -1
  81. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts +51 -0
  82. package/lib/typescript/module/ui/screens/chat/ChatView.styles.d.ts.map +1 -1
  83. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts +13 -0
  84. package/lib/typescript/module/ui/screens/chat/ChatView.types.d.ts.map +1 -1
  85. package/package.json +1 -1
  86. package/src/api/client.ts +3 -1
  87. package/src/api/realtime.ts +2 -0
  88. package/src/i18n.ts +28 -0
  89. package/src/locales.ts +5 -0
  90. package/src/state/widget-store.ts +106 -5
  91. package/src/types.ts +16 -0
  92. package/src/ui/WidgetProvider.tsx +25 -3
  93. package/src/ui/components/TeamAvatars.tsx +58 -0
  94. package/src/ui/components/icons.tsx +37 -0
  95. package/src/ui/screens/chat/ChatView.styles.ts +51 -0
  96. package/src/ui/screens/chat/ChatView.tsx +111 -20
  97. package/src/ui/screens/chat/ChatView.types.ts +13 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Up to three overlapping teammate avatars — the Messenger header when no
3
+ * teammate has joined yet, and the "Waiting for a teammate" row (Intercom-style).
4
+ * Falls back to a single placeholder avatar when the workspace sent none.
5
+ */
6
+
7
+ import { StyleSheet, View } from 'react-native';
8
+
9
+ import type { TeamAvatar } from '../../types';
10
+ import { palette } from '../styles/theme';
11
+ import { Avatar } from './Avatar';
12
+
13
+ interface TeamAvatarsProps {
14
+ avatars: TeamAvatar[];
15
+ size?: number;
16
+ color?: string;
17
+ /** Ring between overlapping avatars; matches the surface behind them. */
18
+ ringColor?: string;
19
+ translucent?: boolean;
20
+ }
21
+
22
+ export function TeamAvatars({
23
+ avatars,
24
+ size = 32,
25
+ color = palette.ink,
26
+ ringColor = palette.white,
27
+ translucent = false,
28
+ }: TeamAvatarsProps) {
29
+ if (avatars.length === 0) {
30
+ return <Avatar size={size} color={color} translucent={translucent} />;
31
+ }
32
+ const ring = Math.max(1.5, size * 0.06);
33
+ return (
34
+ <View style={styles.row} accessibilityElementsHidden importantForAccessibility="no-hide-descendants">
35
+ {avatars.slice(0, 3).map((avatar, index) => (
36
+ <View
37
+ key={`${avatar.initials}-${index}`}
38
+ style={[
39
+ styles.slot,
40
+ {
41
+ marginStart: index === 0 ? 0 : -size * 0.3,
42
+ borderRadius: (size + ring * 2) / 2,
43
+ borderWidth: ring,
44
+ borderColor: ringColor,
45
+ zIndex: 3 - index,
46
+ },
47
+ ]}>
48
+ <Avatar avatar={avatar.avatarUrl} name={avatar.initials} size={size} color={color} translucent={translucent} />
49
+ </View>
50
+ ))}
51
+ </View>
52
+ );
53
+ }
54
+
55
+ const styles = StyleSheet.create({
56
+ row: { flexDirection: 'row', alignItems: 'center' },
57
+ slot: { overflow: 'hidden' },
58
+ });
@@ -313,3 +313,40 @@ const styles = StyleSheet.create({
313
313
  justifyContent: 'center',
314
314
  },
315
315
  });
316
+
317
+ /** A ring with two hands — the reply-time hint beside the team name. */
318
+ export function ClockIcon({ size = 14, color = palette.textMuted, style }: IconProps) {
319
+ const thickness = Math.max(1.25, size * 0.1);
320
+ const hand: ViewStyle = {
321
+ position: 'absolute',
322
+ width: thickness,
323
+ borderRadius: thickness,
324
+ backgroundColor: color,
325
+ bottom: size / 2 - thickness / 2,
326
+ };
327
+ return (
328
+ <View style={[{ width: size, height: size }, styles.center, style]}>
329
+ <View
330
+ style={{
331
+ position: 'absolute',
332
+ width: size,
333
+ height: size,
334
+ borderRadius: size / 2,
335
+ borderWidth: thickness,
336
+ borderColor: color,
337
+ }}
338
+ />
339
+ <View style={[hand, { height: size * 0.32, left: size / 2 - thickness / 2 }]} />
340
+ <View
341
+ style={[
342
+ hand,
343
+ {
344
+ height: size * 0.24,
345
+ left: size / 2 - thickness / 2,
346
+ transform: [{ translateX: size * 0.1 }, { translateY: size * 0.04 }, { rotate: '-90deg' }],
347
+ },
348
+ ]}
349
+ />
350
+ </View>
351
+ );
352
+ }
@@ -50,6 +50,36 @@ export const styles = StyleSheet.create({
50
50
  fontSize: fontSize.caption,
51
51
  marginTop: 1,
52
52
  },
53
+ headerReplyTime: {
54
+ flexDirection: 'row',
55
+ alignItems: 'center',
56
+ gap: 4,
57
+ marginTop: 1,
58
+ },
59
+ headerReplyTimeText: {
60
+ flexShrink: 1,
61
+ marginTop: 0,
62
+ },
63
+ waitingRow: {
64
+ flexDirection: 'row',
65
+ alignItems: 'center',
66
+ justifyContent: 'center',
67
+ gap: spacing.sm,
68
+ paddingVertical: spacing.sm,
69
+ },
70
+ waitingText: {
71
+ color: palette.textMuted,
72
+ fontSize: fontSize.caption,
73
+ },
74
+ joinedRow: {
75
+ flexDirection: 'row',
76
+ justifyContent: 'center',
77
+ gap: spacing.xs,
78
+ },
79
+ joinedName: {
80
+ fontWeight: '700',
81
+ color: palette.ink,
82
+ },
53
83
  messagesContentEmpty: {
54
84
  flexGrow: 1,
55
85
  justifyContent: 'center',
@@ -108,6 +138,13 @@ export const styles = StyleSheet.create({
108
138
  marginBottom: 3,
109
139
  marginStart: spacing.xs,
110
140
  },
141
+ receiptText: {
142
+ alignSelf: 'flex-end',
143
+ marginTop: 3,
144
+ marginEnd: spacing.xs,
145
+ fontSize: 11,
146
+ color: palette.textSubtle,
147
+ },
111
148
  messageTime: {
112
149
  fontSize: 11,
113
150
  alignSelf: 'flex-end',
@@ -190,6 +227,20 @@ export const styles = StyleSheet.create({
190
227
  fontSize: fontSize.caption,
191
228
  marginTop: spacing.xs,
192
229
  },
230
+ deletedBubble: {
231
+ maxWidth: '80%',
232
+ paddingHorizontal: spacing.md,
233
+ paddingVertical: spacing.sm,
234
+ borderRadius: radius.lg + 4,
235
+ borderWidth: 1,
236
+ borderStyle: 'dashed',
237
+ borderColor: palette.border,
238
+ },
239
+ deletedText: {
240
+ color: palette.textMuted,
241
+ fontSize: fontSize.caption,
242
+ fontStyle: 'italic',
243
+ },
193
244
  systemRow: {
194
245
  alignItems: 'center',
195
246
  paddingVertical: spacing.sm,
@@ -47,7 +47,8 @@ import { Avatar } from '../../components/Avatar';
47
47
  import { ImageViewer } from '../../components/ImageViewer';
48
48
  import { WidgetErrorBoundary } from '../../components/ErrorBoundary';
49
49
  import { GradientBackground } from '../../components/Gradient';
50
- import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon, PaperclipIcon } from '../../components/icons';
50
+ import { ArrowUpIcon, ChatOutlineIcon, ChevronIcon, ClockIcon, PaperclipIcon } from '../../components/icons';
51
+ import { TeamAvatars } from '../../components/TeamAvatars';
51
52
  import { Markdown } from '../../components/Markdown';
52
53
  import { useReduceMotion } from '../../hooks/use-reduce-motion';
53
54
  import { globalStyles } from '../../styles/global';
@@ -67,7 +68,11 @@ export function ChatView({
67
68
  color,
68
69
  agentName,
69
70
  agentAvatar,
71
+ agentOnline,
70
72
  isOnline,
73
+ teamName,
74
+ teamAvatars = [],
75
+ replyTimeLabel,
71
76
  locale,
72
77
  prefillText,
73
78
  offlineMessage,
@@ -309,6 +314,17 @@ export function ChatView({
309
314
  const headerTextColor = contrastingTextColor(color);
310
315
  const onDarkHeader = headerTextColor === palette.white;
311
316
  const isEmptyThread = !loading && messages.length === 0;
317
+ const agentIsActive = agentOnline ?? isOnline;
318
+ // "Waiting for a teammate" until someone replies: no teammate in the header
319
+ // and the visitor spoke last (bot replies before that still count as waiting).
320
+ const lastChatMessage = [...messages]
321
+ .reverse()
322
+ .find((message) => (message.messageType ?? 'message') === 'message' && !message.deletedAt);
323
+ const awaitingTeammate = !agentName && !agentTyping && lastChatMessage?.direction === 'inbound';
324
+ // "Sent" / "Seen" goes under the visitor's latest message only.
325
+ const lastVisitorMessageId = [...messages]
326
+ .reverse()
327
+ .find((message) => message.direction === 'inbound' && (message.messageType ?? 'message') === 'message')?.id;
312
328
  // Two stops off the brand color: flat fill looked like an unstyled block.
313
329
  // A non-hex brand color falls through mixHex unchanged and renders solid.
314
330
  const headerGradient = [color, mixHex(color, '#000000', 0.16)];
@@ -334,28 +350,57 @@ export function ChatView({
334
350
  <ChevronIcon size={18} direction={backDirection()} color={headerTextColor} />
335
351
  </Pressable>
336
352
 
337
- <View style={styles.headerIdentity}>
338
- <View style={styles.avatarWrapper}>
339
- <Avatar
340
- avatar={agentAvatar}
341
- name={agentName}
342
- size={36}
343
- translucent={onDarkHeader}
353
+ {agentName ? (
354
+ // A teammate joined: their photo, name and own presence.
355
+ <View style={styles.headerIdentity}>
356
+ <View style={styles.avatarWrapper}>
357
+ <Avatar
358
+ avatar={agentAvatar}
359
+ name={agentName}
360
+ size={36}
361
+ translucent={onDarkHeader}
362
+ color={palette.ink}
363
+ />
364
+ {agentIsActive ? <View style={styles.onlineDot} /> : null}
365
+ </View>
366
+ <View style={styles.headerText}>
367
+ <Text style={[styles.headerName, { color: headerTextColor }]} numberOfLines={1}>
368
+ {agentName}
369
+ </Text>
370
+ <Text
371
+ style={[styles.headerStatus, { color: withAlpha(headerTextColor, 0.75) }]}
372
+ numberOfLines={1}>
373
+ {agentIsActive ? labels.statusOnline : labels.statusAway}
374
+ </Text>
375
+ </View>
376
+ </View>
377
+ ) : (
378
+ // No teammate yet, or the conversation closed: the team, Intercom-style.
379
+ <View style={styles.headerIdentity}>
380
+ <TeamAvatars
381
+ avatars={teamAvatars}
382
+ size={30}
344
383
  color={palette.ink}
384
+ ringColor={color}
385
+ translucent={onDarkHeader}
345
386
  />
346
- {isOnline ? <View style={styles.onlineDot} /> : null}
347
- </View>
348
- <View style={styles.headerText}>
349
- <Text style={[styles.headerName, { color: headerTextColor }]} numberOfLines={1}>
350
- {agentName || labels.team}
351
- </Text>
352
- <Text
353
- style={[styles.headerStatus, { color: withAlpha(headerTextColor, 0.75) }]}
354
- numberOfLines={1}>
355
- {isOnline ? labels.statusOnline : labels.statusAway}
356
- </Text>
387
+ <View style={styles.headerText}>
388
+ <Text style={[styles.headerName, { color: headerTextColor }]} numberOfLines={1}>
389
+ {teamName || labels.team}
390
+ </Text>
391
+ {replyTimeLabel ? (
392
+ <View style={styles.headerReplyTime}>
393
+ <ClockIcon size={12} color={withAlpha(headerTextColor, 0.75)} />
394
+ <Text
395
+ style={[styles.headerStatus, styles.headerReplyTimeText, { color: withAlpha(headerTextColor, 0.75) }]}
396
+ numberOfLines={1}>
397
+ {replyTimeLabel}
398
+ </Text>
399
+ </View>
400
+ ) : null}
401
+ </View>
357
402
  </View>
358
- </View>
403
+ )}
359
404
 
360
405
  <Pressable
361
406
  onPress={onClose}
@@ -443,6 +488,7 @@ export function ChatView({
443
488
  : null
444
489
  }
445
490
  timeLabel={formatClockTime(message.createdAt, locale)}
491
+ receiptLabel={message.id === lastVisitorMessageId ? receiptLabel(message, labels) : undefined}
446
492
  onSubmitProcedureForm={
447
493
  shouldShowProcedureForm(messages, message)
448
494
  ? (body) => void onSendMessage(body).catch(() => {})
@@ -457,6 +503,13 @@ export function ChatView({
457
503
  );
458
504
  })}
459
505
 
506
+ {awaitingTeammate ? (
507
+ <View style={styles.waitingRow}>
508
+ <TeamAvatars avatars={teamAvatars} size={20} color={color} />
509
+ <Text style={styles.waitingText}>{labels.waitingForTeammate}</Text>
510
+ </View>
511
+ ) : null}
512
+
460
513
  {agentTyping ? (
461
514
  <View style={styles.typingRow}>
462
515
  <View style={styles.typingBubble}>
@@ -576,6 +629,7 @@ function MessageBubble({
576
629
  labels,
577
630
  senderLabel,
578
631
  timeLabel,
632
+ receiptLabel,
579
633
  onSubmitProcedureForm,
580
634
  onAgentUiAction,
581
635
  onOpenImage,
@@ -585,10 +639,28 @@ function MessageBubble({
585
639
  labels: ChatLabels;
586
640
  senderLabel?: string | null;
587
641
  timeLabel: string;
642
+ receiptLabel?: string;
588
643
  onSubmitProcedureForm?: (body: string) => void;
589
644
  onAgentUiAction?: (action: AgentUiAction) => void | Promise<void>;
590
645
  onOpenImage: (image: { url: string; name: string }) => void;
591
646
  }) {
647
+ const joinedName = message.metadata?.event === 'agent_joined' ? message.metadata?.agentName : null;
648
+ if (message.messageType === 'system' && typeof joinedName === 'string' && joinedName) {
649
+ return (
650
+ <View style={[styles.systemRow, styles.joinedRow]}>
651
+ <Avatar
652
+ avatar={typeof message.metadata?.agentAvatar === 'string' ? message.metadata.agentAvatar : null}
653
+ name={joinedName}
654
+ size={20}
655
+ color={palette.ink}
656
+ />
657
+ <Text style={styles.systemText}>
658
+ <Text style={styles.joinedName}>{joinedName}</Text> {labels.joinedConversation}
659
+ </Text>
660
+ </View>
661
+ );
662
+ }
663
+
592
664
  if (message.messageType === 'system') {
593
665
  const text = message.bodyPlain || stripHtml(message.body || '') || labels.conversationUpdated;
594
666
  return (
@@ -599,6 +671,17 @@ function MessageBubble({
599
671
  }
600
672
 
601
673
  const isVisitor = message.direction === 'inbound';
674
+
675
+ if (message.deletedAt) {
676
+ return (
677
+ <View style={[styles.messageRow, isVisitor ? styles.messageRowRight : styles.messageRowLeft]}>
678
+ <View style={styles.deletedBubble}>
679
+ <Text style={styles.deletedText}>{labels.messageDeleted}</Text>
680
+ </View>
681
+ </View>
682
+ );
683
+ }
684
+
602
685
  const rtl = isRTL();
603
686
  const messageText = message.bodyPlain || stripHtml(message.body || '');
604
687
  const hasBody = !!messageText;
@@ -658,6 +741,8 @@ function MessageBubble({
658
741
  ) : null}
659
742
  </View>
660
743
 
744
+ {receiptLabel ? <Text style={styles.receiptText}>{receiptLabel}</Text> : null}
745
+
661
746
  {agentUiBlocks.length > 0 && onAgentUiAction ? (
662
747
  <AgentUiBlocks
663
748
  blocks={agentUiBlocks}
@@ -679,6 +764,12 @@ function MessageBubble({
679
764
  );
680
765
  }
681
766
 
767
+ /** Sent once the server has it, Seen once a teammate opened the conversation. */
768
+ function receiptLabel(message: Message, labels: ChatLabels): string | undefined {
769
+ if (message.deletedAt || message.status === 'pending' || message.status === 'failed') return undefined;
770
+ return message.status === 'read' ? labels.messageSeen : labels.messageSent;
771
+ }
772
+
682
773
  function AttachmentPreview({
683
774
  attachment,
684
775
  isVisitor,
@@ -1,3 +1,4 @@
1
+ import type { TeamAvatar } from '../../../types';
1
2
  /** Props and label bundle for the chat thread. */
2
3
 
3
4
  import type { DraftAttachment, PickResult } from '../../../attachments/pickers';
@@ -25,6 +26,11 @@ export interface ChatLabels {
25
26
  today: string;
26
27
  yesterday: string;
27
28
  conversationUpdated: string;
29
+ messageDeleted: string;
30
+ messageSent: string;
31
+ messageSeen: string;
32
+ waitingForTeammate: string;
33
+ joinedConversation: string;
28
34
  procedureSend: string;
29
35
  procedureSent: string;
30
36
  blocks: AgentUiBlockLabels;
@@ -56,7 +62,14 @@ export interface ChatViewProps {
56
62
  /** Whoever is handling this thread, once the backend assigns someone. */
57
63
  agentName?: string | null;
58
64
  agentAvatar?: string | null;
65
+ /** The assigned teammate's own presence; falls back to `isOnline` when absent. */
66
+ agentOnline?: boolean | null;
59
67
  isOnline: boolean;
68
+ /** Header title with no teammate yet (or after close); falls back to the Team label. */
69
+ teamName?: string | null;
70
+ teamAvatars?: TeamAvatar[];
71
+ /** e.g. "We usually reply within a few minutes", under the team name. */
72
+ replyTimeLabel?: string;
60
73
  prefillText?: string;
61
74
  offlineMessage?: string;
62
75
  locale: string;