Mischief

10 / Agent UI

Conversation

The scroll container a thread lives in. It follows a streaming reply to the bottom, and stops the moment the reader scrolls up to read something older.

Live previewSource

You

Why is the migration failing?

Assistant

The unique index is created before the backfill runs, so rows still holding a null email collide.

You

Can you reorder it?

Assistant

Yes. Moving the backfill above the index is enough, and the test suite passes after that.

You

What about existing rows?

Assistant

They are covered by the same backfill, so nothing else needs to change.

Installation

Copy the source into your project, or keep it behind a package.

Copy the component into your project and make it yours.

Usage

export function Thread({ messages }: { messages: Msg[] }) {
  return (
    <Conversation className="h-[32rem]">
      {messages.map((message) => (
        <Message key={message.id} role={message.role}>
          {message.content}
        </Message>
      ))}
    </Conversation>
  )
}

API

stickToBottombooleanFollows new content to the bottom. Defaults to true.
thresholdnumberHow close to the bottom still counts as following, in pixels. Defaults to 48.
showJumpButton, jumpLabelboolean, stringThe control offered once following has stopped.
onFollowChange(following: boolean) => voidRuns when the reader leaves or returns to the bottom.

Accessibility

Scrolling is never taken away from the reader. New content is followed only while they are already at the bottom, so scrolling up to read something older is not undone by the next token. Returning is an ordinary button rather than a gesture. The viewport uses contained overscroll so reaching the end does not scroll the page behind it.

Dependencies

lucide-react