Skip to content

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.

TSX
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#

PropValuesNotes
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.
searchbooleanTitle filter above the list
nowepoch msPins 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#

PropValuesNotes
sessionsChatSession[]Replaces the host's list, for a filtered or merged view
onNewSession() => voidShows a "New session" button that calls it
onNewSessionIn(group: string) => voidCalled after the host created a session from the "+" of a group header
confirmDeletebooleanAsks before a delete. Default true
emptyReactNodeShown when the host has no sessions
aria-labelstringName 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.

TSX
<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.

TypeScript
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).

FlagEffect
--db <path>Another sessions database
--out <file>Output file
--redactReplaces your home directory with ~ in every string

Reference#

zidane-ui/chat, zidane-ui/chat/headless.