Skip to content

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

typescript
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:

TabIconContent
ChatMessageSquareMessages, reactions, typing indicator, input
PeopleUsersChatParticipantRoster showing online/offline participants

Key Behaviors

  • Auto-scroll: Scrolls to bottom on new messages (via setTimeout for 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 + AlertDialog confirmation
  • 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

typescript
interface Props {
	participants: ChatParticipant[];
	disabled?: boolean;
	maxLength?: number; // Default: 5000
	onSend: (content: string, mentions: MentionData[]) => void | Promise<void>;
	onTypingInput: () => void;
}

Exported Functions

typescript
// 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 @ opens MentionSuggestionList as a floating dropdown
  • Character count: Displays remaining characters, turns red near limit
  • Error recovery: If onSend throws, the editor content is restored
  • Typing throttle: Calls onTypingInput throttled to 500ms intervals

TipTap Extensions

  • StarterKit (basic editing)
  • Placeholder ("Type a message...")
  • CharacterCount (enforces maxLength)
  • Mention (with custom suggestion renderer using MentionSuggestionList)

MentionSuggestionList

File: src/lib/components/chat/MentionSuggestionList.svelte

Dropdown list for @mention autocomplete. Mounted dynamically by TipTap's Mention extension via mount().

Props

typescript
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

typescript
interface Props {
	content: string;
	isOwn: boolean;
	mentions?: MentionData[];
}

Content Parsing

The component splits message text into three segment types:

  1. Plain text - Rendered as-is with whitespace-pre-wrap
  2. URLs - Detected via regex, rendered as clickable links (target="_blank", rel="noopener noreferrer")
  3. @mentions - Matched against mentions array, 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

typescript
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

typescript
interface Props {
	participants: ChatParticipant[];
}

Role Priority Order

  1. Host / Owner
  2. Producer
  3. Co-host / Admin
  4. Editor
  5. 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.typingText for 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 FileComponentCoverage
MessageContent.test.tsMessageContentURL detection, mention highlighting, plain text
MentionSuggestionList.test.tsMentionSuggestionListRendering, selection, keyboard nav
ChatParticipantRoster.test.tsChatParticipantRosterOnline/offline grouping, role sorting

Internal documentation - Not for public distribution