Overview
Dense append-only viewer for timestamped operational events. Use it for workflow run logs, job output streams, webhook traces, activity completion feeds, and other real-time or historical event consoles.
Overview
EventStreamViewer renders a scrollable list of timestamped events, each with a severity tone, optional source label, a one-line summary, and optional expandable JSON details. It handles follow-latest scrolling (auto-scroll to bottom as events arrive), a paused state when the user scrolls away, filtering hooks, copy actions, reconnect boundaries, and sequence-gap markers. Empty, loading, disconnected, and truncated states are built in.
Usage
With filtering
When you pass an onFilter callback, the viewer renders a search input. Your component is responsible for filtering the events array in response to the callback.
With copy-visible action
Follow-latest with bindable state
The followLatest prop is bindable. Bind it to read whether the viewer is paused or to programmatically resume following.
Event structure
Each event in the events array can be a normal StreamEvent or an additive reconnect boundary:
Reconnect and gap markers
Pass EventStreamEntry[] when a retained stream can resume after a network reconnect. A { kind: 'reconnected' } entry renders a visible divider labeled Reconnected — N events replayed.
Set detectSequenceGaps only when events is the complete, unfiltered stream. When consecutive rendered StreamEvent entries both provide integer sequence values and the later value is not the previous value plus one, the viewer inserts a sequence-gap marker before the later event. The marker shows the expected and received sequence numbers, for example Sequence gap — expected 8, received 10.
The onCopyVisible callback includes reconnect and sequence-gap markers in the copied text, so the copied stream preserves the same replay and continuity context users saw on screen.
Accessibility
The viewer exposes a role="log" region, which carries implicit polite live-region semantics so screen readers announce newly appended events. The region is keyboard focusable (tabindex="0") to support arrow-key scrolling.
Each event's timestamp is wrapped in a <time datetime="..."> element with the machine-readable ISO value, so assistive technology can parse the precise moment. The visible label is separate from the machine-readable datetime attribute.
Severity tones are conveyed both visually (color bar, badge text) and through an aria-label on the badge element. Source labels and summary text are part of the document flow and are read naturally.
The JSON details panel is toggled by a button with aria-expanded and aria-controls. Copy actions have descriptive aria-label attributes.
Reconnect boundaries render as visible dividers with role="separator" and a matching accessible label. Sequence gaps render as visible notes with role="note" and an accessible label that includes the expected and received sequence numbers.
A visually-hidden live region (always in the DOM, never removed with {#if}) announces copy confirmation to screen readers without double-announcing on the interactive buttons.
See event-stream-viewer.a11y.md for the full accessibility contract.
When to use
- Displaying real-time or historical operational events such as workflow steps, job logs, or webhook traces.
- Showing an append-only diagnostic stream with filtering, copy, and structured detail expansion.
- Showing a social activity feed or notification timeline — use feed instead.
- Rendering paginated historical records with sorting — use data-table instead.
Examples
Basic event stream
A minimal operational event log with severity tones, timestamps, and source labels. Suitable for job runners, deploy logs, and webhook traces.
Reconnect replay with sequence gap
Shows a reconnect boundary after retained events replay, followed by an advisory sequence-gap marker.
Loading and empty states
Demonstrates the loading skeleton, empty state, and truncated notice variants.
With search filtering
Consumer-controlled filtering via the onFilter callback. The viewer renders a search input; the consumer owns the filtered events array.
Workflow run with retries and signals
A realistic workflow run event stream with activity completions, retry attempts, inbound signals, and a structured failure trace with expandable JSON details.
Props
| Name | Type | Default | Required | Bindable | Description |
|---|---|---|---|---|---|
events | EventStreamEntry[] | req | Events and additive boundary entries to render in chronological order, oldest first. | ||
connectionState | 'connected' | 'connecting' | 'disconnected' | 'error' | Current connection state. When provided, renders a StatusDot connection preset in the toolbar. Omit when the stream has no live transport. | |||
followLatest | boolean | true | bind | When true, new events automatically scroll the list to the bottom. Set to false to pause follow-latest (e.g. while the user reads earlier events). Bindable so the parent can read the paused state the component sets internally. | |
truncated | boolean | false | Whether to show the "events were truncated" notice. This is a boolean flag,
not a count: the viewer never slices events itself. Set it to true when
you have already trimmed the array (e.g. capped retention) and want users to
know earlier events are not shown. | ||
loading | boolean | false | Show a loading skeleton instead of the event list. Use while the first batch of events is in flight. | ||
label | text | 'Event stream' | Accessible label for the event list region. Required for accessibility. Defaults to "Event stream". | ||
detectSequenceGaps | boolean | false | Enables sequence-gap markers when events is the complete, unfiltered
stream. Leave false for retained windows, severity-filtered subsets, search
results, or any other caller-trimmed event array. | ||
onCopyVisible | (text: string) => void | Callback fired when the user clicks the "Copy visible" toolbar action. Receives the text of all currently visible events. When omitted the copy action is hidden. | |||
onFilter | (query: string) => void | Callback fired when the user updates the filter query in the toolbar's
search field. The consumer is responsible for filtering events in
response. When omitted the filter input is hidden. | |||
filterQuery | text | '' | Current filter query value, for controlled usage. Pairs with onFilter. |