@linktr.ee/messaging-react 3.5.6-rc-1783060033 → 3.5.6-rc-1783097662

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 (40) hide show
  1. package/dist/{Card-DYe76Xh3.cjs → Card-4eHS-HQB.cjs} +2 -2
  2. package/dist/{Card-DYe76Xh3.cjs.map → Card-4eHS-HQB.cjs.map} +1 -1
  3. package/dist/{Card-CAJnEq1K.cjs → Card-Bxu71IQM.cjs} +2 -2
  4. package/dist/{Card-CAJnEq1K.cjs.map → Card-Bxu71IQM.cjs.map} +1 -1
  5. package/dist/{Card-D_N5hclc.cjs → Card-By-eDX0U.cjs} +2 -2
  6. package/dist/{Card-D_N5hclc.cjs.map → Card-By-eDX0U.cjs.map} +1 -1
  7. package/dist/{Card-rblNjC58.js → Card-CmLNgIdO.js} +2 -2
  8. package/dist/{Card-rblNjC58.js.map → Card-CmLNgIdO.js.map} +1 -1
  9. package/dist/{Card-B5lE5A2D.js → Card-XSDNvOZf.js} +3 -3
  10. package/dist/{Card-B5lE5A2D.js.map → Card-XSDNvOZf.js.map} +1 -1
  11. package/dist/{Card-MBp3Wg6r.js → Card-mDB7sZiu.js} +2 -2
  12. package/dist/{Card-MBp3Wg6r.js.map → Card-mDB7sZiu.js.map} +1 -1
  13. package/dist/{LockedThumbnail-BqyPPVrO.js → LockedThumbnail-C44Wkle9.js} +3 -3
  14. package/dist/{LockedThumbnail-BqyPPVrO.js.map → LockedThumbnail-C44Wkle9.js.map} +1 -1
  15. package/dist/{LockedThumbnail-C7PpVZQ0.cjs → LockedThumbnail-CWhzO3wx.cjs} +2 -2
  16. package/dist/{LockedThumbnail-C7PpVZQ0.cjs.map → LockedThumbnail-CWhzO3wx.cjs.map} +1 -1
  17. package/dist/assets/index.css +1 -1
  18. package/dist/{index-BXzwYXLT.js → index-BRV621wo.js} +55 -67
  19. package/dist/index-BRV621wo.js.map +1 -0
  20. package/dist/index-DBVgTHrA.cjs +2 -0
  21. package/dist/index-DBVgTHrA.cjs.map +1 -0
  22. package/dist/index.cjs +1 -1
  23. package/dist/index.js +1 -1
  24. package/package.json +2 -2
  25. package/src/components/AttachmentCard/index.tsx +1 -1
  26. package/src/components/CustomLinkPreviewList/CustomLinkPreviewCard.tsx +1 -1
  27. package/src/components/CustomLinkPreviewList/index.tsx +6 -12
  28. package/src/components/CustomMessage/CustomMessage.stories.tsx +27 -0
  29. package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +2 -2
  30. package/src/components/CustomMessage/MessageTag.stories.tsx +2 -2
  31. package/src/components/CustomMessageInput/index.tsx +4 -12
  32. package/src/components/LinkAttachment/components/_shared/CardShell.tsx +1 -1
  33. package/src/components/LockedAttachment/components/_shared/LockedCardShell.tsx +1 -1
  34. package/src/components/MediaMessage/index.tsx +8 -1
  35. package/src/components/MessageAttachment/_shared/Bubble.tsx +7 -7
  36. package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +3 -3
  37. package/src/styles.css +104 -9
  38. package/dist/index-BXzwYXLT.js.map +0 -1
  39. package/dist/index-BflWZq0e.cjs +0 -2
  40. package/dist/index-BflWZq0e.cjs.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-BflWZq0e.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./index-DBVgTHrA.cjs");exports.ActionButton=e.ActionButton;exports.Avatar=e.Avatar;exports.ChannelEmptyState=e.ChannelEmptyState;exports.ChannelList=e.ChannelList;exports.ChannelView=e.ChannelView;exports.CustomMessageProvider=e.CustomMessageProvider;exports.FaqList=e.FaqList;exports.FaqListItem=e.FaqListItem;exports.LinkAttachment=e.LinkAttachment;exports.LockedAttachment=e.LockedAttachment;exports.MediaMessage=e.MediaMessage;exports.MessageAttachment=e.MessageAttachment;exports.MessageVoteButtons=e.MessageVoteButtons;exports.MessagingProvider=e.MessagingProvider;exports.MessagingShell=e.MessagingShell;exports.buildCompactMetaLabel=e.buildCompactMetaLabel;exports.formatFileSize=e.formatFileSize;exports.formatRelativeTime=e.formatRelativeTime;exports.getFileExtensionLabel=e.getFileExtensionLabel;exports.getMessageDisplayText=e.getMessageDisplayText;exports.isLinkAttachment=e.isLinkAttachment;exports.isUuidLike=e.isUuidLike;exports.messageAttachmentGroupPositionFromStream=e.bubbleGroupPositionFromStream;exports.normalizeLanguageCode=e.normalizeLanguageCode;exports.resolveConversationParticipant=e.resolveConversationParticipant;exports.resolveLinkAttachment=e.resolveLinkAttachment;exports.resolveMediaFromMessage=e.resolveMediaFromMessage;exports.resolveParticipantDisplayName=e.resolveParticipantDisplayName;exports.useComposerLocked=e.useComposerLocked;exports.useCustomMessage=e.useCustomMessage;exports.useMessageVote=e.useMessageVote;exports.useMessaging=e.useMessaging;
2
2
  //# sourceMappingURL=index.cjs.map
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-BXzwYXLT.js";
1
+ import { a as e, b as t, C as i, c as o, d as n, e as m, F as r, f as g, L as l, h as M, M as c, i as u, j as L, k as h, l as d, m as p, n as v, o as C, p as A, q as k, s as F, t as P, u as b, v as f, w as x, x as y, y as S, z as q, B as z, D as B, E as D, G as E } from "./index-BRV621wo.js";
2
2
  export {
3
3
  e as ActionButton,
4
4
  t as Avatar,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linktr.ee/messaging-react",
3
- "version": "3.5.6-rc-1783060033",
3
+ "version": "3.5.6-rc-1783097662",
4
4
  "description": "React messaging components built on messaging-core for web applications",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@linktr.ee/component-library": "11.8.6",
41
- "@linktr.ee/messaging-core": "2.3.0-rc-1783060033",
41
+ "@linktr.ee/messaging-core": "2.3.0-rc-1783097662",
42
42
  "@phosphor-icons/react": "^2.1.10"
43
43
  },
44
44
  "devDependencies": {
@@ -57,7 +57,7 @@ const AttachmentCard: React.FC<AttachmentCardProps> = ({
57
57
  data-testid={dataTestId}
58
58
  className={classNames(
59
59
  'relative w-[280px] select-none overflow-hidden rounded-[24px] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]',
60
- isDark ? 'bg-[#121110]' : 'bg-white'
60
+ isDark ? 'bg-[#1e2330]' : 'bg-white'
61
61
  )}
62
62
  >
63
63
  {topLeft ? (
@@ -23,7 +23,7 @@ const CustomLinkPreviewCard: React.FC<CustomLinkPreviewCardProps> = ({
23
23
  href={og_scrape_url}
24
24
  target="_blank"
25
25
  rel="noopener noreferrer"
26
- className="relative block w-full rounded-[24px] bg-[#121110] p-2 no-underline transition-opacity hover:opacity-90"
26
+ className="relative block w-full rounded-[24px] bg-[#1e2330] p-2 no-underline transition-opacity hover:opacity-90"
27
27
  >
28
28
  {image_url && (
29
29
  <img
@@ -32,19 +32,13 @@ export const CustomLinkPreviewList = () => {
32
32
  if (!showLinkPreviews) return null
33
33
 
34
34
  return (
35
- // Layout contract: this list is the composer's *shrinkable* region. The
36
- // mobile visitor surface sizes itself to the keyboard-shrunk
37
- // visualViewport, so when vertical space runs out this container gives
38
- // way (min-h-0) and scrolls, while the textarea + send row stays visible
39
- // (shrink-0 in CustomMessageInput). Each card is wrapped in a shrink-0
40
- // block so it keeps its intrinsic height and the container scrolls — a
41
- // bare flex-column child would be shrunk on the column axis and its
42
- // fixed-height hero would spill out of the collapsed card box.
43
- <div className="messaging-link-preview-list flex min-h-0 flex-col items-stretch w-full gap-2 mb-4 overflow-y-auto overscroll-contain">
35
+ <div className="flex flex-col items-stretch w-full gap-2 mb-4">
44
36
  {stateLinkPreviews.map((linkPreview) => (
45
- <div key={linkPreview.og_scrape_url} className="w-full shrink-0">
46
- <CustomLinkPreviewCard link={linkPreview} onDismiss={handleDismiss} />
47
- </div>
37
+ <CustomLinkPreviewCard
38
+ key={linkPreview.og_scrape_url}
39
+ link={linkPreview}
40
+ onDismiss={handleDismiss}
41
+ />
48
42
  ))}
49
43
  </div>
50
44
  )
@@ -247,6 +247,33 @@ WithTipTag.args = {
247
247
  ],
248
248
  }
249
249
 
250
+ export const ThreeConsecutiveSentMessages: StoryFn<TemplateProps> =
251
+ Template.bind({})
252
+ ThreeConsecutiveSentMessages.args = {
253
+ messages: [
254
+ {
255
+ id: 'msg-1',
256
+ text: 'why is it that the sun',
257
+ user: storyUsers.visitor,
258
+ },
259
+ {
260
+ id: 'msg-2',
261
+ text: 'has rays',
262
+ user: storyUsers.creator,
263
+ },
264
+ {
265
+ id: 'msg-3',
266
+ text: 'how how how',
267
+ user: storyUsers.creator,
268
+ },
269
+ {
270
+ id: 'msg-4',
271
+ text: 'thanks for the note',
272
+ user: storyUsers.visitor,
273
+ },
274
+ ],
275
+ }
276
+
250
277
  export const TipOnly: StoryFn<TemplateProps> = Template.bind({})
251
278
  TipOnly.args = {
252
279
  messages: [
@@ -42,8 +42,8 @@ interface ChatNode {
42
42
  }
43
43
 
44
44
  const TEXT_BG_BY_ROLE: Record<ChatRole, string> = {
45
- sender: 'bg-[#121110] text-white',
46
- receiver: 'bg-[#e9eaed] text-[#080707]',
45
+ sender: 'bg-[#1e2330] text-white',
46
+ receiver: 'bg-[#f2f1ef] text-[#000000]',
47
47
  }
48
48
 
49
49
  const TextBubble: React.FC<{ role: ChatRole; children: React.ReactNode }> = ({
@@ -83,7 +83,7 @@ ChatbotReceiverText.parameters = skipInChromatic
83
83
  export const ChatbotSenderText: StoryFn<ComponentProps> = (args) => {
84
84
  return (
85
85
  <div className="p-12">
86
- <div className="bg-[#121110] rounded-[24px] px-4 py-3 w-[280px]">
86
+ <div className="bg-[#1e2330] rounded-[24px] px-4 py-3 w-[280px]">
87
87
  <MessageTag {...args} />
88
88
  </div>
89
89
  </div>
@@ -150,7 +150,7 @@ export const AllVariants: StoryFn = () => {
150
150
  </div>
151
151
  <div className="flex items-center gap-4">
152
152
  <span className="text-sm w-32">Chatbot (sender):</span>
153
- <div className="bg-[#121110] rounded-[24px] px-4 py-3 w-[280px]">
153
+ <div className="bg-[#1e2330] rounded-[24px] px-4 py-3 w-[280px]">
154
154
  <MessageTag
155
155
  message={createMockMessage({
156
156
  metadata: { custom_type: 'MESSAGE_CHATBOT' },
@@ -61,22 +61,14 @@ const CustomMessageInputInner: React.FC = () => {
61
61
  } = useComponentContext('CustomMessageInput')
62
62
 
63
63
  return (
64
- // min-h-0 lets the composer shrink under keyboard squeeze (the link
65
- // preview list inside is the scrollable region); the textarea + send row
66
- // is shrink-0 so it can never be pushed out of view. self-stretch matters
67
- // because the parent `.message-input` row uses items-end, which suppresses
68
- // the default cross-axis stretch — without it this container keeps its
69
- // content height and the squeeze never reaches the preview list. Stream's
70
- // <MessageInput> renders its Input directly (context providers only, no
71
- // wrapper element), so this is a direct flex item of that row.
72
64
  <div
73
- className="central-container flex min-h-0 flex-col gap-2 min-w-0 w-full self-stretch p-2 bg-white rounded-[1.5rem] shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)]"
65
+ className="central-container flex flex-col gap-2 min-w-0 w-full p-2 bg-white rounded-[1.5rem] shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)]"
74
66
  data-dd-privacy="mask"
75
67
  >
76
68
  <QuotedMessagePreview />
77
69
  <CustomLinkPreviewList />
78
70
  <AttachmentPreviewList />
79
- <div className="flex shrink-0">
71
+ <div className="flex">
80
72
  <div className="w-full ml-2 mr-4 self-center leading-[0]">
81
73
  <TextareaComposer
82
74
  aria-disabled={disabled || undefined}
@@ -163,12 +155,12 @@ export const CustomMessageInput: React.FC<CustomMessageInputProps> = ({
163
155
  }
164
156
 
165
157
  return (
166
- <div className="flex min-h-0 flex-col gap-4 p-4">
158
+ <div className="flex flex-col gap-4 p-4">
167
159
  <div
168
160
  // @ts-expect-error Only React 19 onwards has `inert` in its types.
169
161
  inert={isFrozen ? '' : undefined}
170
162
  aria-disabled={isFrozen || undefined}
171
- className="message-input flex min-h-0 items-end gap-4 aria-disabled:opacity-40"
163
+ className="message-input flex items-end gap-4 aria-disabled:opacity-40"
172
164
  >
173
165
  {renderActions && (
174
166
  <div className="flex h-12 shrink-0 items-center justify-center">
@@ -64,7 +64,7 @@ const CardShell: React.FC<CardShellProps> = ({
64
64
  const isInteractive = href != null || onClick != null
65
65
  const className = classNames(
66
66
  SHELL_CLASS,
67
- bgClassName ?? (variant === 'dark' ? 'bg-[#121110]' : 'bg-white'),
67
+ bgClassName ?? (variant === 'dark' ? 'bg-[#1e2330]' : 'bg-white'),
68
68
  // `focus-ring` is a design-system utility from the component-library
69
69
  // tailwind preset — outline-none + a black 2px focus-visible ring
70
70
  // with offset, so keyboard users can see the focused card.
@@ -26,7 +26,7 @@ const LockedCardShell: React.FC<LockedCardShellProps> = ({
26
26
  className={classNames(
27
27
  'relative w-[280px] select-none overflow-hidden rounded-md',
28
28
  'shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]',
29
- variant === 'dark' ? 'bg-[#121110]' : 'bg-white'
29
+ variant === 'dark' ? 'bg-[#1e2330]' : 'bg-white'
30
30
  )}
31
31
  >
32
32
  {children}
@@ -15,7 +15,7 @@ function formatBytes(bytes: number): string {
15
15
  }
16
16
 
17
17
  function linkCardShellClass(isMyMessage: boolean) {
18
- const bg = isMyMessage ? 'bg-[#121110]' : 'bg-[#F3F3F1]'
18
+ const bg = isMyMessage ? 'bg-[#1e2330]' : 'bg-[#F3F3F1]'
19
19
  return `w-[280px] select-none overflow-hidden rounded-[24px] ${bg} shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]`
20
20
  }
21
21
 
@@ -318,6 +318,13 @@ const MediaMessageRoot: React.FC<MediaMessageProps> = ({
318
318
  <div className="str-chat__message-bubble-wrapper">
319
319
  <div
320
320
  className="str-chat__message-bubble"
321
+ // The visible child is a self-contained AttachmentCard (white /
322
+ // #1e2330 card with its own shadow + radius), not a speech
323
+ // bubble, and this wrapper is transparent. Mark it so the
324
+ // received/sent tail in `styles.css` skips it — otherwise a
325
+ // tail tinted from the text-bubble surface (#f2f1ef) hangs off
326
+ // a white card and reads as a detached grey nub.
327
+ data-attachment-bubble="true"
321
328
  style={{ padding: 0, borderRadius: 0, overflow: 'visible', background: 'transparent' }}
322
329
  >
323
330
  {linkAttachment ? (
@@ -29,8 +29,8 @@ export interface BubbleProps {
29
29
  // Colors and metrics tracked from `stream-chat-react`'s default
30
30
  // `.str-chat__message-bubble` token set so attachments visually drop
31
31
  // into a normal `CustomMessage` thread without any seams:
32
- // - light → `--str-chat__secondary-surface-color` = grey200 (#e9eaed)
33
- // - dark → `.str-chat__message--me` override in `styles.css` (#121110)
32
+ // - light → `--str-chat__message-bubble-background-color` in `styles.css` (#f2f1ef)
33
+ // - dark → `.str-chat__message--me` override in `styles.css` (#1e2330)
34
34
  // - text padding → `--str-chat__spacing-2 --str-chat__spacing-4` = 8px 16px
35
35
  // - border-radius → `--str-chat__border-radius-md` = 18px
36
36
  //
@@ -41,13 +41,13 @@ export interface BubbleProps {
41
41
  // rebrand would need to touch every site. A central token would
42
42
  // collapse the migration to one definition.
43
43
  const BUBBLE_BG_BY_VARIANT: Record<BubbleVariant, string> = {
44
- dark: 'bg-[#121110]',
45
- light: 'bg-[#e9eaed]',
44
+ dark: 'bg-[#1e2330]',
45
+ light: 'bg-[#f2f1ef]',
46
46
  }
47
47
 
48
48
  const BUBBLE_TEXT_BY_VARIANT: Record<BubbleVariant, string> = {
49
49
  dark: 'text-white',
50
- light: 'text-[#080707]',
50
+ light: 'text-[#000000]',
51
51
  }
52
52
 
53
53
  const BUBBLE_BORDER_BY_VARIANT: Record<BubbleVariant, string> = {
@@ -104,8 +104,8 @@ const CORNER_CLASSES_BY_SIDE_AND_POSITION: Record<
104
104
  * is the file', 'Here is the image').
105
105
  *
106
106
  * Two visual variants:
107
- * - `dark` — sender-side (Composer / Sent), white text on `#121110`.
108
- * - `light` — recipient-side (Received), dark text on `#e9eaed`.
107
+ * - `dark` — sender-side (Composer / Sent), white text on `#1e2330`.
108
+ * - `light` — recipient-side (Received), dark text on `#f2f1ef`.
109
109
  *
110
110
  * Background colors, padding, border-radius, and inherited font come
111
111
  * from the stream-chat-react `.str-chat__message-bubble` token set so
@@ -41,11 +41,11 @@ export interface CompactDocumentRowProps {
41
41
  // so they stay variant-aware. The title inherits the bubble's text
42
42
  // color (set on the parent `Bubble`).
43
43
  //
44
- // `text-black/55` on the `#e9eaed` light bubble lands right at the
44
+ // `text-black/55` on the `#f2f1ef` light bubble lands right at the
45
45
  // WCAG AA 4.5:1 small-text threshold (≈4.5:1) — bump to `/65` so
46
46
  // the meta line has a comfortable margin on the light bubble. The
47
- // dark variant (`text-white/55` on `#121110`) already passes AA at
48
- // ~6:1, so we leave it alone.
47
+ // dark variant (`text-white/55` on `#1e2330`) already passes AA,
48
+ // so we leave it alone.
49
49
  const META_CLASS_BY_VARIANT: Record<BubbleVariant, string> = {
50
50
  dark: 'text-white/55',
51
51
  light: 'text-black/65',
package/src/styles.css CHANGED
@@ -8,10 +8,10 @@
8
8
  --str-chat__font-family: inherit;
9
9
 
10
10
  --str-chat__message-bubble-border-radius: 1.5rem;
11
- --str-chat__message-bubble-background-color: #f1f0ee;
12
- --str-chat__message-bubble-color: #181818;
11
+ --str-chat__message-bubble-background-color: #f2f1ef;
12
+ --str-chat__message-bubble-color: #000000;
13
13
 
14
- --str-chat__own-message-bubble-background-color: #121110;
14
+ --str-chat__own-message-bubble-background-color: #1e2330;
15
15
  --str-chat__own-message-bubble-color: #fff;
16
16
  }
17
17
 
@@ -560,7 +560,36 @@
560
560
 
561
561
  .str-chat__message .str-chat__message-bubble {
562
562
  font-size: 0.875rem;
563
- line-height: 1.25rem;
563
+ line-height: 1.3125rem;
564
+ letter-spacing: 0.14px;
565
+ }
566
+
567
+ /* Keep every bubble a full pill regardless of its position in a same-author
568
+ run. stream-chat-react's default theme flattens the inner corners of
569
+ grouped bubbles (bottom-/top-inline-end for `--me`, inline-start for
570
+ `--other`) via `.str-chat__li--{top,middle,bottom} .str-chat__message--{me,other}
571
+ .str-chat__message-bubble` rules at specificity (0,3,0). We prepend the
572
+ `.str-chat` root class to reach (0,4,0) so this wins independently of
573
+ stylesheet load order and restores the fully-rounded pill from the Figma
574
+ spec — only the trailing bubble's tail (`::after`, below) breaks the pill. */
575
+ .str-chat .str-chat__li--single .str-chat__message--me .str-chat__message-bubble,
576
+ .str-chat .str-chat__li--top .str-chat__message--me .str-chat__message-bubble,
577
+ .str-chat .str-chat__li--middle .str-chat__message--me .str-chat__message-bubble,
578
+ .str-chat .str-chat__li--bottom .str-chat__message--me .str-chat__message-bubble,
579
+ .str-chat
580
+ .str-chat__li--single
581
+ .str-chat__message--other
582
+ .str-chat__message-bubble,
583
+ .str-chat .str-chat__li--top .str-chat__message--other .str-chat__message-bubble,
584
+ .str-chat
585
+ .str-chat__li--middle
586
+ .str-chat__message--other
587
+ .str-chat__message-bubble,
588
+ .str-chat
589
+ .str-chat__li--bottom
590
+ .str-chat__message--other
591
+ .str-chat__message-bubble {
592
+ border-radius: var(--str-chat__message-bubble-border-radius);
564
593
  }
565
594
 
566
595
  /* Chatbot reply markdown — restore semantics that consumer Tailwind preflight
@@ -667,21 +696,87 @@
667
696
  margin-bottom: 0;
668
697
  }
669
698
 
699
+ /* Roomier, even vertical gap between bubbles within a same-author run.
700
+ stream defaults grouped messages to 0.125rem (2px), which reads as
701
+ cramped now that each bubble is a full pill; the Figma grouped-message
702
+ spacing is closer to 6px. `.str-chat` prefix → (0,3,0) so it beats
703
+ stream's (0,2,0) grouping rule regardless of stylesheet load order. */
704
+ .str-chat .str-chat__li--top .str-chat__message,
705
+ .str-chat .str-chat__li--middle .str-chat__message {
706
+ margin-block-end: 0.375rem;
707
+ }
708
+
670
709
  .str-chat__li--single,
671
710
  .str-chat__li--bottom {
672
711
  .str-chat__message {
673
712
  margin-block-end: 0.875rem;
674
713
  }
675
714
 
676
- .str-chat__message--other .str-chat__message-bubble {
677
- border-end-start-radius: var(--str-chat__message-bubble-border-radius);
715
+ /* `:not([data-attachment-bubble])` keeps the tail off self-contained
716
+ attachment/media cards (see `MediaMessage`): those render their own
717
+ shadowed card inside a transparent bubble, so a tail tinted from the
718
+ text-bubble surface would hang off the card rather than read as part
719
+ of it. Only genuine text/message bubbles get a tail. */
720
+ .str-chat__message--other
721
+ .str-chat__message-bubble-wrapper
722
+ > .str-chat__message-bubble:last-child:not([data-attachment-bubble]) {
723
+ position: relative;
724
+ /* Form a stacking context so the `z-index: -1` tail below paints
725
+ *after* this bubble's own (opaque, same-color) background but
726
+ *behind* its in-flow content. Without this the tail would either
727
+ sit on top of content (a positioned pseudo-element paints above
728
+ in-flow siblings — switching to `::before` would NOT change that)
729
+ or drop behind the whole bubble. This matters most for the
730
+ zero-padding `MediaMessageRoot` bubble, whose media card reaches
731
+ the corner: the tail must tuck behind the card, not cover it. */
732
+ isolation: isolate;
733
+
734
+ &::after {
735
+ content: '';
736
+ position: absolute;
737
+ inset-block-end: 0;
738
+ inset-inline-start: calc(-1 * 0.25rem);
739
+ z-index: -1;
740
+ width: 1.25rem;
741
+ height: 1.4375rem;
742
+ background-color: var(--str-chat__message-bubble-background-color);
743
+ pointer-events: none;
744
+ transform: scaleX(-1);
745
+ transform-origin: center;
746
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='23' fill='none'%3E%3Cpath fill='black' d='M0 0H15C15 10.1934 15.859 15.2345 19.168 21.4893C19.527 22.168 19.039 22.9917 18.274 22.9178C5.176 21.6506 0.32 6.0737 0 0Z'/%3E%3C/svg%3E")
747
+ no-repeat center / 100% 100%;
748
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='23' fill='none'%3E%3Cpath fill='black' d='M0 0H15C15 10.1934 15.859 15.2345 19.168 21.4893C19.527 22.168 19.039 22.9917 18.274 22.9178C5.176 21.6506 0.32 6.0737 0 0Z'/%3E%3C/svg%3E")
749
+ no-repeat center / 100% 100%;
750
+ }
678
751
  }
679
752
 
680
- .str-chat__message--me .str-chat__message-bubble {
681
- border-end-end-radius: var(--str-chat__message-bubble-border-radius);
753
+ .str-chat__message--me
754
+ .str-chat__message-bubble-wrapper
755
+ > .str-chat__message-bubble:last-child:not([data-attachment-bubble]) {
756
+ position: relative;
757
+ /* See the `--other` rule above: stacking context so the tail tucks
758
+ behind in-flow content (e.g. the corner of a zero-padding
759
+ `MediaMessageRoot` card) instead of painting over it. */
760
+ isolation: isolate;
761
+
762
+ &::after {
763
+ content: '';
764
+ position: absolute;
765
+ inset-block-end: 0;
766
+ inset-inline-end: calc(-1 * 0.25rem);
767
+ z-index: -1;
768
+ width: 1.25rem;
769
+ height: 1.4375rem;
770
+ background-color: var(--str-chat__own-message-bubble-background-color);
771
+ pointer-events: none;
772
+ -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='23' fill='none'%3E%3Cpath fill='black' d='M0 0H15C15 10.1934 15.859 15.2345 19.168 21.4893C19.527 22.168 19.039 22.9917 18.274 22.9178C5.176 21.6506 0.32 6.0737 0 0Z'/%3E%3C/svg%3E")
773
+ no-repeat center / 100% 100%;
774
+ mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='23' fill='none'%3E%3Cpath fill='black' d='M0 0H15C15 10.1934 15.859 15.2345 19.168 21.4893C19.527 22.168 19.039 22.9917 18.274 22.9178C5.176 21.6506 0.32 6.0737 0 0Z'/%3E%3C/svg%3E")
775
+ no-repeat center / 100% 100%;
776
+ }
682
777
  }
683
- }
684
778
 
779
+ }
685
780
  /* Locked attachment wrapper: stack card + text bubble in the correct direction */
686
781
  .str-chat__li .str-chat__message--me .str-chat__message-bubble-wrapper {
687
782
  display: flex;