AiStreamContainer
Package: @synapse-ui/stream-container
Selector: synapse-ai-stream-container
Status: Stable
A container built for rendering streaming markdown chunks with syntax highlighting and copy-to-clipboard utilities.
Overview
AiStreamContainer accepts incremental text chunks from a generic SSE stream, renders them as markdown using marked, and applies Shiki syntax highlighting to fenced code blocks. It is optimized for high-frequency DOM updates with OnPush change detection.
Installation
npm install @synapse-ui/stream-container @synapse-ui/theme @synapse-ui/core
npm install marked shiki
Basic Example
import { Component, signal } from '@angular/core';
import { AiStreamContainerComponent } from '@synapse-ui/stream-container';
@Component({
selector: 'app-response',
standalone: true,
imports: [AiStreamContainerComponent],
template: ` <synapse-ai-stream-container [content]="streamContent()" [streaming]="isStreaming()" [autoScroll]="true" /> `,
})
export class ResponseComponent {
streamContent = signal('');
isStreaming = signal(false);
connectToSSE(url: string): void {
this.isStreaming.set(true);
const source = new EventSource(url);
source.onmessage = (event) => {
this.streamContent.update((prev) => prev + event.data);
};
source.onerror = () => {
source.close();
this.isStreaming.set(false);
};
}
}
API Reference (Planned)
Inputs
| Input | Type | Default | Description |
|---|---|---|---|
content | string | '' | Accumulated markdown content |
streaming | boolean | false | Shows cursor indicator when true |
autoScroll | boolean | true | Scroll to bottom on new chunks |
highlightTheme | string | 'github-dark' | Shiki theme for code blocks |
allowedTags | string[] | Safe defaults | HTML tags allowed after sanitization |
emptyMessage | string | 'Waiting for response...' | Shown when content is empty |
Outputs
| Output | Payload | Description |
|---|---|---|
codeCopied | { language: string; code: string } | Fired when user copies a code block |
renderComplete | void | Fired when streaming ends and final render completes |
Streaming Architecture
flowchart LR
SSE[SSE Chunks] --> Buffer[Chunk Buffer]
Buffer --> Marked[marked parser]
Marked --> Sanitize[DOMPurify / sanitizer]
Sanitize --> DOM[Incremental DOM update]
Marked -->|fenced code| Shiki[Shiki highlighter]
Shiki --> DOM
Chunk Buffer Strategy
The container maintains an internal buffer to avoid re-parsing the entire document on every chunk:
- Append new chunk to buffer.
- Debounce parse (configurable, default ~16ms for 60fps).
- Diff and patch DOM where possible.
- Highlight only new or changed code blocks.
See ADR 004 for the full decision record.
Markdown Support
Supported via marked with GFM (GitHub Flavored Markdown):
- Headings, paragraphs, lists
- Bold, italic, strikethrough
- Fenced code blocks with language tags
- Blockquotes
- Tables (GFM)
- Links (sanitized,
target="_blank"withrel="noopener")
Unsupported or stripped for security: raw HTML, script tags, iframes.
Code Block Features
Each fenced code block renders with:
- Language label badge
- Copy-to-clipboard button
- Shiki syntax highlighting
- Horizontal scroll for long lines
/* Uses these tokens */
background: var(--synapse-ai-code-bg);
font-family: var(--synapse-font-family-mono);
SSE Integration Pattern
Synapse UI does not manage the SSE connection — your app does. Recommended pattern:
import { inject, DestroyRef } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { createSSEObservable } from '@synapse-ui/core';
// Planned utility in @synapse-ui/core
createSSEObservable('/api/chat')
.pipe(takeUntilDestroyed())
.subscribe({
next: (chunk) => this.content.update((c) => c + chunk),
complete: () => this.streaming.set(false),
});
Accessibility
- Container uses
role="log"witharia-live="polite"during streaming. - Copy buttons are keyboard accessible with
aria-label="Copy code". - Streaming cursor is decorative (
aria-hidden="true"). - Code blocks are navigable via keyboard (Tab to copy button).
Performance Guidelines
| Scenario | Recommendation |
|---|---|
| Chunks < 50/sec | Default settings |
| Chunks > 50/sec | Increase debounce to 32–50ms |
| Very long responses | Enable CDK virtual scroll (post-MVP) |
| Multiple streams | Use separate container instances |
Theming
| Element | Token |
|---|---|
| Container background | --synapse-surface-primary |
| Text | --synapse-text-primary |
| Code block background | --synapse-ai-code-bg |
| Stream cursor | --synapse-ai-stream-cursor |
| Blockquote border | --synapse-border-strong |