wip list sections

Kujtim Hoxha created

Change summary

internal/tui/components/core/list/list.go             | 251 +++
internal/tui/components/dialogs/commands/arguments.go |  72 +
internal/tui/components/dialogs/commands/commands.go  |  18 
internal/tui/components/dialogs/commands/item.go      |  59 +
internal/tui/components/dialogs/commands/keys.go      |   1 
internal/tui/components/dialogs/commands/loader.go    |   4 
internal/tui/components/dialogs/dialogs.go            |  15 
internal/tui/components/dialogs/keys.go               |   2 
internal/tui/tui.go                                   | 712 ------------
9 files changed, 370 insertions(+), 764 deletions(-)

Detailed changes

internal/tui/components/core/list/list.go 🔗

@@ -14,7 +14,6 @@ import (
 	"github.com/opencode-ai/opencode/internal/tui/components/anim"
 	"github.com/opencode-ai/opencode/internal/tui/layout"
 	"github.com/opencode-ai/opencode/internal/tui/styles"
-	"github.com/opencode-ai/opencode/internal/tui/theme"
 	"github.com/opencode-ai/opencode/internal/tui/util"
 	"github.com/sahilm/fuzzy"
 )
@@ -60,6 +59,13 @@ type HasMatchIndexes interface {
 	MatchIndexes([]int) // Sets the indexes of matched characters in the item's content
 }
 
+// SectionHeader interface identifies items that are section headers.
+// Section headers are rendered differently and are skipped during navigation.
+type SectionHeader interface {
+	util.Model
+	IsSectionHeader() bool // Returns true if this item is a section header
+}
+
 // renderedItem represents a cached rendered item with its position and content.
 type renderedItem struct {
 	lines  []string // The rendered lines of text for this item
@@ -539,6 +545,7 @@ func (r *reverseRenderer) renderItemLines(item util.Model) []string {
 
 // selectPreviousItem moves selection to the previous item in the list.
 // Handles focus management and ensures the selected item remains visible.
+// Skips section headers during navigation.
 func (m *model) selectPreviousItem() tea.Cmd {
 	if m.selectionState.selectedIndex <= 0 {
 		return nil
@@ -546,6 +553,17 @@ func (m *model) selectPreviousItem() tea.Cmd {
 
 	cmds := []tea.Cmd{m.blurSelected()}
 	m.selectionState.selectedIndex--
+
+	// Skip section headers
+	for m.selectionState.selectedIndex >= 0 && m.isSectionHeader(m.selectionState.selectedIndex) {
+		m.selectionState.selectedIndex--
+	}
+
+	// If we went past the beginning, stay at the first non-header item
+	if m.selectionState.selectedIndex < 0 {
+		m.selectionState.selectedIndex = m.findFirstSelectableItem()
+	}
+
 	cmds = append(cmds, m.focusSelected())
 	m.ensureSelectedItemVisible()
 	return tea.Batch(cmds...)
@@ -553,6 +571,7 @@ func (m *model) selectPreviousItem() tea.Cmd {
 
 // selectNextItem moves selection to the next item in the list.
 // Handles focus management and ensures the selected item remains visible.
+// Skips section headers during navigation.
 func (m *model) selectNextItem() tea.Cmd {
 	if m.selectionState.selectedIndex >= len(m.filteredItems)-1 || m.selectionState.selectedIndex < 0 {
 		return nil
@@ -560,11 +579,53 @@ func (m *model) selectNextItem() tea.Cmd {
 
 	cmds := []tea.Cmd{m.blurSelected()}
 	m.selectionState.selectedIndex++
+
+	// Skip section headers
+	for m.selectionState.selectedIndex < len(m.filteredItems) && m.isSectionHeader(m.selectionState.selectedIndex) {
+		m.selectionState.selectedIndex++
+	}
+
+	// If we went past the end, stay at the last non-header item
+	if m.selectionState.selectedIndex >= len(m.filteredItems) {
+		m.selectionState.selectedIndex = m.findLastSelectableItem()
+	}
+
 	cmds = append(cmds, m.focusSelected())
 	m.ensureSelectedItemVisible()
 	return tea.Batch(cmds...)
 }
 
+// isSectionHeader checks if the item at the given index is a section header.
+func (m *model) isSectionHeader(index int) bool {
+	if index < 0 || index >= len(m.filteredItems) {
+		return false
+	}
+	if header, ok := m.filteredItems[index].(SectionHeader); ok {
+		return header.IsSectionHeader()
+	}
+	return false
+}
+
+// findFirstSelectableItem finds the first item that is not a section header.
+func (m *model) findFirstSelectableItem() int {
+	for i := 0; i < len(m.filteredItems); i++ {
+		if !m.isSectionHeader(i) {
+			return i
+		}
+	}
+	return NoSelection
+}
+
+// findLastSelectableItem finds the last item that is not a section header.
+func (m *model) findLastSelectableItem() int {
+	for i := len(m.filteredItems) - 1; i >= 0; i-- {
+		if !m.isSectionHeader(i) {
+			return i
+		}
+	}
+	return NoSelection
+}
+
 // ensureSelectedItemVisible scrolls the list to make the selected item visible.
 // Uses different strategies for forward and reverse rendering modes.
 func (m *model) ensureSelectedItemVisible() {
@@ -631,25 +692,25 @@ func (m *model) ensureVisibleReverse(cachedItem renderedItem) {
 	}
 }
 
-// goToBottom switches to reverse mode and selects the last item.
+// goToBottom switches to reverse mode and selects the last selectable item.
 // Commonly used for chat-like interfaces where new content appears at the bottom.
+// Skips section headers when selecting the last item.
 func (m *model) goToBottom() tea.Cmd {
 	cmds := []tea.Cmd{m.blurSelected()}
 	m.viewState.reverse = true
-	m.selectionState.selectedIndex = len(m.filteredItems) - 1
+	m.selectionState.selectedIndex = m.findLastSelectableItem()
 	cmds = append(cmds, m.focusSelected())
 	m.ResetView()
 	return tea.Batch(cmds...)
 }
 
-// goToTop switches to forward mode and selects the first item.
+// goToTop switches to forward mode and selects the first selectable item.
 // Standard behavior for most list interfaces.
+// Skips section headers when selecting the first item.
 func (m *model) goToTop() tea.Cmd {
 	cmds := []tea.Cmd{m.blurSelected()}
 	m.viewState.reverse = false
-	if len(m.filteredItems) > 0 {
-		m.selectionState.selectedIndex = 0
-	}
+	m.selectionState.selectedIndex = m.findFirstSelectableItem()
 	cmds = append(cmds, m.focusSelected())
 	m.ResetView()
 	return tea.Batch(cmds...)
@@ -715,8 +776,12 @@ func (m *model) rerenderItem(inx int) {
 }
 
 // getItemLines converts an item to its rendered lines, including any gap spacing.
+// Handles section headers with special styling.
 func (m *model) getItemLines(item util.Model) []string {
-	itemLines := strings.Split(item.View().String(), "\n")
+	var itemLines []string
+
+	itemLines = strings.Split(item.View().String(), "\n")
+
 	if m.gapSize > 0 {
 		gap := make([]string, m.gapSize)
 		itemLines = append(itemLines, gap...)
@@ -995,6 +1060,7 @@ func (m *model) setReverse(reverse bool) {
 
 // SetItems replaces all items in the list with a new set.
 // Initializes all items, sets their sizes, and establishes initial selection.
+// Ensures the initial selection skips section headers.
 func (m *model) SetItems(items []util.Model) tea.Cmd {
 	m.allItems = items
 	m.filteredItems = items
@@ -1006,9 +1072,9 @@ func (m *model) SetItems(items []util.Model) tea.Cmd {
 
 	if len(m.filteredItems) > 0 {
 		if m.viewState.reverse {
-			m.selectionState.selectedIndex = len(m.filteredItems) - 1
+			m.selectionState.selectedIndex = m.findLastSelectableItem()
 		} else {
-			m.selectionState.selectedIndex = 0
+			m.selectionState.selectedIndex = m.findFirstSelectableItem()
 		}
 		if cmd := m.focusSelected(); cmd != nil {
 			cmds = append(cmds, cmd)
@@ -1022,18 +1088,75 @@ func (m *model) SetItems(items []util.Model) tea.Cmd {
 }
 
 func (c *model) inputStyle() lipgloss.Style {
-	t := theme.CurrentTheme()
-	return styles.BaseStyle().
-		BorderStyle(lipgloss.NormalBorder()).
-		BorderForeground(t.TextMuted()).
-		BorderBackground(t.Background()).
-		BorderBottom(true)
+	return styles.BaseStyle()
+}
+
+// section represents a group of items under a section header.
+type section struct {
+	header SectionHeader
+	items  []util.Model
+}
+
+// parseSections parses the flat item list into sections.
+func (m *model) parseSections() []section {
+	var sections []section
+	var currentSection *section
+
+	for _, item := range m.allItems {
+		if header, ok := item.(SectionHeader); ok && header.IsSectionHeader() {
+			// Start a new section
+			if currentSection != nil {
+				sections = append(sections, *currentSection)
+			}
+			currentSection = &section{
+				header: header,
+				items:  []util.Model{},
+			}
+		} else if currentSection != nil {
+			// Add item to current section
+			currentSection.items = append(currentSection.items, item)
+		} else {
+			// Item without a section header - create an implicit section
+			if len(sections) == 0 || sections[len(sections)-1].header != nil {
+				sections = append(sections, section{
+					header: nil,
+					items:  []util.Model{item},
+				})
+			} else {
+				// Add to the last implicit section
+				sections[len(sections)-1].items = append(sections[len(sections)-1].items, item)
+			}
+		}
+	}
+
+	// Don't forget the last section
+	if currentSection != nil {
+		sections = append(sections, *currentSection)
+	}
+
+	return sections
+}
+
+// flattenSections converts sections back to a flat list.
+func (m *model) flattenSections(sections []section) []util.Model {
+	var result []util.Model
+
+	for _, sect := range sections {
+		if sect.header != nil {
+			result = append(result, sect.header)
+		}
+		result = append(result, sect.items...)
+	}
+
+	return result
 }
 
 func (m *model) filter(search string) tea.Cmd {
 	var cmds []tea.Cmd
 	search = strings.TrimSpace(search)
 	search = strings.ToLower(search)
+
+	// Clear focus and match indexes from all items
 	for _, item := range m.allItems {
 		if i, ok := item.(layout.Focusable); ok {
 			cmds = append(cmds, i.Blur())
@@ -1042,34 +1165,32 @@ func (m *model) filter(search string) tea.Cmd {
 			i.MatchIndexes(make([]int, 0))
 		}
 	}
+
 	if search == "" {
-		cmds = append(cmds, m.SetItems(m.allItems)) // Reset to all items if search is empty
+		cmds = append(cmds, m.SetItems(m.allItems))
 		return tea.Batch(cmds...)
 	}
-	words := make([]string, 0, len(m.allItems))
-	for _, cmd := range m.allItems {
-		if f, ok := cmd.(HasFilterValue); ok {
-			words = append(words, strings.ToLower(f.FilterValue()))
-		} else {
-			words = append(words, strings.ToLower(""))
-		}
-	}
-	matches := fuzzy.Find(search, words)
-	sort.Sort(matches)
-	filteredItems := make([]util.Model, 0, len(matches))
-	for _, match := range matches {
-		item := m.allItems[match.Index]
-		if i, ok := item.(HasMatchIndexes); ok {
-			i.MatchIndexes(match.MatchedIndexes)
+
+	// Parse items into sections
+	sections := m.parseSections()
+	var filteredSections []section
+
+	for _, sect := range sections {
+		filteredSection := m.filterSection(sect, search)
+		if filteredSection != nil {
+			filteredSections = append(filteredSections, *filteredSection)
 		}
-		filteredItems = append(filteredItems, item)
 	}
-	m.filteredItems = filteredItems
-	if len(filteredItems) > 0 {
+
+	// Rebuild flat list from filtered sections
+	m.filteredItems = m.flattenSections(filteredSections)
+
+	// Set initial selection
+	if len(m.filteredItems) > 0 {
 		if m.viewState.reverse {
-			m.selectionState.selectedIndex = len(filteredItems) - 1
+			m.selectionState.selectedIndex = m.findLastSelectableItem()
 		} else {
-			m.selectionState.selectedIndex = 0
+			m.selectionState.selectedIndex = m.findFirstSelectableItem()
 		}
 		if cmd := m.focusSelected(); cmd != nil {
 			cmds = append(cmds, cmd)
@@ -1081,3 +1202,59 @@ func (m *model) filter(search string) tea.Cmd {
 	m.ResetView()
 	return tea.Batch(cmds...)
 }
+
+// filterSection filters items within a section and returns the section if it has matches.
+func (m *model) filterSection(sect section, search string) *section {
+	var matchedItems []util.Model
+	var hasHeaderMatch bool
+
+	// Check if section header itself matches
+	if sect.header != nil {
+		headerText := strings.ToLower(sect.header.View().String())
+		if strings.Contains(headerText, search) {
+			hasHeaderMatch = true
+			// If header matches, include all items in the section
+			matchedItems = sect.items
+		}
+	}
+
+	// If header didn't match, filter items within the section
+	if !hasHeaderMatch && len(sect.items) > 0 {
+		// Create words array for items in this section
+		words := make([]string, len(sect.items))
+		for i, item := range sect.items {
+			if f, ok := item.(HasFilterValue); ok {
+				words[i] = strings.ToLower(f.FilterValue())
+			} else {
+				words[i] = ""
+			}
+		}
+
+		// Find matches within this section
+		matches := fuzzy.Find(search, words)
+
+		// Sort matches by score but preserve relative order for equal scores
+		sort.SliceStable(matches, func(i, j int) bool {
+			return matches[i].Score > matches[j].Score
+		})
+
+		// Build matched items list
+		for _, match := range matches {
+			item := sect.items[match.Index]
+			if i, ok := item.(HasMatchIndexes); ok {
+				i.MatchIndexes(match.MatchedIndexes)
+			}
+			matchedItems = append(matchedItems, item)
+		}
+	}
+
+	// Return section only if it has matches
+	if len(matchedItems) > 0 {
+		return &section{
+			header: sect.header,
+			items:  matchedItems,
+		}
+	}
+
+	return nil
+}

internal/tui/components/dialogs/commands/arguments.go 🔗

@@ -1,5 +1,17 @@
 package commands
 
+import (
+	tea "github.com/charmbracelet/bubbletea/v2"
+	"github.com/charmbracelet/lipgloss/v2"
+	"github.com/opencode-ai/opencode/internal/tui/components/dialogs"
+	"github.com/opencode-ai/opencode/internal/tui/styles"
+	"github.com/opencode-ai/opencode/internal/tui/theme"
+)
+
+const (
+	argumentsDialogID dialogs.DialogID = "arguments"
+)
+
 // ShowArgumentsDialogMsg is a message that is sent to show the arguments dialog.
 type ShowArgumentsDialogMsg struct {
 	CommandID string
@@ -14,3 +26,63 @@ type CloseArgumentsDialogMsg struct {
 	Content   string
 	Args      map[string]string
 }
+
+// CommandArgumentsDialog represents the commands dialog.
+type CommandArgumentsDialog interface {
+	dialogs.DialogModel
+}
+
+type commandArgumentsDialogCmp struct {
+	width   int
+	wWidth  int // Width of the terminal window
+	wHeight int // Height of the terminal window
+}
+
+func NewCommandArgumentsDialog() CommandArgumentsDialog {
+	return &commandArgumentsDialogCmp{}
+}
+
+// Init implements CommandArgumentsDialog.
+func (c *commandArgumentsDialogCmp) Init() tea.Cmd {
+	return nil
+}
+
+// Update implements CommandArgumentsDialog.
+func (c *commandArgumentsDialogCmp) Update(tea.Msg) (tea.Model, tea.Cmd) {
+	return c, nil
+}
+
+// View implements CommandArgumentsDialog.
+func (c *commandArgumentsDialogCmp) View() tea.View {
+	return tea.NewView("")
+}
+
+func (c *commandArgumentsDialogCmp) moveCursor(cursor *tea.Cursor) *tea.Cursor {
+	offset := 10 + 1
+	cursor.Y += offset
+	_, col := c.Position()
+	cursor.X = cursor.X + col + 2
+	return cursor
+}
+
+func (c *commandArgumentsDialogCmp) style() lipgloss.Style {
+	t := theme.CurrentTheme()
+	return styles.BaseStyle().
+		Width(c.width).
+		Padding(1).
+		Border(lipgloss.RoundedBorder()).
+		BorderBackground(t.Background()).
+		BorderForeground(t.TextMuted())
+}
+
+func (q *commandArgumentsDialogCmp) Position() (int, int) {
+	row := 10
+	col := q.wWidth / 2
+	col -= q.width / 2
+	return row, col
+}
+
+// ID implements CommandArgumentsDialog.
+func (c *commandArgumentsDialogCmp) ID() dialogs.DialogID {
+	return argumentsDialogID
+}

internal/tui/components/dialogs/commands/commands.go 🔗

@@ -13,7 +13,7 @@ import (
 )
 
 const (
-	id dialogs.DialogID = "commands"
+	commandsDialogID dialogs.DialogID = "commands"
 
 	defaultWidth int = 60
 )
@@ -54,11 +54,17 @@ func (c *commandDialogCmp) Init() tea.Cmd {
 		return util.ReportError(err)
 	}
 
-	commands = append(commands, c.defaultCommands()...)
+	commandItems := []util.Model{}
+	if len(commands) > 0 {
+		commandItems = append(commandItems, NewItemSection("Custom"))
+		for _, cmd := range commands {
+			commandItems = append(commandItems, NewCommandItem(cmd))
+		}
+	}
 
-	commandItems := make([]util.Model, 0, len(commands))
+	commandItems = append(commandItems, NewItemSection("Default"))
 
-	for _, cmd := range commands {
+	for _, cmd := range c.defaultCommands() {
 		commandItems = append(commandItems, NewCommandItem(cmd))
 	}
 
@@ -93,7 +99,7 @@ func (c *commandDialogCmp) listWidth() int {
 }
 
 func (c *commandDialogCmp) listHeight() int {
-	listHeigh := len(c.commandList.Items()) + 2 // height based on items + 2 for the input
+	listHeigh := len(c.commandList.Items()) + 2 + 4 // height based on items + 2 for the input + 4 for the sections
 	return min(listHeigh, c.wHeight/2)
 }
 
@@ -158,5 +164,5 @@ func (c *commandDialogCmp) defaultCommands() []Command {
 }
 
 func (c *commandDialogCmp) ID() dialogs.DialogID {
-	return id
+	return commandsDialogID
 }

internal/tui/components/dialogs/commands/item.go 🔗

@@ -1,9 +1,12 @@
 package commands
 
 import (
+	"strings"
+
 	tea "github.com/charmbracelet/bubbletea/v2"
 	"github.com/charmbracelet/lipgloss/v2"
 	"github.com/charmbracelet/x/ansi"
+	"github.com/opencode-ai/opencode/internal/tui/components/core/list"
 	"github.com/opencode-ai/opencode/internal/tui/layout"
 	"github.com/opencode-ai/opencode/internal/tui/styles"
 	"github.com/opencode-ai/opencode/internal/tui/theme"
@@ -54,15 +57,15 @@ func (c *commandItem) View() tea.View {
 		titleMatchStyle = titleMatchStyle.Foreground(t.Background()).Background(t.Primary()).Bold(true)
 	}
 	var ranges []lipgloss.Range
-	truncatedTitle := ansi.Truncate(c.command.Title, c.width-2, "…")
-	text := titleStyle.Padding(0, 1).Render(truncatedTitle)
+	truncatedTitle := ansi.Truncate(c.command.Title, c.width, "…")
+	text := titleStyle.Render(truncatedTitle)
 	if len(c.matchIndexes) > 0 {
 		for _, rng := range matchedRanges(c.matchIndexes) {
 			// ansi.Cut is grapheme and ansi sequence aware, we match against a ansi.Stripped string, but we might still have graphemes.
 			// all that to say that rng is byte positions, but we need to pass it down to ansi.Cut as char positions.
 			// so we need to adjust it here:
 			start, stop := bytePosToVisibleCharPos(text, rng)
-			ranges = append(ranges, lipgloss.NewRange(start+1, stop+2, titleMatchStyle))
+			ranges = append(ranges, lipgloss.NewRange(start, stop+1, titleMatchStyle))
 		}
 		text = lipgloss.StyleRanges(text, ranges...)
 	}
@@ -148,3 +151,53 @@ func bytePosToVisibleCharPos(str string, rng [2]int) (int, int) {
 	stop = pos
 	return start, stop
 }
+
+type ItemSection interface {
+	util.Model
+	layout.Sizeable
+	list.SectionHeader
+}
+type itemSectionModel struct {
+	width int
+	title string
+}
+
+func NewItemSection(title string) ItemSection {
+	return &itemSectionModel{
+		title: title,
+	}
+}
+
+func (m *itemSectionModel) Init() tea.Cmd {
+	return nil
+}
+
+func (m *itemSectionModel) Update(tea.Msg) (tea.Model, tea.Cmd) {
+	return m, nil
+}
+
+func (m *itemSectionModel) View() tea.View {
+	t := theme.CurrentTheme()
+	title := ansi.Truncate(m.title, m.width-1, "…")
+	style := styles.BaseStyle().Padding(1, 0, 0, 0).Width(m.width).Foreground(t.TextMuted()).Bold(true)
+	if len(title) < m.width {
+		remainingWidth := m.width - lipgloss.Width(title)
+		if remainingWidth > 0 {
+			title += " " + strings.Repeat("─", remainingWidth-1)
+		}
+	}
+	return tea.NewView(style.Render(title))
+}
+
+func (m *itemSectionModel) GetSize() (int, int) {
+	return m.width, 1
+}
+
+func (m *itemSectionModel) SetSize(width int, height int) tea.Cmd {
+	m.width = width
+	return nil
+}
+
+func (m *itemSectionModel) IsSectionHeader() bool {
+	return true
+}

internal/tui/components/dialogs/commands/loader.go 🔗

@@ -162,7 +162,6 @@ func createCommandHandler(id string, content string) func(Command) tea.Cmd {
 
 		return util.CmdHandler(CommandRunCustomMsg{
 			Content: content,
-			Args:    nil,
 		})
 	}
 }
@@ -189,7 +188,7 @@ func extractArgNames(content string) []string {
 
 func ensureDir(path string) error {
 	if _, err := os.Stat(path); os.IsNotExist(err) {
-		return os.MkdirAll(path, 0755)
+		return os.MkdirAll(path, 0o755)
 	}
 	return nil
 }
@@ -200,5 +199,4 @@ func isMarkdownFile(name string) bool {
 
 type CommandRunCustomMsg struct {
 	Content string
-	Args    map[string]string
 }

internal/tui/components/dialogs/dialogs.go 🔗

@@ -23,11 +23,6 @@ type CloseCallback interface {
 	Close() tea.Cmd
 }
 
-// AbsolutePositionable is an interface for components that can set their position
-type AbsolutePositionable interface {
-	SetPosition(x, y int)
-}
-
 // OpenDialogMsg is sent to open a new dialog with specified dimensions.
 type OpenDialogMsg struct {
 	Model DialogModel
@@ -50,14 +45,14 @@ type dialogCmp struct {
 	width, height int
 	dialogs       []DialogModel
 	idMap         map[DialogID]int
-	keymap        KeyMap
+	keyMap        KeyMap
 }
 
 // NewDialogCmp creates a new dialog manager.
 func NewDialogCmp() DialogCmp {
 	return dialogCmp{
 		dialogs: []DialogModel{},
-		keymap:  DefaultKeymap(),
+		keyMap:  DefaultKeyMap(),
 		idMap:   make(map[DialogID]int),
 	}
 }
@@ -94,7 +89,7 @@ func (d dialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		}
 		return d, nil
 	case tea.KeyPressMsg:
-		if key.Matches(msg, d.keymap.Close) {
+		if key.Matches(msg, d.keyMap.Close) {
 			return d, util.CmdHandler(CloseDialogMsg{})
 		}
 	}
@@ -114,10 +109,10 @@ func (d dialogCmp) handleOpen(msg OpenDialogMsg) (tea.Model, tea.Cmd) {
 			return d, nil // Do not open a dialog if it's already the topmost one
 		}
 		if dialog.ID() == "quit" {
-			return d, nil // Do not open dialogs ontop of quit
+			return d, nil // Do not open dialogs on top of quit
 		}
 	}
-	// if the dialog is already in thel stack make it the last item
+	// if the dialog is already in the stack make it the last item
 	if _, ok := d.idMap[msg.Model.ID()]; ok {
 		existing := d.dialogs[d.idMap[msg.Model.ID()]]
 		// Reuse the model so we keep the state

internal/tui/components/dialogs/keys.go 🔗

@@ -10,7 +10,7 @@ type KeyMap struct {
 	Close key.Binding
 }
 
-func DefaultKeymap() KeyMap {
+func DefaultKeyMap() KeyMap {
 	return KeyMap{
 		Close: key.NewBinding(
 			key.WithKeys("esc"),

internal/tui/tui.go 🔗

@@ -17,8 +17,6 @@ import (
 	"github.com/opencode-ai/opencode/internal/tui/util"
 )
 
-// type startCompactSessionMsg struct{}
-
 type appModel struct {
 	width, height int
 	keyMap        KeyMap
@@ -32,40 +30,6 @@ type appModel struct {
 
 	app *app.App
 
-	// selectedSession session.Session
-	//
-	// showPermissions bool
-	// permissions     dialog.PermissionDialogCmp
-	//
-	// showHelp bool
-	// help     dialog.HelpCmp
-	//
-	// showSessionDialog bool
-	// sessionDialog     dialog.SessionDialog
-	//
-	// showCommandDialog bool
-	// commandDialog     dialog.CommandDialog
-	// commands          []dialog.Command
-	//
-	// showModelDialog bool
-	// modelDialog     dialog.ModelDialog
-	//
-	// showInitDialog bool
-	// initDialog     dialog.InitDialogCmp
-	//
-	// showFilepicker bool
-	// filepicker     dialog.FilepickerCmp
-	//
-	// showThemeDialog bool
-	// themeDialog     dialog.ThemeDialog
-	//
-	// showMultiArgumentsDialog bool
-	// multiArgumentsDialog     dialog.MultiArgumentsDialogCmp
-	//
-	// isCompacting      bool
-	// compactingMessage string
-
-	// NEW DIALOG
 	dialog dialogs.DialogCmp
 }
 
@@ -77,32 +41,6 @@ func (a appModel) Init() tea.Cmd {
 
 	cmd = a.status.Init()
 	cmds = append(cmds, cmd)
-	// cmd = a.help.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.sessionDialog.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.commandDialog.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.modelDialog.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.initDialog.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.filepicker.Init()
-	// cmds = append(cmds, cmd)
-	// cmd = a.themeDialog.Init()
-	// cmds = append(cmds, cmd)
-
-	// Check if we should show the init dialog
-	// cmds = append(cmds, func() tea.Msg {
-	// 	shouldShow, err := config.ShouldShowInitDialog()
-	// 	if err != nil {
-	// 		return util.InfoMsg{
-	// 			Type: util.InfoTypeError,
-	// 			Msg:  "Failed to check init status: " + err.Error(),
-	// 		}
-	// 	}
-	// 	return dialog.ShowInitDialogMsg{Show: shouldShow}
-	// })
 	return tea.Batch(cmds...)
 }
 
@@ -113,56 +51,13 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	switch msg := msg.(type) {
 	case tea.WindowSizeMsg:
 		return a, a.handleWindowResize(msg)
-	// TODO: remove when refactor is done
-	// msg.Height -= 1 // Make space for the status bar
-	// a.width, a.height = msg.Width, msg.Height
-	//
-	// s, _ := a.status.Update(msg)
-	// a.status = s.(core.StatusCmp)
-	// updated, cmd := a.pages[a.currentPage].Update(msg)
-	// a.pages[a.currentPage] = updated.(util.Model)
-	// cmds = append(cmds, cmd)
-	//
-	// prm, permCmd := a.permissions.Update(msg)
-	// a.permissions = prm.(dialog.PermissionDialogCmp)
-	// cmds = append(cmds, permCmd)
-	//
-	// help, helpCmd := a.help.Update(msg)
-	// a.help = help.(dialog.HelpCmp)
-	// cmds = append(cmds, helpCmd)
-	//
-	// session, sessionCmd := a.sessionDialog.Update(msg)
-	// a.sessionDialog = session.(dialog.SessionDialog)
-	// cmds = append(cmds, sessionCmd)
-	//
-	// command, commandCmd := a.commandDialog.Update(msg)
-	// a.commandDialog = command.(dialog.CommandDialog)
-	// cmds = append(cmds, commandCmd)
-	//
-	// filepicker, filepickerCmd := a.filepicker.Update(msg)
-	// a.filepicker = filepicker.(dialog.FilepickerCmp)
-	// cmds = append(cmds, filepickerCmd)
-	//
-	// a.initDialog.SetSize(msg.Width, msg.Height)
-	//
-	// if a.showMultiArgumentsDialog {
-	// 	a.multiArgumentsDialog.SetSize(msg.Width, msg.Height)
-	// 	args, argsCmd := a.multiArgumentsDialog.Update(msg)
-	// 	a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
-	// 	cmds = append(cmds, argsCmd, a.multiArgumentsDialog.Init())
-	// }
-	//
-	// dialog, cmd := a.dialog.Update(msg)
-	// a.dialog = dialog.(dialogs.DialogCmp)
-	// cmds = append(cmds, cmd)
-	//
-	// return a, tea.Batch(cmds...)
 
 	// Dialog messages
 	case dialogs.OpenDialogMsg, dialogs.CloseDialogMsg:
 		u, dialogCmd := a.dialog.Update(msg)
 		a.dialog = u.(dialogs.DialogCmp)
 		return a, dialogCmd
+	case commands.ShowArgumentsDialogMsg:
 
 	// Page change messages
 	case page.PageChangeMsg:
@@ -170,398 +65,28 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 	// Status Messages
 	case util.InfoMsg, util.ClearStatusMsg:
-		s, cmd := a.status.Update(msg)
+		s, statusCmd := a.status.Update(msg)
 		a.status = s.(core.StatusCmp)
-		cmds = append(cmds, cmd)
+		cmds = append(cmds, statusCmd)
 		return a, tea.Batch(cmds...)
+
 	// Logs
 	case pubsub.Event[logging.LogMessage]:
 		// Send to the status component
-		s, cmd := a.status.Update(msg)
+		s, statusCmd := a.status.Update(msg)
 		a.status = s.(core.StatusCmp)
-		cmds = append(cmds, cmd)
+		cmds = append(cmds, statusCmd)
 
 		// If the current page is logs, update the logs view
 		if a.currentPage == page.LogsPage {
-			updated, cmd := a.pages[a.currentPage].Update(msg)
+			updated, pageCmd := a.pages[a.currentPage].Update(msg)
 			a.pages[a.currentPage] = updated.(util.Model)
-			cmds = append(cmds, cmd)
+			cmds = append(cmds, pageCmd)
 		}
 		return a, tea.Batch(cmds...)
-
-	// // Permission
-	// case pubsub.Event[permission.PermissionRequest]:
-	// 	a.showPermissions = true
-	// 	return a, a.permissions.SetPermissions(msg.Payload)
-	// case dialog.PermissionResponseMsg:
-	// 	var cmd tea.Cmd
-	// 	switch msg.Action {
-	// 	case dialog.PermissionAllow:
-	// 		a.app.Permissions.Grant(msg.Permission)
-	// 	case dialog.PermissionAllowForSession:
-	// 		a.app.Permissions.GrantPersistant(msg.Permission)
-	// 	case dialog.PermissionDeny:
-	// 		a.app.Permissions.Deny(msg.Permission)
-	// 	}
-	// 	a.showPermissions = false
-	// 	return a, cmd
-	//
-	// 	// Theme changed
-	// case dialog.ThemeChangedMsg:
-	// 	updated, cmd := a.pages[a.currentPage].Update(msg)
-	// 	a.pages[a.currentPage] = updated.(util.Model)
-	// 	a.showThemeDialog = false
-	// 	return a, tea.Batch(cmd, util.ReportInfo("Theme changed to: "+msg.ThemeName))
-	//
-	// case dialog.CloseSessionDialogMsg:
-	// 	a.showSessionDialog = false
-	// 	return a, nil
-	//
-	// case dialog.CloseCommandDialogMsg:
-	// 	a.showCommandDialog = false
-	// 	return a, nil
-	//
-	// case startCompactSessionMsg:
-	// 	// Start compacting the current session
-	// 	a.isCompacting = true
-	// 	a.compactingMessage = "Starting summarization..."
-	//
-	// 	if a.selectedSession.ID == "" {
-	// 		a.isCompacting = false
-	// 		return a, util.ReportWarn("No active session to summarize")
-	// 	}
-	//
-	// 	// Start the summarization process
-	// 	return a, func() tea.Msg {
-	// 		ctx := context.Background()
-	// 		a.app.CoderAgent.Summarize(ctx, a.selectedSession.ID)
-	// 		return nil
-	// 	}
-	//
-	// case pubsub.Event[agent.AgentEvent]:
-	// 	payload := msg.Payload
-	// 	if payload.Error != nil {
-	// 		a.isCompacting = false
-	// 		return a, util.ReportError(payload.Error)
-	// 	}
-	//
-	// 	a.compactingMessage = payload.Progress
-	//
-	// 	if payload.Done && payload.Type == agent.AgentEventTypeSummarize {
-	// 		a.isCompacting = false
-	// 		return a, util.ReportInfo("Session summarization complete")
-	// 	} else if payload.Done && payload.Type == agent.AgentEventTypeResponse && a.selectedSession.ID != "" {
-	// 		model := a.app.CoderAgent.Model()
-	// 		contextWindow := model.ContextWindow
-	// 		tokens := a.selectedSession.CompletionTokens + a.selectedSession.PromptTokens
-	// 		if (tokens >= int64(float64(contextWindow)*0.95)) && config.Get().AutoCompact {
-	// 			return a, util.CmdHandler(startCompactSessionMsg{})
-	// 		}
-	// 	}
-	// 	// Continue listening for events
-	// 	return a, nil
-	//
-	// case dialog.CloseThemeDialogMsg:
-	// 	a.showThemeDialog = false
-	// 	return a, nil
-	//
-	// case dialog.CloseModelDialogMsg:
-	// 	a.showModelDialog = false
-	// 	return a, nil
-	//
-	// case dialog.ModelSelectedMsg:
-	// 	a.showModelDialog = false
-	//
-	// 	model, err := a.app.CoderAgent.Update(config.AgentCoder, msg.Model.ID)
-	// 	if err != nil {
-	// 		return a, util.ReportError(err)
-	// 	}
-	//
-	// 	return a, util.ReportInfo(fmt.Sprintf("Model changed to %s", model.Name))
-	//
-	// case dialog.ShowInitDialogMsg:
-	// 	a.showInitDialog = msg.Show
-	// 	return a, nil
-	//
-	// case dialog.CloseInitDialogMsg:
-	// 	a.showInitDialog = false
-	// 	if msg.Initialize {
-	// 		// Run the initialization command
-	// 		for _, cmd := range a.commands {
-	// 			if cmd.ID == "init" {
-	// 				// Mark the project as initialized
-	// 				if err := config.MarkProjectInitialized(); err != nil {
-	// 					return a, util.ReportError(err)
-	// 				}
-	// 				return a, cmd.Handler(cmd)
-	// 			}
-	// 		}
-	// 	} else {
-	// 		// Mark the project as initialized without running the command
-	// 		if err := config.MarkProjectInitialized(); err != nil {
-	// 			return a, util.ReportError(err)
-	// 		}
-	// 	}
-	// 	return a, nil
-	//
-	// case chat.SessionSelectedMsg:
-	// 	a.selectedSession = msg
-	// 	a.sessionDialog.SetSelectedSession(msg.ID)
-	//
-	// case pubsub.Event[session.Session]:
-	// 	if msg.Type == pubsub.UpdatedEvent && msg.Payload.ID == a.selectedSession.ID {
-	// 		a.selectedSession = msg.Payload
-	// 	}
-	// case dialog.SessionSelectedMsg:
-	// 	a.showSessionDialog = false
-	// 	if a.currentPage == page.ChatPage {
-	// 		return a, util.CmdHandler(chat.SessionSelectedMsg(msg.Session))
-	// 	}
-	// 	return a, nil
-	//
-	// case dialog.CommandSelectedMsg:
-	// 	a.showCommandDialog = false
-	// 	// Execute the command handler if available
-	// 	if msg.Command.Handler != nil {
-	// 		return a, msg.Command.Handler(msg.Command)
-	// 	}
-	// 	return a, util.ReportInfo("Command selected: " + msg.Command.Title)
-	//
-	// case dialog.ShowMultiArgumentsDialogMsg:
-	// 	// Show multi-arguments dialog
-	// 	a.multiArgumentsDialog = dialog.NewMultiArgumentsDialogCmp(msg.CommandID, msg.Content, msg.ArgNames)
-	// 	a.showMultiArgumentsDialog = true
-	// 	return a, a.multiArgumentsDialog.Init()
-	//
-	// case dialog.CloseMultiArgumentsDialogMsg:
-	// 	// Close multi-arguments dialog
-	// 	a.showMultiArgumentsDialog = false
-	//
-	// 	// If submitted, replace all named arguments and run the command
-	// 	if msg.Submit {
-	// 		content := msg.Content
-	//
-	// 		// Replace each named argument with its value
-	// 		for name, value := range msg.Args {
-	// 			placeholder := "$" + name
-	// 			content = strings.ReplaceAll(content, placeholder, value)
-	// 		}
-	//
-	// 		// Execute the command with arguments
-	// 		return a, util.CmdHandler(dialog.CommandRunCustomMsg{
-	// 			Content: content,
-	// 			Args:    msg.Args,
-	// 		})
-	// 	}
-	// 	return a, nil
-	//
 	case tea.KeyPressMsg:
 		return a, a.handleKeyPressMsg(msg)
-		// if a.dialog.HasDialogs() {
-		// 	u, dialogCmd := a.dialog.Update(msg)
-		// 	a.dialog = u.(dialogs.DialogCmp)
-		// 	return a, dialogCmd
-		// }
-		// // If multi-arguments dialog is open, let it handle the key press first
-		// if a.showMultiArgumentsDialog {
-		// 	args, cmd := a.multiArgumentsDialog.Update(msg)
-		// 	a.multiArgumentsDialog = args.(dialog.MultiArgumentsDialogCmp)
-		// 	return a, cmd
-		// }
-		//
-		// switch {
-		// case key.Matches(msg, keys.Quit):
-		// 	// TODO: fix this after testing
-		// 	// a.showQuit = !a.showQuit
-		// 	// if a.showHelp {
-		// 	// 	a.showHelp = false
-		// 	// }
-		// 	// if a.showSessionDialog {
-		// 	// 	a.showSessionDialog = false
-		// 	// }
-		// 	// if a.showCommandDialog {
-		// 	// 	a.showCommandDialog = false
-		// 	// }
-		// 	// if a.showFilepicker {
-		// 	// 	a.showFilepicker = false
-		// 	// 	a.filepicker.ToggleFilepicker(a.showFilepicker)
-		// 	// }
-		// 	// if a.showModelDialog {
-		// 	// 	a.showModelDialog = false
-		// 	// }
-		// 	// if a.showMultiArgumentsDialog {
-		// 	// 	a.showMultiArgumentsDialog = false
-		// 	// }
-		// 	return a, util.CmdHandler(dialogs.OpenDialogMsg{
-		// 		Model: quit.NewQuitDialog(),
-		// 	})
-		// case key.Matches(msg, keys.SwitchSession):
-		// 	if a.currentPage == page.ChatPage && !a.showPermissions && !a.showCommandDialog {
-		// 		// Load sessions and show the dialog
-		// 		sessions, err := a.app.Sessions.List(context.Background())
-		// 		if err != nil {
-		// 			return a, util.ReportError(err)
-		// 		}
-		// 		if len(sessions) == 0 {
-		// 			return a, util.ReportWarn("No sessions available")
-		// 		}
-		// 		a.sessionDialog.SetSessions(sessions)
-		// 		a.showSessionDialog = true
-		// 		return a, nil
-		// 	}
-		// 	return a, nil
-		// case key.Matches(msg, keys.Commands):
-		// if a.currentPage == page.ChatPage && !a.showPermissions && !a.showSessionDialog && !a.showThemeDialog && !a.showFilepicker {
-		// 	// Show commands dialog
-		// 	if len(a.commands) == 0 {
-		// 		return a, util.ReportWarn("No commands available")
-		// 	}
-		// 	a.commandDialog.SetCommands(a.commands)
-		// 	a.showCommandDialog = true
-		// 	return a, nil
-		// }
-		// 	return a, util.CmdHandler(dialogs.OpenDialogMsg{
-		// 		Model: commands.NewCommandDialog(),
-		// 	})
-		// case key.Matches(msg, keys.Models):
-		// 	if a.showModelDialog {
-		// 		a.showModelDialog = false
-		// 		return a, nil
-		// 	}
-		// 	if a.currentPage == page.ChatPage && !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
-		// 		a.showModelDialog = true
-		// 		return a, nil
-		// 	}
-		// 	return a, nil
-		// case key.Matches(msg, keys.SwitchTheme):
-		// 	if !a.showPermissions && !a.showSessionDialog && !a.showCommandDialog {
-		// 		// Show theme switcher dialog
-		// 		a.showThemeDialog = true
-		// 		// Theme list is dynamically loaded by the dialog component
-		// 		return a, a.themeDialog.Init()
-		// 	}
-		// 	return a, nil
-		// case key.Matches(msg, returnKey) || key.Matches(msg):
-		// 	if msg.String() == quitKey {
-		// 		if a.currentPage == page.LogsPage {
-		// 			return a, a.moveToPage(page.ChatPage)
-		// 		}
-		// 	} else if !a.filepicker.IsCWDFocused() {
-		// 		if a.showHelp {
-		// 			a.showHelp = !a.showHelp
-		// 			return a, nil
-		// 		}
-		// 		if a.showInitDialog {
-		// 			a.showInitDialog = false
-		// 			// Mark the project as initialized without running the command
-		// 			if err := config.MarkProjectInitialized(); err != nil {
-		// 				return a, util.ReportError(err)
-		// 			}
-		// 			return a, nil
-		// 		}
-		// 		if a.showFilepicker {
-		// 			a.showFilepicker = false
-		// 			a.filepicker.ToggleFilepicker(a.showFilepicker)
-		// 			return a, nil
-		// 		}
-		// 		if a.currentPage == page.LogsPage {
-		// 			return a, a.moveToPage(page.ChatPage)
-		// 		}
-		// 	}
-		// case key.Matches(msg, keys.Logs):
-		// 	return a, a.moveToPage(page.LogsPage)
-		// case key.Matches(msg, keys.Help):
-		// 	a.showHelp = !a.showHelp
-		// 	return a, nil
-		// case key.Matches(msg, helpEsc):
-		// 	if a.app.CoderAgent.IsBusy() {
-		// 		a.showHelp = !a.showHelp
-		// 		return a, nil
-		// 	}
-		// case key.Matches(msg, keys.Filepicker):
-		// 	a.showFilepicker = !a.showFilepicker
-		// 	a.filepicker.ToggleFilepicker(a.showFilepicker)
-		// 	return a, nil
-		// }
-		// default:
-		// 	u, dialogCmd := a.dialog.Update(msg)
-		// 	a.dialog = u.(dialogs.DialogCmp)
-		// 	cmds = append(cmds, dialogCmd)
-		// f, filepickerCmd := a.filepicker.Update(msg)
-		// a.filepicker = f.(dialog.FilepickerCmp)
-		// cmds = append(cmds, filepickerCmd)
-		// }
-
-		// if a.showFilepicker {
-		// 	f, filepickerCmd := a.filepicker.Update(msg)
-		// 	a.filepicker = f.(dialog.FilepickerCmp)
-		// 	cmds = append(cmds, filepickerCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showPermissions {
-		// 	d, permissionsCmd := a.permissions.Update(msg)
-		// 	a.permissions = d.(dialog.PermissionDialogCmp)
-		// 	cmds = append(cmds, permissionsCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showSessionDialog {
-		// 	d, sessionCmd := a.sessionDialog.Update(msg)
-		// 	a.sessionDialog = d.(dialog.SessionDialog)
-		// 	cmds = append(cmds, sessionCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showCommandDialog {
-		// 	d, commandCmd := a.commandDialog.Update(msg)
-		// 	a.commandDialog = d.(dialog.CommandDialog)
-		// 	cmds = append(cmds, commandCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showModelDialog {
-		// 	d, modelCmd := a.modelDialog.Update(msg)
-		// 	a.modelDialog = d.(dialog.ModelDialog)
-		// 	cmds = append(cmds, modelCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showInitDialog {
-		// 	d, initCmd := a.initDialog.Update(msg)
-		// 	a.initDialog = d.(dialog.InitDialogCmp)
-		// 	cmds = append(cmds, initCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
-		// }
-		//
-		// if a.showThemeDialog {
-		// 	d, themeCmd := a.themeDialog.Update(msg)
-		// 	a.themeDialog = d.(dialog.ThemeDialog)
-		// 	cmds = append(cmds, themeCmd)
-		// 	// Only block key messages send all other messages down
-		// 	if _, ok := msg.(tea.KeyPressMsg); ok {
-		// 		return a, tea.Batch(cmds...)
-		// 	}
 	}
-	//
 	s, _ := a.status.Update(msg)
 	a.status = s.(core.StatusCmp)
 	updated, cmd := a.pages[a.currentPage].Update(msg)
@@ -659,177 +184,6 @@ func (a *appModel) View() tea.View {
 
 	appView := lipgloss.JoinVertical(lipgloss.Top, components...)
 
-	// if a.showPermissions {
-	// 	overlay := a.permissions.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showFilepicker {
-	// 	overlay := a.filepicker.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// // Show compacting status overlay
-	// if a.isCompacting {
-	// 	t := theme.CurrentTheme()
-	// 	style := lipgloss.NewStyle().
-	// 		Border(lipgloss.RoundedBorder()).
-	// 		BorderForeground(t.BorderFocused()).
-	// 		BorderBackground(t.Background()).
-	// 		Padding(1, 2).
-	// 		Background(t.Background()).
-	// 		Foreground(t.Text())
-	//
-	// 	overlay := style.Render("Summarizing\n" + a.compactingMessage)
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showHelp {
-	// 	bindings := layout.KeyMapToSlice(a.keymap)
-	// 	if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
-	// 		bindings = append(bindings, p.BindingKeys()...)
-	// 	}
-	// 	if a.showPermissions {
-	// 		bindings = append(bindings, a.permissions.BindingKeys()...)
-	// 	}
-	// 	if a.currentPage == page.LogsPage {
-	// 		// bindings = append(bindings, logsKeyReturnKey)
-	// 	}
-	// 	if !a.app.CoderAgent.IsBusy() {
-	// 		// bindings = append(bindings, helpEsc)
-	// 	}
-	//
-	// 	a.help.SetBindings(bindings)
-	//
-	// 	overlay := a.help.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showSessionDialog {
-	// 	overlay := a.sessionDialog.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showModelDialog {
-	// 	overlay := a.modelDialog.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showCommandDialog {
-	// 	overlay := a.commandDialog.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showInitDialog {
-	// 	overlay := a.initDialog.View()
-	// 	appView = layout.PlaceOverlay(
-	// 		a.width/2-lipgloss.Width(overlay)/2,
-	// 		a.height/2-lipgloss.Height(overlay)/2,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showThemeDialog {
-	// 	overlay := a.themeDialog.View().String()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
-	//
-	// if a.showMultiArgumentsDialog {
-	// 	overlay := a.multiArgumentsDialog.View()
-	// 	row := lipgloss.Height(appView) / 2
-	// 	row -= lipgloss.Height(overlay) / 2
-	// 	col := lipgloss.Width(appView) / 2
-	// 	col -= lipgloss.Width(overlay) / 2
-	// 	appView = layout.PlaceOverlay(
-	// 		col,
-	// 		row,
-	// 		overlay,
-	// 		appView,
-	// 		true,
-	// 	)
-	// }
 	t := theme.CurrentTheme()
 	if a.dialog.HasDialogs() {
 		layers := append(
@@ -863,63 +217,13 @@ func New(app *app.App) tea.Model {
 		loadedPages: make(map[page.PageID]bool),
 		keyMap:      DefaultKeyMap(),
 
-		// help:          dialog.NewHelpCmp(),
-		// sessionDialog: dialog.NewSessionDialogCmp(),
-		// commandDialog: dialog.NewCommandDialogCmp(),
-		// modelDialog:   dialog.NewModelDialogCmp(),
-		// permissions:   dialog.NewPermissionDialogCmp(),
-		// initDialog:    dialog.NewInitDialogCmp(),
-		// themeDialog:   dialog.NewThemeDialogCmp(),
-		// commands:      []dialog.Command{},
 		pages: map[page.PageID]util.Model{
 			page.ChatPage: page.NewChatPage(app),
 			page.LogsPage: page.NewLogsPage(),
 		},
-		// filepicker: dialog.NewFilepickerCmp(app),
 
-		// New dialog
 		dialog: dialogs.NewDialogCmp(),
 	}
 
-	// 	model.RegisterCommand(dialog.Command{
-	// 		ID:          "init",
-	// 		Title:       "Initialize Project",
-	// 		Description: "Create/Update the OpenCode.md memory file",
-	// 		Handler: func(cmd dialog.Command) tea.Cmd {
-	// 			prompt := `Please analyze this codebase and create a OpenCode.md file containing:
-	// 1. Build/lint/test commands - especially for running a single test
-	// 2. Code style guidelines including imports, formatting, types, naming conventions, error handling, etc.
-	//
-	// The file you create will be given to agentic coding agents (such as yourself) that operate in this repository. Make it about 20 lines long.
-	// If there's already a opencode.md, improve it.
-	// If there are Cursor rules (in .cursor/rules/ or .cursorrules) or Copilot rules (in .github/copilot-instructions.md), make sure to include them.`
-	// 			return tea.Batch(
-	// 				util.CmdHandler(chat.SendMsg{
-	// 					Text: prompt,
-	// 				}),
-	// 			)
-	// 		},
-	// 	})
-	//
-	// 	model.RegisterCommand(dialog.Command{
-	// 		ID:          "compact",
-	// 		Title:       "Compact Session",
-	// 		Description: "Summarize the current session and create a new one with the summary",
-	// 		Handler: func(cmd dialog.Command) tea.Cmd {
-	// 			return func() tea.Msg {
-	// 				return startCompactSessionMsg{}
-	// 			}
-	// 		},
-	// 	})
-	// 	// Load custom commands
-	// 	customCommands, err := dialog.LoadCustomCommands()
-	// 	if err != nil {
-	// 		logging.Warn("Failed to load custom commands", "error", err)
-	// 	} else {
-	// 		for _, cmd := range customCommands {
-	// 			model.RegisterCommand(cmd)
-	// 		}
-	// 	}
-
 	return model
 }