Session list
The desktop app's sidebar as a React component that reads sessions from any ChatHost.
SessionList is the desktop app's sidebar as a React component: sessions grouped by project or date, with search, rename, favorite, archive and delete. It reads a ChatHost. The preview below runs on a fixture host. Search it, or open the menu of a row.
Mount it#
Wrap it in a ChatProvider, and pass the active id and a select handler. The sessions and the actions come from the host.
import 'zidane-ui/styles.css'
import { ChatProvider, SessionList } from 'zidane-ui/chat'
export function Sidebar({ host, activeId, onSelect }) {
return (
<ChatProvider host={host}>
<SessionList activeId={activeId} onSelect={onSelect} groupBy="project" search />
</ChatProvider>
)
}Host capabilities#
The list reads host.sessions.list. A row's menu shows Rename, Favorite, Archive and Delete only when the host implements rename, setFavorite, setArchived and remove (every member).
createFixtureHost({ sessions, transcripts }) builds a complete host over static data and keeps edits in memory. The previews and the kit's tests use it.
Grouping and sorting#
| Prop | Values | Notes |
|---|---|---|
groupBy | 'none' (default), 'project', 'date', 'state' | project: by session.group, headed by groupLabel, five rows before "Show more". date: Today, Yesterday, weekdays of this week, then months. state: running, completed, error, idle. |
sortBy | 'recency' (default), 'createdTime', 'alphabetical' | Pinned sessions (pinned: true) stay on top. Favorites (favorite: true) get their own section above the groups. |
search | boolean | Title filter above the list |
now | epoch ms | Pins the clock for relative times (screenshots, server renders) |
calendar | { timeZone?, locale? } | Pins the zone and locale of date labels, so a prerendered list hydrates without a mismatch |
Other props#
| Prop | Values | Notes |
|---|---|---|
sessions | ChatSession[] | Replaces the host's list, for a filtered or merged view |
onNewSession | () => void | Shows a "New session" button that calls it |
onNewSessionIn | (group: string) => void | Called after the host created a session from the "+" of a group header |
confirmDelete | boolean | Asks before a delete. Default true |
empty | ReactNode | Shown when the host has no sessions |
aria-label | string | Name of the list landmark. Default: the sessions label |
Per-row additions#
rowProps(session) returns the additions for one row: badges, meta (default: the relative time), sub (a second line), status, dot, titleDisplay and menuItems. The list places the menu items before Delete.
<SessionList
onSelect={open}
rowProps={s => ({
badges: s.worktree ? <span className="badge">worktree</span> : null,
menuItems: [{ id: 'fork', label: 'Fork', onSelect: () => fork(s.id) }],
})}
/>To rename "Sessions" or translate the list, override labels.
Test your host#
Your own ChatHost runs the suite every implementation in the repository runs: describeChatHostContract from zidane-ui/chat/testing/contract (vitest). It hands your factory a scripted backend (sessions, transcripts, what each prompt streams) and checks every member you list. The members you leave out must be undefined. Tests that need them are skipped. A SidebarHost runs describeSidebarHostContract from the same entry.
import { describeChatHostContract, describeSidebarHostContract } from 'zidane-ui/chat/testing/contract'
describeChatHostContract(
'my host',
backend => ({ host: makeHost(backend), dispose: () => closeHost() }),
['sessions', 'sessions.create', 'sessions.rename', 'send', 'interrupt'],
)
describeSidebarHostContract(
'my sidebar host',
backend => ({ host: makeSidebarHost(backend.environments), control: {} }),
['environment'],
)createFixtureHost and createServerHost (zidane-ui/adapters) both pass it. Their tests (ui/src/chat/headless/fixture-host.contract.test.ts, ui/src/adapters/server-host.contract.test.ts) show a full backend.
Recording a fixture#
In ui/, bun run scripts/record-fixture.ts <session-id> exports a real session as fixture JSON for createFixtureHost. It reads ~/.zidane/sessions.db by default. On Linux, the apps keep sessions in another place (find yours).
| Flag | Effect |
|---|---|
--db <path> | Another sessions database |
--out <file> | Output file |
--redact | Replaces your home directory with ~ in every string |