@@ -13,7 +13,7 @@ import (
1313 "github.com/charmbracelet/crush/internal/session"
1414 "github.com/charmbracelet/crush/internal/tui/components/chat/messages"
1515 "github.com/charmbracelet/crush/internal/tui/components/core/layout"
16− "github.com/charmbracelet/crush/internal/tui/components/core/list"
16+ "github.com/charmbracelet/crush/internal/tui/exp/list"
1717 "github.com/charmbracelet/crush/internal/tui/styles"
1818 "github.com/charmbracelet/crush/internal/tui/util"
1919)
@@ -49,8 +49,8 @@ type messageListCmp struct {
4949 app *app.App
5050 width, height int
5151 session session.Session
52− listCmp list.ListModel
53− previousSelected int // Last selected item index for restoring focus
52+ listCmp list.List[list.Item]
53+ previousSelected string // Last selected item index for restoring focus
5454
5555 lastUserMessageTime int64
5656 defaultListKeyMap list.KeyMap
@@ -61,14 +61,15 @@ type messageListCmp struct {
6161func New(app *app.App) MessageListCmp {
6262 defaultListKeyMap := list.DefaultKeyMap()
6363 listCmp := list.New(
64− list.WithGapSize(1),
65− list.WithReverse(true),
64+ []list.Item{},
65+ list.WithGap(1),
66+ list.WithDirection(list.Backward),
6667 list.WithKeyMap(defaultListKeyMap),
6768 )
6869 return &messageListCmp{
6970 app: app,
7071 listCmp: listCmp,
71− previousSelected: list.NoSelection,
72+ previousSelected: "",
7273 defaultListKeyMap: defaultListKeyMap,
7374 }
7475}
@@ -89,7 +90,7 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8990 return m, nil
9091 case SessionClearedMsg:
9192 m.session = session.Session{}
92− return m, m.listCmp.SetItems([]util.Model{})
93+ return m, m.listCmp.SetItems([]list.Item{})
9394
9495 case pubsub.Event[message.Message]:
9596 cmd := m.handleMessageEvent(msg)
@@ -97,7 +98,7 @@ func (m *messageListCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
9798 default:
9899 var cmds []tea.Cmd
99100 u, cmd := m.listCmp.Update(msg)
100− m.listCmp = u.(list.ListModel)
101+ m.listCmp = u.(list.List[list.Item])
101102 cmds = append(cmds, cmd)
102103 return m, tea.Batch(cmds...)
103104 }
@@ -169,7 +170,7 @@ func (m *messageListCmp) handleChildSession(event pubsub.Event[message.Message])
169170
170171 toolCall.SetNestedToolCalls(nestedToolCalls)
171172 m.listCmp.UpdateItem(
172− toolCallInx,
173+ toolCall.ID(),
173174 toolCall,
174175 )
175176 return tea.Batch(cmds...)
@@ -233,7 +234,7 @@ func (m *messageListCmp) handleToolMessage(msg message.Message) tea.Cmd {
233234 if toolCallIndex := m.findToolCallByID(items, tr.ToolCallID); toolCallIndex != NotFound {
234235 toolCall := items[toolCallIndex].(messages.ToolCallCmp)
235236 toolCall.SetToolResult(tr)
236− m.listCmp.UpdateItem(toolCallIndex, toolCall)
237+ m.listCmp.UpdateItem(toolCall.ID(), toolCall)
237238 }
238239 }
239240 return nil
@@ -241,7 +242,7 @@ func (m *messageListCmp) handleToolMessage(msg message.Message) tea.Cmd {
241242
242243// findToolCallByID searches for a tool call with the specified ID.
243244// Returns the index if found, NotFound otherwise.
244−func (m *messageListCmp) findToolCallByID(items []util.Model, toolCallID string) int {
245+func (m *messageListCmp) findToolCallByID(items []list.Item, toolCallID string) int {
245246 // Search backwards as tool calls are more likely to be recent
246247 for i := len(items) - 1; i >= 0; i-- {
247248 if toolCall, ok := items[i].(messages.ToolCallCmp); ok && toolCall.GetToolCall().ID == toolCallID {
@@ -274,7 +275,7 @@ func (m *messageListCmp) handleUpdateAssistantMessage(msg message.Message) tea.C
274275}
275276
276277// findAssistantMessageAndToolCalls locates the assistant message and its tool calls.
277−func (m *messageListCmp) findAssistantMessageAndToolCalls(items []util.Model, messageID string) (int, map[int]messages.ToolCallCmp) {
278+func (m *messageListCmp) findAssistantMessageAndToolCalls(items []list.Item, messageID string) (int, map[int]messages.ToolCallCmp) {
278279 assistantIndex := NotFound
279280 toolCalls := make(map[int]messages.ToolCallCmp)
280281
@@ -310,7 +311,7 @@ func (m *messageListCmp) updateAssistantMessageContent(msg message.Message, assi
310311 uiMsg := items[assistantIndex].(messages.MessageCmp)
311312 uiMsg.SetMessage(msg)
312313 m.listCmp.UpdateItem(
313− assistantIndex,
314+ items[assistantIndex].ID(),
314315 uiMsg,
315316 )
316317 if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonEndTurn {
@@ -322,7 +323,8 @@ func (m *messageListCmp) updateAssistantMessageContent(msg message.Message, assi
322323 )
323324 }
324325 } else if hasToolCallsOnly {
325− m.listCmp.DeleteItem(assistantIndex)
326+ items := m.listCmp.Items()
327+ m.listCmp.DeleteItem(items[assistantIndex].ID())
326328 }
327329
328330 return cmd
@@ -349,13 +351,13 @@ func (m *messageListCmp) updateToolCalls(msg message.Message, existingToolCalls
349351// updateOrAddToolCall updates an existing tool call or adds a new one.
350352func (m *messageListCmp) updateOrAddToolCall(msg message.Message, tc message.ToolCall, existingToolCalls map[int]messages.ToolCallCmp) tea.Cmd {
351353 // Try to find existing tool call
352− for index, existingTC := range existingToolCalls {
354+ for _, existingTC := range existingToolCalls {
353355 if tc.ID == existingTC.GetToolCall().ID {
354356 existingTC.SetToolCall(tc)
355357 if msg.FinishPart() != nil && msg.FinishPart().Reason == message.FinishReasonCanceled {
356358 existingTC.SetCancelled()
357359 }
358− m.listCmp.UpdateItem(index, existingTC)
360+ m.listCmp.UpdateItem(tc.ID, existingTC)
359361 return nil
360362 }
361363 }
@@ -400,7 +402,7 @@ func (m *messageListCmp) SetSession(session session.Session) tea.Cmd {
400402 }
401403
402404 if len(sessionMessages) == 0 {
403− return m.listCmp.SetItems([]util.Model{})
405+ return m.listCmp.SetItems([]list.Item{})
404406 }
405407
406408 // Initialize with first message timestamp
@@ -427,8 +429,8 @@ func (m *messageListCmp) buildToolResultMap(messages []message.Message) map[stri
427429}
428430
429431// convertMessagesToUI converts database messages to UI components.
430−func (m *messageListCmp) convertMessagesToUI(sessionMessages []message.Message, toolResultMap map[string]message.ToolResult) []util.Model {
431− uiMessages := make([]util.Model, 0)
432+func (m *messageListCmp) convertMessagesToUI(sessionMessages []message.Message, toolResultMap map[string]message.ToolResult) []list.Item {
433+ uiMessages := make([]list.Item, 0)
432434
433435 for _, msg := range sessionMessages {
434436 switch msg.Role {
@@ -447,8 +449,8 @@ func (m *messageListCmp) convertMessagesToUI(sessionMessages []message.Message,
447449}
448450
449451// convertAssistantMessage converts an assistant message and its tool calls to UI components.
450−func (m *messageListCmp) convertAssistantMessage(msg message.Message, toolResultMap map[string]message.ToolResult) []util.Model {
451− var uiMessages []util.Model
452+func (m *messageListCmp) convertAssistantMessage(msg message.Message, toolResultMap map[string]message.ToolResult) []list.Item {
453+ var uiMessages []list.Item
452454
453455 // Add assistant message if it should be displayed
454456 if m.shouldShowAssistantMessage(msg) {