@@ -59,6 +59,10 @@ type Chat struct {
5959
6060 // Pending single click action (delayed to detect double-click)
6161 pendingClickID int // Incremented on each click to invalidate old pending clicks
62+
63+ // follow is a flag to indicate whether the view should auto-scroll to
64+ // bottom on new messages.
65+ follow bool
6266}
6367
6468// NewChat creates a new instance of [Chat] that handles chat interactions and
@@ -93,8 +97,8 @@ func (m *Chat) Draw(scr uv.Screen, area uv.Rectangle) {
9397func (m *Chat) SetSize(width, height int) {
9498 m.list.SetSize(width, height)
9599 // Anchor to bottom if we were at the bottom.
96− if m.list.AtBottom() {
97− m.list.ScrollToBottom()
100+ if m.AtBottom() {
101+ m.ScrollToBottom()
98102 }
99103}
100104
@@ -120,7 +124,7 @@ func (m *Chat) SetMessages(msgs ...chat.MessageItem) {
120124 items[i] = msg
121125 }
122126 m.list.SetItems(items...)
123− m.list.ScrollToBottom()
127+ m.ScrollToBottom()
124128}
125129
126130// AppendMessages appends a new message item to the chat list.
@@ -239,31 +243,72 @@ func (m *Chat) Blur() {
239243 m.list.Blur()
240244}
241245
246+// AtBottom returns whether the chat list is currently scrolled to the bottom.
247+func (m *Chat) AtBottom() bool {
248+ return m.list.AtBottom()
249+}
250+
251+// Follow returns whether the chat view is in follow mode (auto-scroll to
252+// bottom on new messages).
253+func (m *Chat) Follow() bool {
254+ return m.follow
255+}
256+
257+// ScrollToBottom scrolls the chat view to the bottom.
258+func (m *Chat) ScrollToBottom() {
259+ m.list.ScrollToBottom()
260+ m.follow = true // Enable follow mode when user scrolls to bottom
261+}
262+
263+// ScrollToTop scrolls the chat view to the top.
264+func (m *Chat) ScrollToTop() {
265+ m.list.ScrollToTop()
266+ m.follow = false // Disable follow mode when user scrolls up
267+}
268+
269+// ScrollBy scrolls the chat view by the given number of line deltas.
270+func (m *Chat) ScrollBy(lines int) {
271+ m.list.ScrollBy(lines)
272+ m.follow = lines > 0 && m.AtBottom() // Disable follow mode if user scrolls up
273+}
274+
275+// ScrollToSelected scrolls the chat view to the selected item.
276+func (m *Chat) ScrollToSelected() {
277+ m.list.ScrollToSelected()
278+ m.follow = m.AtBottom() // Disable follow mode if user scrolls up
279+}
280+
281+// ScrollToIndex scrolls the chat view to the item at the given index.
282+func (m *Chat) ScrollToIndex(index int) {
283+ m.list.ScrollToIndex(index)
284+ m.follow = m.AtBottom() // Disable follow mode if user scrolls up
285+}
286+
242287// ScrollToTopAndAnimate scrolls the chat view to the top and returns a command to restart
243288// any paused animations that are now visible.
244289func (m *Chat) ScrollToTopAndAnimate() tea.Cmd {
245− m.list.ScrollToTop()
290+ m.ScrollToTop()
246291 return m.RestartPausedVisibleAnimations()
247292}
248293
249294// ScrollToBottomAndAnimate scrolls the chat view to the bottom and returns a command to
250295// restart any paused animations that are now visible.
251296func (m *Chat) ScrollToBottomAndAnimate() tea.Cmd {
252− m.list.ScrollToBottom()
297+ m.ScrollToBottom()
253298 return m.RestartPausedVisibleAnimations()
254299}
255300
256301// ScrollByAndAnimate scrolls the chat view by the given number of line deltas and returns
257302// a command to restart any paused animations that are now visible.
258303func (m *Chat) ScrollByAndAnimate(lines int) tea.Cmd {
259− m.list.ScrollBy(lines)
304+ m.ScrollBy(lines)
260305 return m.RestartPausedVisibleAnimations()
261306}
262307
263308// ScrollToSelectedAndAnimate scrolls the chat view to the selected item and returns a
264309// command to restart any paused animations that are now visible.
265310func (m *Chat) ScrollToSelectedAndAnimate() tea.Cmd {
266− m.list.ScrollToSelected()
311+ m.ScrollToSelected()
267312 return m.RestartPausedVisibleAnimations()
268313}
269314
@@ -438,10 +483,10 @@ func (m *Chat) MessageItem(id string) chat.MessageItem {
438483func (m *Chat) ToggleExpandedSelectedItem() {
439484 if expandable, ok := m.list.SelectedItem().(chat.Expandable); ok {
440485 if !expandable.ToggleExpanded() {
441− m.list.ScrollToIndex(m.list.Selected())
486+ m.ScrollToIndex(m.list.Selected())
442487 }
443− if m.list.AtBottom() {
444− m.list.ScrollToBottom()
488+ if m.AtBottom() {
489+ m.ScrollToBottom()
445490 }
446491 }
447492}
@@ -549,11 +594,11 @@ func (m *Chat) HandleDelayedClick(msg DelayedClickMsg) bool {
549594 // Toggle expansion if applicable.
550595 if expandable, ok := selectedItem.(chat.Expandable); ok {
551596 if !expandable.ToggleExpanded() {
552− m.list.ScrollToIndex(m.list.Selected())
597+ m.ScrollToIndex(m.list.Selected())
553598 }
554599 }
555− if m.list.AtBottom() {
556− m.list.ScrollToBottom()
600+ if m.AtBottom() {
601+ m.ScrollToBottom()
557602 }
558603 return handled
559604 }