Skip to main content

Timeline

Ordered lists with [status] prefixes are rendered as vertical timelines with step indicators.

Markdown syntax

1. [done] Collect requirements
2. [active] Implement API
3. [pending] Write tests
4. [error] Deploy to staging (rollback triggered)
5. [skipped] Performance benchmarking

Supported status keywords (case-insensitive):

StatusAliases
donecomplete
activecurrent
errorfail
pendingwait
skippedskip

Block ID (bid)

When a message contains multiple timelines, add a <!-- bid:xxx --> comment before each list to assign a unique identifier:

<!-- bid:build -->
1. [pending] Build Docker image
2. [pending] Run test suite
3. [pending] Push to registry

<!-- bid:deploy -->
1. [pending] Deploy to staging
2. [pending] Run smoke tests
3. [pending] Promote to production

The comment is stripped during rendering — it only serves as metadata.

Programmatic status updates

Use updateTimeline() on <i-chat> (same method as the inner list) to change a step’s status after the message has been rendered:

// Single timeline (targets the first timeline in the message)
chatEl.updateTimeline(messageId, step, status);

// Multiple timelines — use bid to target the right one
chatEl.updateTimeline(messageId, 0, 'done', 'build');
chatEl.updateTimeline(messageId, 1, 'active', 'deploy');
ParameterTypeDescription
messageIdstringThe message id that contains the timeline
stepnumberZero-based step index
statusTimelineStatus'done' | 'active' | 'error' | 'pending' | 'skipped'
bidstring?Optional block id; when omitted, targets the first timeline

SSE integration

Timelines that need dynamic status updates are typically generated by the backend orchestration logic (agent frameworks, pipelines), not by the AI model. The workflow has two phases:

Phase 1 — Define the timeline (markdown inside a text or reasoning part):

data: {"reasoning": "<!-- bid:agent -->\n1. [pending] Search documents\n2. [pending] Analyze results\n3. [pending] Generate response\n"}

Map that stream into a part — e.g. appendPart(messageId, reasoningPart(md, { id: 'plan' })) then grow it with updatePart. The <!-- bid:agent --> annotation and [pending] status markers live inside the part’s markdown, so the bid stays associated with its timeline regardless of how the stream is chunked or re-rendered.

Phase 2 — Update step statuses (structured event):

data: {"timeline": {"bid": "agent", "step": 0, "status": "done"}}
data: {"timeline": {"bid": "agent", "step": 1, "status": "active"}}

The frontend parses these events and calls on <i-chat>:

chatEl.updateTimeline(messageId, ev.timeline.step, ev.timeline.status, ev.timeline.bid);

For single-timeline messages, bid can be omitted in both phases.

Timeline CSS custom properties

PropertyDerives fromDescription
--chat-timeline-done--chat-successDone step indicator color
--chat-timeline-active--chat-primaryActive step indicator color
--chat-timeline-error--chat-errorError step indicator color
--chat-timeline-line--chat-borderConnector line color
--chat-timeline-pending-border--chat-borderPending step border color
--chat-timeline-indicator-size--chat-font-sizeIndicator circle diameter