Detailed changes
  
  
    
    @@ -791,6 +791,22 @@
       "created_at": "2025-10-30T02:04:26Z",
       "repoId": 987670088,
       "pullRequestNo": 1335
+    },
+    {
+      "name": "heynemann",
+      "id": 60965,
+      "comment_id": 3475594747,
+      "created_at": "2025-11-01T03:21:03Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1357
+    },
+    {
+      "name": "niklasschaeffer",
+      "id": 1948226,
+      "comment_id": 3476119118,
+      "created_at": "2025-11-01T10:06:05Z",
+      "repoId": 987670088,
+      "pullRequestNo": 1358
     }
   ]
 }
  
  
  
    
    @@ -21,6 +21,6 @@ jobs:
         with:
           commit_message: "chore: auto-update generated files"
           branch: main
-          commit_user_name: actions-user
-          commit_user_email: actions@github.com
-          commit_author: actions-user <actions@github.com>
+          commit_user_name: Charm
+          commit_user_email: 124303983+charmcli@users.noreply.github.com
+          commit_author: Charm <124303983+charmcli@users.noreply.github.com>
  
  
  
    
    @@ -3,7 +3,7 @@ module github.com/charmbracelet/crush
 go 1.25.0
 
 require (
-	charm.land/fantasy v0.1.4
+	charm.land/fantasy v0.1.5
 	github.com/JohannesKaufmann/html-to-markdown v1.6.0
 	github.com/MakeNowJust/heredoc v1.0.0
 	github.com/PuerkitoBio/goquery v1.10.3
@@ -14,7 +14,7 @@ require (
 	github.com/charlievieth/fastwalk v1.0.14
 	github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2
 	github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.6
-	github.com/charmbracelet/catwalk v0.8.1
+	github.com/charmbracelet/catwalk v0.8.2
 	github.com/charmbracelet/colorprofile v0.3.2
 	github.com/charmbracelet/fang v0.4.3
 	github.com/charmbracelet/glamour/v2 v2.0.0-20250811143442-a27abb32f018
@@ -27,21 +27,21 @@ require (
 	github.com/charmbracelet/x/exp/ordered v0.1.0
 	github.com/charmbracelet/x/exp/slice v0.0.0-20250904123553-b4e2667e5ad5
 	github.com/charmbracelet/x/powernap v0.0.0-20251015113943-25f979b54ad4
-	github.com/charmbracelet/x/term v0.2.1
+	github.com/charmbracelet/x/term v0.2.2
 	github.com/denisbrodbeck/machineid v1.0.1
 	github.com/disintegration/imageorient v0.0.0-20180920195336-8147d86e83ec
 	github.com/google/uuid v1.6.0
 	github.com/invopop/jsonschema v0.13.0
 	github.com/joho/godotenv v1.5.1
 	github.com/lucasb-eyer/go-colorful v1.3.0
-	github.com/modelcontextprotocol/go-sdk v1.0.0
+	github.com/modelcontextprotocol/go-sdk v1.1.0
 	github.com/muesli/termenv v0.16.0
 	github.com/ncruces/go-sqlite3 v0.29.1
 	github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
 	github.com/nxadm/tail v1.4.11
 	github.com/openai/openai-go/v2 v2.7.1
 	github.com/posthog/posthog-go v1.6.12
-	github.com/pressly/goose/v3 v3.25.0
+	github.com/pressly/goose/v3 v3.26.0
 	github.com/qjebbs/go-jsons v1.0.0-alpha.4
 	github.com/rivo/uniseg v0.4.7
 	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
  
  
  
    
    @@ -1,5 +1,5 @@
-charm.land/fantasy v0.1.4 h1:H/l2GfMy6Pon0GcXoj4/kuHKZ0jm//xDe2Got0eF3AU=
-charm.land/fantasy v0.1.4/go.mod h1:GT1Y8uYNmmu7OkUxWEiOyzdAf1jYopPJfpWvoDRzGiM=
+charm.land/fantasy v0.1.5 h1:7sta5yC+cSU32Kb+cNQb4b/3fyn13tYOgXsnXhdMlX0=
+charm.land/fantasy v0.1.5/go.mod h1:GT1Y8uYNmmu7OkUxWEiOyzdAf1jYopPJfpWvoDRzGiM=
 cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE=
 cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U=
 cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4=
@@ -82,8 +82,8 @@ github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2
 github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1.0.20250820203609-601216f68ee2/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.6 h1:nXNg4TmtfoQXFdF2BSSjTxFp9bSHQCILkIKK3FXMW/E=
 github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.6/go.mod h1:SUTLq+/pGQ5qntHgt0JswfVJFfgJgWDqyvyiSLVlmbo=
-github.com/charmbracelet/catwalk v0.8.1 h1:Okn6EgMSHlNCYQrSQkyAjLuLiSzDmReGLc0MPcG2F9g=
-github.com/charmbracelet/catwalk v0.8.1/go.mod h1:ReU4SdrLfe63jkEjWMdX2wlZMV3k9r11oQAmzN0m+KY=
+github.com/charmbracelet/catwalk v0.8.2 h1:J7xq/ft/ZByJCHl3JpgvxlCd59bzZPugy66XuoL4vAs=
+github.com/charmbracelet/catwalk v0.8.2/go.mod h1:ReU4SdrLfe63jkEjWMdX2wlZMV3k9r11oQAmzN0m+KY=
 github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
 github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
 github.com/charmbracelet/fang v0.4.3 h1:qXeMxnL4H6mSKBUhDefHu8NfikFbP/MBNTfqTrXvzmY=
@@ -114,8 +114,8 @@ github.com/charmbracelet/x/json v0.2.0 h1:DqB+ZGx2h+Z+1s98HOuOyli+i97wsFQIxP2ZQA
 github.com/charmbracelet/x/json v0.2.0/go.mod h1:opFIflx2YgXgi49xVUu8gEQ21teFAxyMwvOiZhIvWNM=
 github.com/charmbracelet/x/powernap v0.0.0-20251015113943-25f979b54ad4 h1:i/XilBPYK4L1Yo/mc9FPx0SyJzIsN0y4sj1MWq9Sscc=
 github.com/charmbracelet/x/powernap v0.0.0-20251015113943-25f979b54ad4/go.mod h1:cmdl5zlP5mR8TF2Y68UKc7hdGUDiSJ2+4hk0h04Hsx4=
-github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
-github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
+github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
+github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
 github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
 github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
 github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
@@ -214,8 +214,8 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
 github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
-github.com/modelcontextprotocol/go-sdk v1.0.0 h1:Z4MSjLi38bTgLrd/LjSmofqRqyBiVKRyQSJgw8q8V74=
-github.com/modelcontextprotocol/go-sdk v1.0.0/go.mod h1:nYtYQroQ2KQiM0/SbyEPUWQ6xs4B95gJjEalc9AQyOs=
+github.com/modelcontextprotocol/go-sdk v1.1.0 h1:Qjayg53dnKC4UZ+792W21e4BpwEZBzwgRW6LrjLWSwA=
+github.com/modelcontextprotocol/go-sdk v1.1.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10=
 github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
 github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
 github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
@@ -249,8 +249,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/posthog/posthog-go v1.6.12 h1:rsOBL/YdMfLJtOVjKJLgdzYmvaL3aIW6IVbAteSe+aI=
 github.com/posthog/posthog-go v1.6.12/go.mod h1:LcC1Nu4AgvV22EndTtrMXTy+7RGVC0MhChSw7Qk5XkY=
-github.com/pressly/goose/v3 v3.25.0 h1:6WeYhMWGRCzpyd89SpODFnCBCKz41KrVbRT58nVjGng=
-github.com/pressly/goose/v3 v3.25.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
+github.com/pressly/goose/v3 v3.26.0 h1:KJakav68jdH0WDvoAcj8+n61WqOIaPGgH0bJWS6jpmM=
+github.com/pressly/goose/v3 v3.26.0/go.mod h1:4hC1KrritdCxtuFsqgs1R4AU5bWtTAf+cnWvfhf2DNY=
 github.com/qjebbs/go-jsons v1.0.0-alpha.4 h1:Qsb4ohRUHQODIUAsJKdKJ/SIDbsO7oGOzsfy+h1yQZs=
 github.com/qjebbs/go-jsons v1.0.0-alpha.4/go.mod h1:wNJrtinHyC3YSf6giEh4FJN8+yZV7nXBjvmfjhBIcw4=
 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
  
  
  
    
    @@ -33,7 +33,7 @@ func getModels(t *testing.T, r *recorder.Recorder, pair modelPair) (fantasy.Lang
 	return large, small
 }
 
-func setupAgent(t *testing.T, pair modelPair) (SessionAgent, env) {
+func setupAgent(t *testing.T, pair modelPair) (SessionAgent, fakeEnv) {
 	r := newRecorder(t)
 	large, small := getModels(t, r, pair)
 	env := testEnv(t)
  
  
  
    
    @@ -30,7 +30,8 @@ import (
 	_ "github.com/joho/godotenv/autoload"
 )
 
-type env struct {
+// fakeEnv is an environment for testing.
+type fakeEnv struct {
 	workingDir  string
 	sessions    session.Service
 	messages    message.Service
@@ -100,7 +101,7 @@ func zAIBuilder(model string) builderFunc {
 	}
 }
 
-func testEnv(t *testing.T) env {
+func testEnv(t *testing.T) fakeEnv {
 	workingDir := filepath.Join("/tmp/crush-test/", t.Name())
 	os.RemoveAll(workingDir)
 
@@ -123,7 +124,7 @@ func testEnv(t *testing.T) env {
 		os.RemoveAll(workingDir)
 	})
 
-	return env{
+	return fakeEnv{
 		workingDir,
 		sessions,
 		messages,
@@ -133,7 +134,7 @@ func testEnv(t *testing.T) env {
 	}
 }
 
-func testSessionAgent(env env, large, small fantasy.LanguageModel, systemPrompt string, tools ...fantasy.AgentTool) SessionAgent {
+func testSessionAgent(env fakeEnv, large, small fantasy.LanguageModel, systemPrompt string, tools ...fantasy.AgentTool) SessionAgent {
 	largeModel := Model{
 		Model: large,
 		CatwalkCfg: catwalk.Model{
@@ -152,7 +153,7 @@ func testSessionAgent(env env, large, small fantasy.LanguageModel, systemPrompt
 	return agent
 }
 
-func coderAgent(r *recorder.Recorder, env env, large, small fantasy.LanguageModel) (SessionAgent, error) {
+func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.LanguageModel) (SessionAgent, error) {
 	fixedTime := func() time.Time {
 		t, _ := time.Parse("1/2/2006", "1/1/2025")
 		return t
  
  
  
    
    @@ -193,7 +193,7 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
 	}
 
 	switch providerCfg.Type {
-	case openai.Name:
+	case openai.Name, azure.Name:
 		_, hasReasoningEffort := mergedOptions["reasoning_effort"]
 		if !hasReasoningEffort && model.ModelCfg.ReasoningEffort != "" {
 			mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
@@ -250,16 +250,6 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
 		if err == nil {
 			options[google.Name] = parsed
 		}
-	case azure.Name:
-		_, hasReasoningEffort := mergedOptions["reasoning_effort"]
-		if !hasReasoningEffort && model.ModelCfg.ReasoningEffort != "" {
-			mergedOptions["reasoning_effort"] = model.ModelCfg.ReasoningEffort
-		}
-		// azure uses the same options as openaicompat
-		parsed, err := openaicompat.ParseOptions(mergedOptions)
-		if err == nil {
-			options[azure.Name] = parsed
-		}
 	case openaicompat.Name:
 		_, hasReasoningEffort := mergedOptions["reasoning_effort"]
 		if !hasReasoningEffort && model.ModelCfg.ReasoningEffort != "" {
@@ -566,6 +556,7 @@ func (c *coordinator) buildAzureProvider(baseURL, apiKey string, headers map[str
 	opts := []azure.Option{
 		azure.WithBaseURL(baseURL),
 		azure.WithAPIKey(apiKey),
+		azure.WithUseResponsesAPI(),
 	}
 	if c.cfg.Options.Debug {
 		httpClient := log.NewHTTPClient()
@@ -654,6 +645,9 @@ func (c *coordinator) isAnthropicThinking(model config.SelectedModel) bool {
 
 func (c *coordinator) buildProvider(providerCfg config.ProviderConfig, model config.SelectedModel) (fantasy.Provider, error) {
 	headers := maps.Clone(providerCfg.ExtraHeaders)
+	if headers == nil {
+		headers = make(map[string]string)
+	}
 
 	// handle special headers for anthropic
 	if providerCfg.Type == anthropic.Name && c.isAnthropicThinking(model) {
  
  
  
    
    @@ -736,7 +736,7 @@ func earlyState(header string, v *toolCallCmp) (string, bool) {
 		message = t.S().Base.Foreground(t.FgSubtle).Render("Canceled.")
 	case v.result.ToolCallID == "":
 		if v.permissionRequested && !v.permissionGranted {
-			message = t.S().Base.Foreground(t.FgSubtle).Render("Requesting for permission...")
+			message = t.S().Base.Foreground(t.FgSubtle).Render("Requesting permission...")
 		} else {
 			message = t.S().Base.Foreground(t.FgSubtle).Render("Waiting for tool response...")
 		}
  
  
  
    
    @@ -102,7 +102,7 @@ func NewFilterableList[T FilterableItem](items []T, opts ...filterableListOption
 	f.list = New(items, f.listOptions...).(*list[T])
 
 	f.updateKeyMaps()
-	f.items = slices.Collect(f.list.items.Seq())
+	f.items = f.list.items
 
 	if f.inputHidden {
 		return f
@@ -243,7 +243,7 @@ func (f *filterableList[T]) Filter(query string) tea.Cmd {
 		}
 	}
 
-	f.selectedItem = ""
+	f.selectedItemIdx = -1
 	if query == "" || len(f.items) == 0 {
 		return f.list.SetItems(f.items)
 	}
  
  
  
    
    @@ -2,7 +2,6 @@ package list
 
 import (
 	"regexp"
-	"slices"
 	"sort"
 	"strings"
 
@@ -183,7 +182,7 @@ func (f *filterableGroupList[T]) inputHeight() int {
 
 func (f *filterableGroupList[T]) clearItemState() []tea.Cmd {
 	var cmds []tea.Cmd
-	for _, item := range slices.Collect(f.items.Seq()) {
+	for _, item := range f.items {
 		if i, ok := any(item).(layout.Focusable); ok {
 			cmds = append(cmds, i.Blur())
 		}
@@ -253,7 +252,7 @@ func (f *filterableGroupList[T]) filterItemsInGroup(group Group[T], query string
 
 func (f *filterableGroupList[T]) Filter(query string) tea.Cmd {
 	cmds := f.clearItemState()
-	f.selectedItem = ""
+	f.selectedItemIdx = -1
 
 	if query == "" {
 		return f.groupedList.SetGroups(f.groups)
  
  
  
    
    @@ -29,7 +29,7 @@ func TestFilterableList(t *testing.T) {
 			cmd()
 		}
 
-		assert.Equal(t, items[0].ID(), l.selectedItem)
+		assert.Equal(t, 0, l.selectedItemIdx)
 		golden.RequireEqual(t, []byte(l.View()))
 	})
 }
  
  
  
    
    @@ -1,10 +1,7 @@
 package list
 
 import (
-	"slices"
-
 	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 	"github.com/charmbracelet/crush/internal/tui/util"
 )
@@ -40,9 +37,9 @@ func NewGroupedList[T Item](groups []Group[T], opts ...ListOption) GroupedList[T
 			keyMap:    DefaultKeyMap(),
 			focused:   true,
 		},
-		items:         csync.NewSlice[Item](),
-		indexMap:      csync.NewMap[string, int](),
-		renderedItems: csync.NewMap[string, renderedItem](),
+		items:         []Item{},
+		indexMap:      make(map[string]int),
+		renderedItems: make(map[string]renderedItem),
 	}
 	for _, opt := range opts {
 		opt(list.confOptions)
@@ -85,13 +82,13 @@ func (g *groupedList[T]) convertItems() {
 			items = append(items, g)
 		}
 	}
-	g.items.SetSlice(items)
+	g.items = items
 }
 
 func (g *groupedList[T]) SetGroups(groups []Group[T]) tea.Cmd {
 	g.groups = groups
 	g.convertItems()
-	return g.SetItems(slices.Collect(g.items.Seq()))
+	return g.SetItems(g.items)
 }
 
 func (g *groupedList[T]) Groups() []Group[T] {
  
  
  
    
    @@ -1,13 +1,11 @@
 package list
 
 import (
-	"slices"
 	"strings"
 	"sync"
 
 	"github.com/charmbracelet/bubbles/v2/key"
 	tea "github.com/charmbracelet/bubbletea/v2"
-	"github.com/charmbracelet/crush/internal/csync"
 	"github.com/charmbracelet/crush/internal/tui/components/anim"
 	"github.com/charmbracelet/crush/internal/tui/components/core/layout"
 	"github.com/charmbracelet/crush/internal/tui/styles"
@@ -19,6 +17,25 @@ import (
 	"github.com/rivo/uniseg"
 )
 
+const maxGapSize = 100
+
+var newlineBuffer = strings.Repeat("\n", maxGapSize)
+
+var (
+	specialCharsMap  map[string]struct{}
+	specialCharsOnce sync.Once
+)
+
+func getSpecialCharsMap() map[string]struct{} {
+	specialCharsOnce.Do(func() {
+		specialCharsMap = make(map[string]struct{}, len(styles.SelectionIgnoreIcons))
+		for _, icon := range styles.SelectionIgnoreIcons {
+			specialCharsMap[icon] = struct{}{}
+		}
+	})
+	return specialCharsMap
+}
+
 type Item interface {
 	util.Model
 	layout.Sizeable
@@ -35,7 +52,6 @@ type List[T Item] interface {
 	layout.Sizeable
 	layout.Focusable
 
-	// Just change state
 	MoveUp(int) tea.Cmd
 	MoveDown(int) tea.Cmd
 	GoToTop() tea.Cmd
@@ -69,11 +85,10 @@ const (
 
 const (
 	ItemNotFound              = -1
-	ViewportDefaultScrollSize = 2
+	ViewportDefaultScrollSize = 5
 )
 
 type renderedItem struct {
-	id     string
 	view   string
 	height int
 	start  int
@@ -81,16 +96,16 @@ type renderedItem struct {
 }
 
 type confOptions struct {
-	width, height int
-	gap           int
-	// if you are at the last item and go down it will wrap to the top
-	wrap         bool
-	keyMap       KeyMap
-	direction    direction
-	selectedItem string
-	focused      bool
-	resize       bool
-	enableMouse  bool
+	width, height   int
+	gap             int
+	wrap            bool
+	keyMap          KeyMap
+	direction       direction
+	selectedItemIdx int    // Index of selected item (-1 if none)
+	selectedItemID  string // Temporary storage for WithSelectedItem (resolved in New())
+	focused         bool
+	resize          bool
+	enableMouse     bool
 }
 
 type list[T Item] struct {
@@ -98,19 +113,24 @@ type list[T Item] struct {
 
 	offset int
 
-	indexMap *csync.Map[string, int]
-	items    *csync.Slice[T]
+	indexMap      map[string]int
+	items         []T
+	renderedItems map[string]renderedItem
 
-	renderedItems *csync.Map[string, renderedItem]
+	rendered       string
+	renderedHeight int   // cached height of rendered content
+	lineOffsets    []int // cached byte offsets for each line (for fast slicing)
 
-	renderMu sync.Mutex
-	rendered string
+	cachedView       string
+	cachedViewOffset int
+	cachedViewDirty  bool
 
-	movingByItem       bool
-	selectionStartCol  int
-	selectionStartLine int
-	selectionEndCol    int
-	selectionEndLine   int
+	movingByItem        bool
+	prevSelectedItemIdx int // Index of previously selected item (-1 if none)
+	selectionStartCol   int
+	selectionStartLine  int
+	selectionEndCol     int
+	selectionEndLine    int
 
 	selectionActive bool
 }
@@ -149,7 +169,7 @@ func WithDirectionBackward() ListOption {
 // WithSelectedItem sets the initially selected item in the list.
 func WithSelectedItem(id string) ListOption {
 	return func(l *confOptions) {
-		l.selectedItem = id
+		l.selectedItemID = id // Will be resolved to index in New()
 	}
 }
 
@@ -186,17 +206,19 @@ func WithEnableMouse() ListOption {
 func New[T Item](items []T, opts ...ListOption) List[T] {
 	list := &list[T]{
 		confOptions: &confOptions{
-			direction: DirectionForward,
-			keyMap:    DefaultKeyMap(),
-			focused:   true,
+			direction:       DirectionForward,
+			keyMap:          DefaultKeyMap(),
+			focused:         true,
+			selectedItemIdx: -1,
 		},
-		items:              csync.NewSliceFrom(items),
-		indexMap:           csync.NewMap[string, int](),
-		renderedItems:      csync.NewMap[string, renderedItem](),
-		selectionStartCol:  -1,
-		selectionStartLine: -1,
-		selectionEndLine:   -1,
-		selectionEndCol:    -1,
+		items:               items,
+		indexMap:            make(map[string]int, len(items)),
+		renderedItems:       make(map[string]renderedItem),
+		prevSelectedItemIdx: -1,
+		selectionStartCol:   -1,
+		selectionStartLine:  -1,
+		selectionEndLine:    -1,
+		selectionEndCol:     -1,
 	}
 	for _, opt := range opts {
 		opt(list.confOptions)
@@ -206,8 +228,17 @@ func New[T Item](items []T, opts ...ListOption) List[T] {
 		if i, ok := any(item).(Indexable); ok {
 			i.SetIndex(inx)
 		}
-		list.indexMap.Set(item.ID(), inx)
+		list.indexMap[item.ID()] = inx
+	}
+
+	// Resolve selectedItemID to selectedItemIdx if specified
+	if list.selectedItemID != "" {
+		if idx, ok := list.indexMap[list.selectedItemID]; ok {
+			list.selectedItemIdx = idx
+		}
+		list.selectedItemID = "" // Clear temporary storage
 	}
+
 	return list
 }
 
@@ -225,10 +256,25 @@ func (l *list[T]) Update(msg tea.Msg) (util.Model, tea.Cmd) {
 		}
 		return l, nil
 	case anim.StepMsg:
+		// Fast path: if no items, skip processing
+		if len(l.items) == 0 {
+			return l, nil
+		}
+
+		// Fast path: check if ANY items are actually spinning before processing
+		if !l.hasSpinningItems() {
+			return l, nil
+		}
+
 		var cmds []tea.Cmd
-		for _, item := range slices.Collect(l.items.Seq()) {
-			if i, ok := any(item).(HasAnim); ok && i.Spinning() {
-				updated, cmd := i.Update(msg)
+		itemsLen := len(l.items)
+		for i := range itemsLen {
+			if i >= len(l.items) {
+				continue
+			}
+			item := l.items[i]
+			if animItem, ok := any(item).(HasAnim); ok && animItem.Spinning() {
+				updated, cmd := animItem.Update(msg)
 				cmds = append(cmds, cmd)
 				if u, ok := updated.(T); ok {
 					cmds = append(cmds, l.UpdateItem(u.ID(), u))
@@ -288,8 +334,16 @@ func (l *list[T]) handleMouseWheel(msg tea.MouseWheelMsg) (util.Model, tea.Cmd)
 	return l, cmd
 }
 
-// selectionView renders the highlighted selection in the view and returns it
-// as a string. If textOnly is true, it won't render any styles.
+func (l *list[T]) hasSpinningItems() bool {
+	for i := range l.items {
+		item := l.items[i]
+		if animItem, ok := any(item).(HasAnim); ok && animItem.Spinning() {
+			return true
+		}
+	}
+	return false
+}
+
 func (l *list[T]) selectionView(view string, textOnly bool) string {
 	t := styles.CurrentTheme()
 	area := uv.Rect(0, 0, l.width, l.height)
@@ -302,10 +356,7 @@ func (l *list[T]) selectionView(view string, textOnly bool) string {
 	}
 	selArea = selArea.Canon()
 
-	specialChars := make(map[string]bool, len(styles.SelectionIgnoreIcons))
-	for _, icon := range styles.SelectionIgnoreIcons {
-		specialChars[icon] = true
-	}
+	specialChars := getSpecialCharsMap()
 
 	isNonWhitespace := func(r rune) bool {
 		return r != ' ' && r != '\t' && r != 0 && r != '\n' && r != '\r'
@@ -366,7 +417,7 @@ func (l *list[T]) selectionView(view string, textOnly bool) string {
 				}
 
 				char := rune(cellStr[0])
-				isSpecial := specialChars[cellStr]
+				_, isSpecial := specialChars[cellStr]
 
 				if (isNonWhitespace(char) && !isSpecial) || cell.Style.Bg != nil {
 					if bounds.start == -1 {
@@ -409,7 +460,10 @@ func (l *list[T]) selectionView(view string, textOnly bool) string {
 			}
 
 			cellStr := cell.String()
-			if len(cellStr) > 0 && !specialChars[cellStr] {
+			if len(cellStr) > 0 {
+				if _, isSpecial := specialChars[cellStr]; isSpecial {
+					continue
+				}
 				if textOnly {
 					// Collect selected text without styles
 					selectedText.WriteString(cell.String())
@@ -439,33 +493,40 @@ func (l *list[T]) selectionView(view string, textOnly bool) string {
 	return scr.Render()
 }
 
-// View implements List.
 func (l *list[T]) View() string {
 	if l.height <= 0 || l.width <= 0 {
 		return ""
 	}
+
+	if !l.cachedViewDirty && l.cachedViewOffset == l.offset && !l.hasSelection() && l.cachedView != "" {
+		return l.cachedView
+	}
+
 	t := styles.CurrentTheme()
-	view := l.rendered
-	lines := strings.Split(view, "\n")
 
 	start, end := l.viewPosition()
 	viewStart := max(0, start)
-	viewEnd := min(len(lines), end+1)
+	viewEnd := end
 
 	if viewStart > viewEnd {
-		viewStart = viewEnd
+		return ""
 	}
-	lines = lines[viewStart:viewEnd]
+
+	view := l.getLines(viewStart, viewEnd)
 
 	if l.resize {
-		return strings.Join(lines, "\n")
+		return view
 	}
+
 	view = t.S().Base.
 		Height(l.height).
 		Width(l.width).
-		Render(strings.Join(lines, "\n"))
+		Render(view)
 
 	if !l.hasSelection() {
+		l.cachedView = view
+		l.cachedViewOffset = l.offset
+		l.cachedViewDirty = false
 		return view
 	}
 
@@ -474,7 +535,7 @@ func (l *list[T]) View() string {
 
 func (l *list[T]) viewPosition() (int, int) {
 	start, end := 0, 0
-	renderedLines := lipgloss.Height(l.rendered) - 1
+	renderedLines := l.renderedHeight - 1
 	if l.direction == DirectionForward {
 		start = max(0, l.offset)
 		end = min(l.offset+l.height-1, renderedLines)
@@ -486,22 +547,114 @@ func (l *list[T]) viewPosition() (int, int) {
 	return start, end
 }
 
+func (l *list[T]) setRendered(rendered string) {
+	l.rendered = rendered
+	l.renderedHeight = lipgloss.Height(rendered)
+	l.cachedViewDirty = true // Mark view cache as dirty
+
+	if len(rendered) > 0 {
+		l.lineOffsets = make([]int, 0, l.renderedHeight)
+		l.lineOffsets = append(l.lineOffsets, 0)
+
+		offset := 0
+		for {
+			idx := strings.IndexByte(rendered[offset:], '\n')
+			if idx == -1 {
+				break
+			}
+			offset += idx + 1
+			l.lineOffsets = append(l.lineOffsets, offset)
+		}
+	} else {
+		l.lineOffsets = nil
+	}
+}
+
+func (l *list[T]) getLines(start, end int) string {
+	if len(l.lineOffsets) == 0 || start >= len(l.lineOffsets) {
+		return ""
+	}
+
+	if end >= len(l.lineOffsets) {
+		end = len(l.lineOffsets) - 1
+	}
+	if start > end {
+		return ""
+	}
+
+	startOffset := l.lineOffsets[start]
+	var endOffset int
+	if end+1 < len(l.lineOffsets) {
+		endOffset = l.lineOffsets[end+1] - 1
+	} else {
+		endOffset = len(l.rendered)
+	}
+
+	if startOffset >= len(l.rendered) {
+		return ""
+	}
+	endOffset = min(endOffset, len(l.rendered))
+
+	return l.rendered[startOffset:endOffset]
+}
+
+// getLine returns a single line from the rendered content using lineOffsets.
+// This avoids allocating a new string for each line like strings.Split does.
+func (l *list[T]) getLine(index int) string {
+	if len(l.lineOffsets) == 0 || index < 0 || index >= len(l.lineOffsets) {
+		return ""
+	}
+
+	startOffset := l.lineOffsets[index]
+	var endOffset int
+	if index+1 < len(l.lineOffsets) {
+		endOffset = l.lineOffsets[index+1] - 1 // -1 to exclude the newline
+	} else {
+		endOffset = len(l.rendered)
+	}
+
+	if startOffset >= len(l.rendered) {
+		return ""
+	}
+	endOffset = min(endOffset, len(l.rendered))
+
+	return l.rendered[startOffset:endOffset]
+}
+
+// lineCount returns the number of lines in the rendered content.
+func (l *list[T]) lineCount() int {
+	return len(l.lineOffsets)
+}
+
 func (l *list[T]) recalculateItemPositions() {
-	currentContentHeight := 0
-	for _, item := range slices.Collect(l.items.Seq()) {
-		rItem, ok := l.renderedItems.Get(item.ID())
+	l.recalculateItemPositionsFrom(0)
+}
+
+func (l *list[T]) recalculateItemPositionsFrom(startIdx int) {
+	var currentContentHeight int
+
+	if startIdx > 0 && startIdx <= len(l.items) {
+		prevItem := l.items[startIdx-1]
+		if rItem, ok := l.renderedItems[prevItem.ID()]; ok {
+			currentContentHeight = rItem.end + 1 + l.gap
+		}
+	}
+
+	for i := startIdx; i < len(l.items); i++ {
+		item := l.items[i]
+		rItem, ok := l.renderedItems[item.ID()]
 		if !ok {
 			continue
 		}
 		rItem.start = currentContentHeight
 		rItem.end = currentContentHeight + rItem.height - 1
-		l.renderedItems.Set(item.ID(), rItem)
+		l.renderedItems[item.ID()] = rItem
 		currentContentHeight = rItem.end + 1 + l.gap
 	}
 }
 
 func (l *list[T]) render() tea.Cmd {
-	if l.width <= 0 || l.height <= 0 || l.items.Len() == 0 {
+	if l.width <= 0 || l.height <= 0 || len(l.items) == 0 {
 		return nil
 	}
 	l.setDefaultSelected()
@@ -512,51 +665,38 @@ func (l *list[T]) render() tea.Cmd {
 	} else {
 		focusChangeCmd = l.blurSelectedItem()
 	}
-	// we are not rendering the first time
 	if l.rendered != "" {
-		// rerender everything will mostly hit cache
-		l.renderMu.Lock()
-		l.rendered, _ = l.renderIterator(0, false, "")
-		l.renderMu.Unlock()
+		rendered, _ := l.renderIterator(0, false, "")
+		l.setRendered(rendered)
 		if l.direction == DirectionBackward {
 			l.recalculateItemPositions()
 		}
-		// in the end scroll to the selected item
 		if l.focused {
 			l.scrollToSelection()
 		}
 		return focusChangeCmd
 	}
-	l.renderMu.Lock()
 	rendered, finishIndex := l.renderIterator(0, true, "")
-	l.rendered = rendered
-	l.renderMu.Unlock()
-	// recalculate for the initial items
+	l.setRendered(rendered)
 	if l.direction == DirectionBackward {
 		l.recalculateItemPositions()
 	}
-	renderCmd := func() tea.Msg {
-		l.offset = 0
-		// render the rest
 
-		l.renderMu.Lock()
-		l.rendered, _ = l.renderIterator(finishIndex, false, l.rendered)
-		l.renderMu.Unlock()
-		// needed for backwards
-		if l.direction == DirectionBackward {
-			l.recalculateItemPositions()
-		}
-		// in the end scroll to the selected item
-		if l.focused {
-			l.scrollToSelection()
-		}
-		return nil
+	l.offset = 0
+	rendered, _ = l.renderIterator(finishIndex, false, l.rendered)
+	l.setRendered(rendered)
+	if l.direction == DirectionBackward {
+		l.recalculateItemPositions()
 	}
-	return tea.Batch(focusChangeCmd, renderCmd)
+	if l.focused {
+		l.scrollToSelection()
+	}
+
+	return focusChangeCmd
 }
 
 func (l *list[T]) setDefaultSelected() {
-	if l.selectedItem == "" {
+	if l.selectedItemIdx < 0 {
 		if l.direction == DirectionForward {
 			l.selectFirstItem()
 		} else {
@@ -566,27 +706,29 @@ func (l *list[T]) setDefaultSelected() {
 }
 
 func (l *list[T]) scrollToSelection() {
-	rItem, ok := l.renderedItems.Get(l.selectedItem)
+	if l.selectedItemIdx < 0 || l.selectedItemIdx >= len(l.items) {
+		l.selectedItemIdx = -1
+		l.setDefaultSelected()
+		return
+	}
+	item := l.items[l.selectedItemIdx]
+	rItem, ok := l.renderedItems[item.ID()]
 	if !ok {
-		l.selectedItem = ""
+		l.selectedItemIdx = -1
 		l.setDefaultSelected()
 		return
 	}
 
 	start, end := l.viewPosition()
-	// item bigger or equal to the viewport do nothing
 	if rItem.start <= start && rItem.end >= end {
 		return
 	}
-	// if we are moving by item we want to move the offset so that the
-	// whole item is visible not just portions of it
 	if l.movingByItem {
 		if rItem.start >= start && rItem.end <= end {
 			return
 		}
 		defer func() { l.movingByItem = false }()
 	} else {
-		// item already in view do nothing
 		if rItem.start >= start && rItem.start <= end {
 			return
 		}
@@ -599,14 +741,13 @@ func (l *list[T]) scrollToSelection() {
 		if l.direction == DirectionForward {
 			l.offset = rItem.start
 		} else {
-			l.offset = max(0, lipgloss.Height(l.rendered)-(rItem.start+l.height))
+			l.offset = max(0, l.renderedHeight-(rItem.start+l.height))
 		}
 		return
 	}
 
-	renderedLines := lipgloss.Height(l.rendered) - 1
+	renderedLines := l.renderedHeight - 1
 
-	// If item is above the viewport, make it the first item
 	if rItem.start < start {
 		if l.direction == DirectionForward {
 			l.offset = rItem.start
@@ -614,7 +755,6 @@ func (l *list[T]) scrollToSelection() {
 			l.offset = max(0, renderedLines-rItem.start-l.height+1)
 		}
 	} else if rItem.end > end {
-		// If item is below the viewport, make it the last item
 		if l.direction == DirectionForward {
 			l.offset = max(0, rItem.end-l.height+1)
 		} else {
@@ -624,7 +764,11 @@ func (l *list[T]) scrollToSelection() {
 }
 
 func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
-	rItem, ok := l.renderedItems.Get(l.selectedItem)
+	if l.selectedItemIdx < 0 || l.selectedItemIdx >= len(l.items) {
+		return nil
+	}
+	item := l.items[l.selectedItemIdx]
+	rItem, ok := l.renderedItems[item.ID()]
 	if !ok {
 		return nil
 	}
@@ -643,64 +787,60 @@ func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
 	if itemMiddle < start {
 		// select the first item in the viewport
 		// the item is most likely an item coming after this item
-		inx, ok := l.indexMap.Get(rItem.id)
-		if !ok {
-			return nil
-		}
+		inx := l.selectedItemIdx
 		for {
 			inx = l.firstSelectableItemBelow(inx)
 			if inx == ItemNotFound {
 				return nil
 			}
-			item, ok := l.items.Get(inx)
-			if !ok {
+			if inx < 0 || inx >= len(l.items) {
 				continue
 			}
-			renderedItem, ok := l.renderedItems.Get(item.ID())
+
+			item := l.items[inx]
+			renderedItem, ok := l.renderedItems[item.ID()]
 			if !ok {
 				continue
 			}
 
 			// If the item is bigger than the viewport, select it
 			if renderedItem.start <= start && renderedItem.end >= end {
-				l.selectedItem = renderedItem.id
+				l.selectedItemIdx = inx
 				return l.render()
 			}
 			// item is in the view
 			if renderedItem.start >= start && renderedItem.start <= end {
-				l.selectedItem = renderedItem.id
+				l.selectedItemIdx = inx
 				return l.render()
 			}
 		}
 	} else if itemMiddle > end {
 		// select the first item in the viewport
 		// the item is most likely an item coming after this item
-		inx, ok := l.indexMap.Get(rItem.id)
-		if !ok {
-			return nil
-		}
+		inx := l.selectedItemIdx
 		for {
 			inx = l.firstSelectableItemAbove(inx)
 			if inx == ItemNotFound {
 				return nil
 			}
-			item, ok := l.items.Get(inx)
-			if !ok {
+			if inx < 0 || inx >= len(l.items) {
 				continue
 			}
-			renderedItem, ok := l.renderedItems.Get(item.ID())
+
+			item := l.items[inx]
+			renderedItem, ok := l.renderedItems[item.ID()]
 			if !ok {
 				continue
 			}
 
 			// If the item is bigger than the viewport, select it
 			if renderedItem.start <= start && renderedItem.end >= end {
-				l.selectedItem = renderedItem.id
+				l.selectedItemIdx = inx
 				return l.render()
 			}
 			// item is in the view
 			if renderedItem.end >= start && renderedItem.end <= end {
-				l.selectedItem = renderedItem.id
+				l.selectedItemIdx = inx
 				return l.render()
 			}
 		}
@@ -711,46 +851,42 @@ func (l *list[T]) changeSelectionWhenScrolling() tea.Cmd {
 func (l *list[T]) selectFirstItem() {
 	inx := l.firstSelectableItemBelow(-1)
 	if inx != ItemNotFound {
-		item, ok := l.items.Get(inx)
-		if ok {
-			l.selectedItem = item.ID()
-		}
+		l.selectedItemIdx = inx
 	}
 }
 
 func (l *list[T]) selectLastItem() {
-	inx := l.firstSelectableItemAbove(l.items.Len())
+	inx := l.firstSelectableItemAbove(len(l.items))
 	if inx != ItemNotFound {
-		item, ok := l.items.Get(inx)
-		if ok {
-			l.selectedItem = item.ID()
-		}
+		l.selectedItemIdx = inx
 	}
 }
 
 func (l *list[T]) firstSelectableItemAbove(inx int) int {
 	for i := inx - 1; i >= 0; i-- {
-		item, ok := l.items.Get(i)
-		if !ok {
+		if i < 0 || i >= len(l.items) {
 			continue
 		}
+
+		item := l.items[i]
 		if _, ok := any(item).(layout.Focusable); ok {
 			return i
 		}
 	}
 	if inx == 0 && l.wrap {
-		return l.firstSelectableItemAbove(l.items.Len())
+		return l.firstSelectableItemAbove(len(l.items))
 	}
 	return ItemNotFound
 }
 
 func (l *list[T]) firstSelectableItemBelow(inx int) int {
-	itemsLen := l.items.Len()
+	itemsLen := len(l.items)
 	for i := inx + 1; i < itemsLen; i++ {
-		item, ok := l.items.Get(i)
-		if !ok {
+		if i < 0 || i >= len(l.items) {
 			continue
 		}
+
+		item := l.items[i]
 		if _, ok := any(item).(layout.Focusable); ok {
 			return i
 		}
@@ -762,38 +898,52 @@ func (l *list[T]) firstSelectableItemBelow(inx int) int {
 }
 
 func (l *list[T]) focusSelectedItem() tea.Cmd {
-	if l.selectedItem == "" || !l.focused {
+	if l.selectedItemIdx < 0 || !l.focused {
 		return nil
 	}
-	var cmds []tea.Cmd
-	for _, item := range slices.Collect(l.items.Seq()) {
-		if f, ok := any(item).(layout.Focusable); ok {
-			if item.ID() == l.selectedItem && !f.IsFocused() {
-				cmds = append(cmds, f.Focus())
-				l.renderedItems.Del(item.ID())
-			} else if item.ID() != l.selectedItem && f.IsFocused() {
-				cmds = append(cmds, f.Blur())
-				l.renderedItems.Del(item.ID())
-			}
+	// Pre-allocate with expected capacity
+	cmds := make([]tea.Cmd, 0, 2)
+
+	// Blur the previously selected item if it's different
+	if l.prevSelectedItemIdx >= 0 && l.prevSelectedItemIdx != l.selectedItemIdx && l.prevSelectedItemIdx < len(l.items) {
+		prevItem := l.items[l.prevSelectedItemIdx]
+		if f, ok := any(prevItem).(layout.Focusable); ok && f.IsFocused() {
+			cmds = append(cmds, f.Blur())
+			// Mark cache as needing update, but don't delete yet
+			// This allows the render to potentially reuse it
+			delete(l.renderedItems, prevItem.ID())
 		}
 	}
+
+	// Focus the currently selected item
+	if l.selectedItemIdx >= 0 && l.selectedItemIdx < len(l.items) {
+		item := l.items[l.selectedItemIdx]
+		if f, ok := any(item).(layout.Focusable); ok && !f.IsFocused() {
+			cmds = append(cmds, f.Focus())
+			// Mark for re-render
+			delete(l.renderedItems, item.ID())
+		}
+	}
+
+	l.prevSelectedItemIdx = l.selectedItemIdx
 	return tea.Batch(cmds...)
 }
 
 func (l *list[T]) blurSelectedItem() tea.Cmd {
-	if l.selectedItem == "" || l.focused {
+	if l.selectedItemIdx < 0 || l.focused {
 		return nil
 	}
-	var cmds []tea.Cmd
-	for _, item := range slices.Collect(l.items.Seq()) {
-		if f, ok := any(item).(layout.Focusable); ok {
-			if item.ID() == l.selectedItem && f.IsFocused() {
-				cmds = append(cmds, f.Blur())
-				l.renderedItems.Del(item.ID())
-			}
+
+	// Blur the currently selected item
+	if l.selectedItemIdx >= 0 && l.selectedItemIdx < len(l.items) {
+		item := l.items[l.selectedItemIdx]
+		if f, ok := any(item).(layout.Focusable); ok && f.IsFocused() {
+			delete(l.renderedItems, item.ID())
+			return f.Blur()
 		}
 	}
-	return tea.Batch(cmds...)
+
+	return nil
 }
 
 // renderFragment holds updated rendered view fragments
@@ -806,10 +956,15 @@ type renderFragment struct {
 // returns the last index and the rendered content so far
 // we pass the rendered content around and don't use l.rendered to prevent jumping of the content
 func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string) (string, int) {
-	var fragments []renderFragment
+	// Pre-allocate fragments with expected capacity
+	itemsLen := len(l.items)
+	expectedFragments := itemsLen - startInx
+	if limitHeight && l.height > 0 {
+		expectedFragments = min(expectedFragments, l.height)
+	}
+	fragments := make([]renderFragment, 0, expectedFragments)
 
 	currentContentHeight := lipgloss.Height(rendered) - 1
-	itemsLen := l.items.Len()
 	finalIndex := itemsLen
 
 	// first pass: accumulate all fragments to render until the height limit is
@@ -826,19 +981,20 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string
 			inx = (itemsLen - 1) - i
 		}
 
-		item, ok := l.items.Get(inx)
-		if !ok {
+		if inx < 0 || inx >= len(l.items) {
 			continue
 		}
 
+		item := l.items[inx]
+
 		var rItem renderedItem
-		if cache, ok := l.renderedItems.Get(item.ID()); ok {
+		if cache, ok := l.renderedItems[item.ID()]; ok {
 			rItem = cache
 		} else {
 			rItem = l.renderItem(item)
 			rItem.start = currentContentHeight
 			rItem.end = currentContentHeight + rItem.height - 1
-			l.renderedItems.Set(item.ID(), rItem)
+			l.renderedItems[item.ID()] = rItem
 		}
 
 		gap := l.gap + 1
@@ -853,12 +1009,26 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string
 
 	// second pass: build rendered string efficiently
 	var b strings.Builder
+
+	// Pre-size the builder to reduce allocations
+	estimatedSize := len(rendered)
+	for _, f := range fragments {
+		estimatedSize += len(f.view) + f.gap
+	}
+	b.Grow(estimatedSize)
+
 	if l.direction == DirectionForward {
 		b.WriteString(rendered)
-		for _, f := range fragments {
+		for i := range fragments {
+			f := &fragments[i]
 			b.WriteString(f.view)
-			for range f.gap {
-				b.WriteByte('\n')
+			// Optimized gap writing using pre-allocated buffer
+			if f.gap > 0 {
+				if f.gap <= maxGapSize {
+					b.WriteString(newlineBuffer[:f.gap])
+				} else {
+					b.WriteString(strings.Repeat("\n", f.gap))
+				}
 			}
 		}
 
@@ -867,10 +1037,15 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string
 
 	// iterate backwards as fragments are in reversed order
 	for i := len(fragments) - 1; i >= 0; i-- {
-		f := fragments[i]
+		f := &fragments[i]
 		b.WriteString(f.view)
-		for range f.gap {
-			b.WriteByte('\n')
+		// Optimized gap writing using pre-allocated buffer
+		if f.gap > 0 {
+			if f.gap <= maxGapSize {
+				b.WriteString(newlineBuffer[:f.gap])
+			} else {
+				b.WriteString(strings.Repeat("\n", f.gap))
+			}
 		}
 	}
 	b.WriteString(rendered)
@@ -881,7 +1056,6 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string
 func (l *list[T]) renderItem(item Item) renderedItem {
 	view := item.View()
 	return renderedItem{
-		id:     item.ID(),
 		view:   view,
 		height: lipgloss.Height(view),
 	}
@@ -889,17 +1063,17 @@ func (l *list[T]) renderItem(item Item) renderedItem {
 
 // AppendItem implements List.
 func (l *list[T]) AppendItem(item T) tea.Cmd {
-	var cmds []tea.Cmd
+	// Pre-allocate with expected capacity
+	cmds := make([]tea.Cmd, 0, 4)
 	cmd := item.Init()
 	if cmd != nil {
 		cmds = append(cmds, cmd)
 	}
 
-	l.items.Append(item)
-	l.indexMap = csync.NewMap[string, int]()
-	for inx, item := range slices.Collect(l.items.Seq()) {
-		l.indexMap.Set(item.ID(), inx)
-	}
+	newIndex := len(l.items)
+	l.items = append(l.items, item)
+	l.indexMap[item.ID()] = newIndex
+
 	if l.width > 0 && l.height > 0 {
 		cmd = item.SetSize(l.width, l.height)
 		if cmd != nil {
@@ -917,13 +1091,13 @@ func (l *list[T]) AppendItem(item T) tea.Cmd {
 				cmds = append(cmds, cmd)
 			}
 		} else {
-			newItem, ok := l.renderedItems.Get(item.ID())
+			newItem, ok := l.renderedItems[item.ID()]
 			if ok {
 				newLines := newItem.height
-				if l.items.Len() > 1 {
+				if len(l.items) > 1 {
 					newLines += l.gap
 				}
-				l.offset = min(lipgloss.Height(l.rendered)-1, l.offset+newLines)
+				l.offset = min(l.renderedHeight-1, l.offset+newLines)
 			}
 		}
 	}
@@ -938,35 +1112,41 @@ func (l *list[T]) Blur() tea.Cmd {
 
 // DeleteItem implements List.
 func (l *list[T]) DeleteItem(id string) tea.Cmd {
-	inx, ok := l.indexMap.Get(id)
+	inx, ok := l.indexMap[id]
 	if !ok {
 		return nil
 	}
-	l.items.Delete(inx)
-	l.renderedItems.Del(id)
-	for inx, item := range slices.Collect(l.items.Seq()) {
-		l.indexMap.Set(item.ID(), inx)
+	l.items = append(l.items[:inx], l.items[inx+1:]...)
+	delete(l.renderedItems, id)
+	delete(l.indexMap, id)
+
+	// Only update indices for items after the deleted one
+	itemsLen := len(l.items)
+	for i := inx; i < itemsLen; i++ {
+		if i >= 0 && i < len(l.items) {
+			item := l.items[i]
+			l.indexMap[item.ID()] = i
+		}
 	}
 
-	if l.selectedItem == id {
+	// Adjust selectedItemIdx if the deleted item was selected or before it
+	if l.selectedItemIdx == inx {
+		// Deleted item was selected, select the previous item if possible
 		if inx > 0 {
-			item, ok := l.items.Get(inx - 1)
-			if ok {
-				l.selectedItem = item.ID()
-			} else {
-				l.selectedItem = ""
-			}
+			l.selectedItemIdx = inx - 1
 		} else {
-			l.selectedItem = ""
+			l.selectedItemIdx = -1
 		}
+	} else if l.selectedItemIdx > inx {
+		// Selected item is after the deleted one, shift index down
+		l.selectedItemIdx--
 	}
 	cmd := l.render()
 	if l.rendered != "" {
-		renderedHeight := lipgloss.Height(l.rendered)
-		if renderedHeight <= l.height {
+		if l.renderedHeight <= l.height {
 			l.offset = 0
 		} else {
-			maxOffset := renderedHeight - l.height
+			maxOffset := l.renderedHeight - l.height
 			if l.offset > maxOffset {
 				l.offset = maxOffset
 			}
@@ -989,7 +1169,7 @@ func (l *list[T]) GetSize() (int, int) {
 // GoToBottom implements List.
 func (l *list[T]) GoToBottom() tea.Cmd {
 	l.offset = 0
-	l.selectedItem = ""
+	l.selectedItemIdx = -1
 	l.direction = DirectionBackward
 	return l.render()
 }
@@ -997,7 +1177,7 @@ func (l *list[T]) GoToBottom() tea.Cmd {
 // GoToTop implements List.
 func (l *list[T]) GoToTop() tea.Cmd {
 	l.offset = 0
-	l.selectedItem = ""
+	l.selectedItemIdx = -1
 	l.direction = DirectionForward
 	return l.render()
 }
  
  
  
    
    @@ -28,18 +28,18 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[0].ID(), l.selectedItem)
+		assert.Equal(t, 0, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 5, l.indexMap.Len())
-		require.Equal(t, 5, l.items.Len())
-		require.Equal(t, 5, l.renderedItems.Len())
+		require.Equal(t, 5, len(l.indexMap))
+		require.Equal(t, 5, len(l.items))
+		require.Equal(t, 5, len(l.renderedItems))
 		assert.Equal(t, 5, lipgloss.Height(l.rendered))
 		assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
 		start, end := l.viewPosition()
 		assert.Equal(t, 0, start)
 		assert.Equal(t, 4, end)
 		for i := range 5 {
-			item, ok := l.renderedItems.Get(items[i].ID())
+			item, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, i, item.start)
 			assert.Equal(t, i, item.end)
@@ -58,18 +58,18 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[4].ID(), l.selectedItem)
+		assert.Equal(t, 4, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 5, l.indexMap.Len())
-		require.Equal(t, 5, l.items.Len())
-		require.Equal(t, 5, l.renderedItems.Len())
+		require.Equal(t, 5, len(l.indexMap))
+		require.Equal(t, 5, len(l.items))
+		require.Equal(t, 5, len(l.renderedItems))
 		assert.Equal(t, 5, lipgloss.Height(l.rendered))
 		assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
 		start, end := l.viewPosition()
 		assert.Equal(t, 0, start)
 		assert.Equal(t, 4, end)
 		for i := range 5 {
-			item, ok := l.renderedItems.Get(items[i].ID())
+			item, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, i, item.start)
 			assert.Equal(t, i, item.end)
@@ -89,18 +89,18 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[0].ID(), l.selectedItem)
+		assert.Equal(t, 0, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 30, l.indexMap.Len())
-		require.Equal(t, 30, l.items.Len())
-		require.Equal(t, 30, l.renderedItems.Len())
+		require.Equal(t, 30, len(l.indexMap))
+		require.Equal(t, 30, len(l.items))
+		require.Equal(t, 30, len(l.renderedItems))
 		assert.Equal(t, 30, lipgloss.Height(l.rendered))
 		assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
 		start, end := l.viewPosition()
 		assert.Equal(t, 0, start)
 		assert.Equal(t, 9, end)
 		for i := range 30 {
-			item, ok := l.renderedItems.Get(items[i].ID())
+			item, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, i, item.start)
 			assert.Equal(t, i, item.end)
@@ -119,18 +119,18 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[29].ID(), l.selectedItem)
+		assert.Equal(t, 29, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 30, l.indexMap.Len())
-		require.Equal(t, 30, l.items.Len())
-		require.Equal(t, 30, l.renderedItems.Len())
+		require.Equal(t, 30, len(l.indexMap))
+		require.Equal(t, 30, len(l.items))
+		require.Equal(t, 30, len(l.renderedItems))
 		assert.Equal(t, 30, lipgloss.Height(l.rendered))
 		assert.NotEqual(t, "\n", string(l.rendered[len(l.rendered)-1]), "should not end in newline")
 		start, end := l.viewPosition()
 		assert.Equal(t, 20, start)
 		assert.Equal(t, 29, end)
 		for i := range 30 {
-			item, ok := l.renderedItems.Get(items[i].ID())
+			item, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, i, item.start)
 			assert.Equal(t, i, item.end)
@@ -152,11 +152,11 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[0].ID(), l.selectedItem)
+		assert.Equal(t, 0, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 30, l.indexMap.Len())
-		require.Equal(t, 30, l.items.Len())
-		require.Equal(t, 30, l.renderedItems.Len())
+		require.Equal(t, 30, len(l.indexMap))
+		require.Equal(t, 30, len(l.items))
+		require.Equal(t, 30, len(l.renderedItems))
 		expectedLines := 0
 		for i := range 30 {
 			expectedLines += (i + 1) * 1
@@ -168,7 +168,7 @@ func TestList(t *testing.T) {
 		assert.Equal(t, 9, end)
 		currentPosition := 0
 		for i := range 30 {
-			rItem, ok := l.renderedItems.Get(items[i].ID())
+			rItem, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, currentPosition, rItem.start)
 			assert.Equal(t, currentPosition+i, rItem.end)
@@ -190,11 +190,11 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[29].ID(), l.selectedItem)
+		assert.Equal(t, 29, l.selectedItemIdx)
 		assert.Equal(t, 0, l.offset)
-		require.Equal(t, 30, l.indexMap.Len())
-		require.Equal(t, 30, l.items.Len())
-		require.Equal(t, 30, l.renderedItems.Len())
+		require.Equal(t, 30, len(l.indexMap))
+		require.Equal(t, 30, len(l.items))
+		require.Equal(t, 30, len(l.renderedItems))
 		expectedLines := 0
 		for i := range 30 {
 			expectedLines += (i + 1) * 1
@@ -206,7 +206,7 @@ func TestList(t *testing.T) {
 		assert.Equal(t, expectedLines-1, end)
 		currentPosition := 0
 		for i := range 30 {
-			rItem, ok := l.renderedItems.Get(items[i].ID())
+			rItem, ok := l.renderedItems[items[i].ID()]
 			require.True(t, ok)
 			assert.Equal(t, currentPosition, rItem.start)
 			assert.Equal(t, currentPosition+i, rItem.end)
@@ -229,7 +229,7 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[10].ID(), l.selectedItem)
+		assert.Equal(t, 10, l.selectedItemIdx)
 
 		golden.RequireEqual(t, []byte(l.View()))
 	})
@@ -247,7 +247,7 @@ func TestList(t *testing.T) {
 		execCmd(l, l.Init())
 
 		// should select the last item
-		assert.Equal(t, items[10].ID(), l.selectedItem)
+		assert.Equal(t, 10, l.selectedItemIdx)
 
 		golden.RequireEqual(t, []byte(l.View()))
 	})
  
  
  
    
    @@ -179,7 +179,7 @@ func (t *Theme) buildStyles() *Styles {
 			},
 			Cursor: textinput.CursorStyle{
 				Color: t.Secondary,
-				Shape: tea.CursorBar,
+				Shape: tea.CursorBlock,
 				Blink: true,
 			},
 		},
@@ -204,7 +204,7 @@ func (t *Theme) buildStyles() *Styles {
 			},
 			Cursor: textarea.CursorStyle{
 				Color: t.Secondary,
-				Shape: tea.CursorBar,
+				Shape: tea.CursorBlock,
 				Blink: true,
 			},
 		},