Chat Components
All chat components live in src/lib/components/chat/ and follow Svelte 5 Runes patterns.
Component Hierarchy
EpisodeChatSidebar
File: src/lib/components/chat/EpisodeChatSidebar.svelte
Main container that orchestrates all chat functionality: message display, input, participant roster, and real-time subscriptions.
Props
interface Props {
episodeId: string;
currentUser: {
id: string;
name: string;
isGuest?: boolean;
guestId?: string;
avatarUrl?: string;
};
initialMessages?: ChatMessage[];
showPopoutButton?: boolean;
podcastName?: string;
accessToken?: string; // Required for guest portal
participants?: ChatParticipant[];
}Tabs
The sidebar has two tabs:
| Tab | Icon | Content |
|---|---|---|
| Chat | MessageSquare | Messages, reactions, typing indicator, input |
| People | Users | ChatParticipantRoster showing online/offline participants |
Key Behaviors
- Auto-scroll: Scrolls to bottom on new messages (via
setTimeoutfor DOM update) - Infinite scroll: Loads earlier messages when scrolling up
- Unread tracking: Counts unread messages, persists last-read timestamp to DB
- Message deletion: Owner can delete messages via hover trash icon +
AlertDialogconfirmation - Optimistic updates: Messages appear immediately, replaced/removed on server response
- Cleanup: Unsubscribes all channels, resets stores, clears timeouts on destroy
ChatMentionInput
File: src/lib/components/chat/ChatMentionInput.svelte
TipTap-based rich text editor with @mention autocomplete support.
Props
interface Props {
participants: ChatParticipant[];
disabled?: boolean;
maxLength?: number; // Default: 5000
onSend: (content: string, mentions: MentionData[]) => void | Promise<void>;
onTypingInput: () => void;
}Exported Functions
// Focus the editor programmatically
export function focus(): void;Key Behaviors
- Enter to send: Pressing Enter sends the message (Shift+Enter for newline)
- @mention trigger: Typing
@opensMentionSuggestionListas a floating dropdown - Character count: Displays remaining characters, turns red near limit
- Error recovery: If
onSendthrows, the editor content is restored - Typing throttle: Calls
onTypingInputthrottled to 500ms intervals
TipTap Extensions
StarterKit(basic editing)Placeholder("Type a message...")CharacterCount(enforcesmaxLength)Mention(with custom suggestion renderer usingMentionSuggestionList)
MentionSuggestionList
File: src/lib/components/chat/MentionSuggestionList.svelte
Dropdown list for @mention autocomplete. Mounted dynamically by TipTap's Mention extension via mount().
Props
interface Props {
items: ChatParticipant[];
command: (attrs: { id: string; label: string }) => void;
}Key Behaviors
- Filterable: TipTap filters participants by typed query
- Keyboard navigation: Arrow keys to navigate, Enter to select
- Avatar display: Shows participant avatar with fallback initials
- Role labels: Displays role badge (e.g., "Host", "Guest")
MessageContent
File: src/lib/components/chat/MessageContent.svelte
Renders message text with URL linkification and @mention highlighting.
Props
interface Props {
content: string;
isOwn: boolean;
mentions?: MentionData[];
}Content Parsing
The component splits message text into three segment types:
- Plain text - Rendered as-is with
whitespace-pre-wrap - URLs - Detected via regex, rendered as clickable links (
target="_blank",rel="noopener noreferrer") - @mentions - Matched against
mentionsarray, highlighted with colored background. Own mentions use a distinct accent color.
MessageReactions
File: src/lib/components/chat/MessageReactions.svelte
Emoji reaction display and picker.
Props
interface Props {
reactions: ReactionSummary[];
onToggle: (emoji: string) => void;
readonly?: boolean; // Hides picker when true
}Available Emojis
Six hardcoded options: ['👍', '❤️', '😂', '😮', '🎉', '🔥']
Key Behaviors
- Toggle: Clicking an existing reaction toggles it (add/remove)
- Picker: Popover with all 6 emoji options (hidden when
readonly) - Visual state: Reacted emojis shown with highlighted border
- Count display: Shows total reaction count per emoji
ChatParticipantRoster
File: src/lib/components/chat/ChatParticipantRoster.svelte
Displays episode participants grouped by online status, sorted by role priority.
Props
interface Props {
participants: ChatParticipant[];
}Role Priority Order
- Host / Owner
- Producer
- Co-host / Admin
- Editor
- Guest
Key Behaviors
- Online detection: Cross-references with
chatPresenceStore.onlineUsers - Status indicator: Green dot for online, gray for offline
- Avatar fallback: Shows initials when no avatar URL available
- Role badge: Colored badge with role label
TypingIndicator
File: src/lib/components/chat/TypingIndicator.svelte
Animated dots showing when others are typing.
Key Behaviors
- Text generation: Uses
typingStore.typingTextfor grammatically correct output:- "Alice is typing..."
- "Alice and Bob are typing..."
- "3 people are typing..."
- Animation: Three bouncing dots with staggered delays
- Auto-hide: Hidden when no one is typing
ChatPresenceAvatars
File: src/lib/components/chat/ChatPresenceAvatars.svelte
Overlapping avatar circles for the chat header showing who is online.
Key Behaviors
- Max visible: Shows up to 3 avatars, with "+N" overflow counter
- Tooltip: Hovering the overflow counter shows remaining names
- Avatar fallback: Initials when no avatar image available
- Data source: Reads from
chatPresenceStore.onlineList
Test Coverage
Tests live in src/lib/components/chat/__tests__/:
| Test File | Component | Coverage |
|---|---|---|
MessageContent.test.ts | MessageContent | URL detection, mention highlighting, plain text |
MentionSuggestionList.test.ts | MentionSuggestionList | Rendering, selection, keyboard nav |
ChatParticipantRoster.test.ts | ChatParticipantRoster | Online/offline grouping, role sorting |