Topics and naming
A chat is a set of topics — separate conversations, most recently active first. This page covers the topic lifecycle: what a topic is to the components, how one is started, and how a new topic gets its name from the host.
In the excerpts below, sidebarProps stands for the required ChatSidebar props (open, onClose, topics, messages, onSendMessage) shown in the basic usage.
The topic interface
Section titled “The topic interface”interface ChatTopic { id: ChatIdentifier; name?: string; // unset or blank while not named yet startedBy?: ChatIdentifier; // resolved through authorOf at render time started?: Date; lastActivity?: Date; // orders the list; falls back to started metadata?: Record<string, unknown>;}The list orders topics by lastActivity (falling back to started) itself — hand it topics in any order.
ChatTopicList props
Section titled “ChatTopicList props”| Prop | Type | Default | Behavior |
|---|---|---|---|
topics, onOpen | TTopic[], (topic: TTopic) => void | Required | Topics and the callback for opening one. |
onStart | () => void | — | Shows the new-topic button when supplied. |
topicActions | ChatTopicAction<TTopic>[] | — | Host actions beside topics where they are available. See Topic actions. |
status | ChatStatus | Ready | List query display state. |
authorOf, renderAvatar, buildAvatarUrl | callbacks | — | Resolve and render the topic starter. |
isTopicUnnamed | (topic: TTopic) => boolean | Blank name | Decides when to show the pending placeholder. |
labels, className | ChatTopicListLabels, string | — | Label overrides and root class name. |
Starting a topic
Section titled “Starting a topic”onStartTopic raises the intent; creating the topic is the application’s business. Answer with the new topic’s id — directly or through a promise — and the sidebar opens it, ready for the first message:
import { Guid } from '@cratis/fundamentals';
<ChatSidebar {...sidebarProps} onStartTopic={async () => { const topicId = Guid.create(); await createTopic(topicId); // your command, your storage return topicId; }}/>Omit onStartTopic to leave the new-topic affordance off entirely.
Host-side auto-naming
Section titled “Host-side auto-naming”A topic is not named by the components — it is named by the host, typically by asking an LLM for a good title once there is a first message to derive it from. The contract:
-
A topic without a usable
namecounts as unnamed. It renders a placeholder (New topic, overridable through labels) in a pending style — in the topics list and as the conversation’s title. -
When the first message is sent in an unnamed topic, the sidebar invokes
onRequestTopicName(topic, firstMessageBody)— once. It is not invoked again for later messages, and never for a topic that already has a name.“First” is decided from your data at send time: the sidebar asks when the open topic is in
topics, is unnamed, and has no messages inmessagesyet. If a second message is sent before your data delivers the first one, the sidebar asks again, and if a newly started topic has not reachedtopicsyet, it does not ask at all. Make the naming handler safe to call twice, and deliver a started topic before its first message is likely to be sent. -
The host produces a name however it likes and supplies it through the topic data. When the updated topic arrives (the next query push, the next render), the placeholder simply stops being needed.
<ChatSidebar {...sidebarProps} onRequestTopicName={async (topic, firstMessage) => { const name = await askLlmForTopicName(firstMessage); // your model, your prompt await renameTopic(topic.id, name); // your command — flows back in via the data }}/>When “unnamed” means something else
Section titled “When “unnamed” means something else”The default rule is “no name, or only whitespace”. A backend that stores a literal placeholder for unnamed topics overrides the rule — and the placeholder label — without touching anything else:
<ChatSidebar {...sidebarProps} isTopicUnnamed={topic => topic.name === 'New topic'} labels={{ unnamedTopic: 'Naming…' }}/>Owning the selection
Section titled “Owning the selection”Left alone, the sidebar keeps the open topic internally and reports changes through onTopicSelected(topicId, topic) — undefined when the person goes back to the list. To own the selection (deep links, restoring state), pass selectedTopicId: an identifier opens that topic, null shows the list.