Messages
A single message surface — source, channel, time and body — that reads the same whether it's an AI reply, an email, or (later) WhatsApp / SMS. <koala-thread> stacks them into a conversation.
Canonical
Variants
Layout
TwoSided (default) is the chat convention — your turns on the right, the other party's on the left. Linear is the Slack convention — every turn sits left-aligned, and identity is carried by the source name plus BubbleTone. Enquiry threads use Linear, because more than two parties speak.
<koala-thread> (TwoSided, default)
<koala-thread layout="Linear">
Direction
Direction only affects the TwoSided layout — in Linear every message sits left regardless, so pass the honest direction and let the layout decide.
Bubble tones
An optional BubbleTone overrides the direction-based bubble colour — the author-colour convention for Linear threads: our turns are Primary, the client's are Info, and external parties (the other side) are Neutral.
Channels
The same surface labels its source channel. Only AI is wired today; email, WhatsApp and SMS render the frame ready for when their data lands.
Contextual tag
An optional Tag rides in the meta row as a neutral outlined pill — used where a turn carries context beyond its channel, like an enquiry's audience ("Other side" / "Client only").
Status tones
The optional status badge carries a tone: Neutral, Success, Warning, Danger — for delivery state or the answering model.
Footnote
An optional Footnote renders as a hint-text line under the bubble — provenance for turns entered on someone's behalf, like an enquiry reply logged by a colleague.
Logged by Sarah Mitchell
Props
3 helpers| Helper / attribute | Notes |
|---|---|
| <koala-thread> | Conversation container. Stacks <koala-message> children vertically and hands them the layout. |
| layout | Optional ThreadLayout: TwoSided (default) or Linear. Inherited by the <koala-message> and <koala-thread-aside> children. |
| empty-text | Centered placeholder shown when the thread has no messages. |
| <koala-message message="…"> | One message. Takes a MessageView (source, channel, direction, timestamp, body, optional subject, status, tag, bubble tone, footnote). |
| <koala-thread-aside label="…" icon="…"> | Labelled, indented side conversation nested inside a thread. icon defaults to MessageSquare. |
| MessageView.BodyHtml | Rendered as raw HTML — the caller must sanitise it first. |
| MessageView.Tag | Optional contextual chip in the meta row (neutral outlined pill) — e.g. an enquiry's audience. |
| MessageView.BubbleTone | Optional MessageBubbleTone: Neutral, Primary, Info. Overrides the direction-based bubble colour — carries author identity in Linear threads. |
| MessageView.Footnote | Optional hint-text line under the bubble — e.g. "Logged by Sarah Mitchell" for turns entered on someone's behalf. |
| KoalaMessageTagHelper.RenderHtml(message, layout) | Static renderer for callers outside the Razor pipeline (client-side pending-message templates, other tag helpers) — emits identical markup. The bubble body carries a data-message-body hook so scripts can inject text into a cloned template. |
Do & don't
message.BodyHtml = sanitiser.Sanitize(email.Html)
BodyHtml in the adapter before building a MessageView — email bodies through the HTML sanitiser, AI output through Markdig then sanitise.
message.BodyHtml = email.Html
BodyHtml. It renders unescaped, so unsanitised input is an XSS hole.
layout="Linear" for Slack-style.
Thread aside
<koala-thread-aside> is a labelled, indented side conversation nested inside a thread — a rail-marked run of messages that belong to the thread but not to its main exchange, like an enquiry's client-only turns.
Empty thread
empty-text renders a centered placeholder when a thread has no messages yet.
Message input
<koala-message-input> is the composer: a text area with a send button seamlessly attached at the foot. Enter sends; Shift+Enter adds a line. Drop it inside a form whose submit posts the message.