# 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`](https://zidane.si/docs/ui-kit.md#pick-a-host). 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](https://zidane.si/docs/ui-kit.md#what-a-host-offers)).

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

```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](https://zidane.si/docs/ui-kit.md#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.

```ts
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](https://zidane.si/docs/files.md#config-and-data-files)).

| Flag | Effect |
| - | - |
| `--db <path>` | Another sessions database |
| `--out <file>` | Output file |
| `--redact` | Replaces your home directory with `~` in every string |

## Reference

[`zidane-ui/chat`](https://zidane.si/docs/reference/ui-chat.md), [`zidane-ui/chat/headless`](https://zidane.si/docs/reference/ui-chat-headless.md).
