fix(tui): further guard against type accessor and map panics (#783)

Christian Rocha created

Change summary

internal/tui/tui.go | 82 +++++++++++++++++++++++++++++++++++++---------
1 file changed, 65 insertions(+), 17 deletions(-)

Detailed changes

internal/tui/tui.go 🔗

@@ -76,8 +76,13 @@ type appModel struct {
 
 // Init initializes the application model and returns initial commands.
 func (a appModel) Init() tea.Cmd {
+	item, ok := a.pages[a.currentPage]
+	if !ok {
+		return nil
+	}
+
 	var cmds []tea.Cmd
-	cmd := a.pages[a.currentPage].Init()
+	cmd := item.Init()
 	cmds = append(cmds, cmd)
 	a.loadedPages[a.currentPage] = true
 
@@ -99,7 +104,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case tea.KeyboardEnhancementsMsg:
 		for id, page := range a.pages {
 			m, pageCmd := page.Update(msg)
-			a.pages[id] = m.(util.Model)
+			if model, ok := m.(util.Model); ok {
+				a.pages[id] = model
+			}
+
 			if pageCmd != nil {
 				cmds = append(cmds, pageCmd)
 			}
@@ -114,7 +122,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	case completions.OpenCompletionsMsg, completions.FilterCompletionsMsg,
 		completions.CloseCompletionsMsg, completions.RepositionCompletionsMsg:
 		u, completionCmd := a.completions.Update(msg)
-		a.completions = u.(completions.Completions)
+		if model, ok := u.(completions.Completions); ok {
+			a.completions = model
+		}
+
 		return a, completionCmd
 
 	// Dialog messages
@@ -214,9 +225,12 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			return a, nil
 		}
 
-		// forward to page
+		// Forward to view.
 		updated, itemCmd := item.Update(msg)
-		a.pages[a.currentPage] = updated.(util.Model)
+		if model, ok := updated.(util.Model); ok {
+			a.pages[a.currentPage] = model
+		}
+
 		return a, itemCmd
 	case pubsub.Event[permission.PermissionRequest]:
 		return a, util.CmdHandler(dialogs.OpenDialogMsg{
@@ -241,7 +255,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		// Forward agent events to dialogs
 		if a.dialog.HasDialogs() && a.dialog.ActiveDialogID() == compact.CompactDialogID {
 			u, dialogCmd := a.dialog.Update(payload)
-			a.dialog = u.(dialogs.DialogCmp)
+			if model, ok := u.(dialogs.DialogCmp); ok {
+				a.dialog = model
+			}
+
 			cmds = append(cmds, dialogCmd)
 		}
 
@@ -270,7 +287,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 
 		a.isConfigured = config.HasInitialDataConfig()
 		updated, pageCmd := item.Update(msg)
-		a.pages[a.currentPage] = updated.(util.Model)
+		if model, ok := updated.(util.Model); ok {
+			a.pages[a.currentPage] = model
+		}
+
 		cmds = append(cmds, pageCmd)
 		return a, tea.Batch(cmds...)
 
@@ -289,14 +309,20 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			}
 
 			updated, pageCmd := item.Update(msg)
-			a.pages[a.currentPage] = updated.(util.Model)
+			if model, ok := updated.(util.Model); ok {
+				a.pages[a.currentPage] = model
+			}
+
 			cmds = append(cmds, pageCmd)
 		}
 		return a, tea.Batch(cmds...)
 	case tea.PasteMsg:
 		if a.dialog.HasDialogs() {
 			u, dialogCmd := a.dialog.Update(msg)
-			a.dialog = u.(dialogs.DialogCmp)
+			if model, ok := u.(dialogs.DialogCmp); ok {
+				a.dialog = model
+			}
+
 			cmds = append(cmds, dialogCmd)
 		} else {
 			item, ok := a.pages[a.currentPage]
@@ -305,7 +331,10 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 			}
 
 			updated, pageCmd := item.Update(msg)
-			a.pages[a.currentPage] = updated.(util.Model)
+			if model, ok := updated.(util.Model); ok {
+				a.pages[a.currentPage] = model
+			}
+
 			cmds = append(cmds, pageCmd)
 		}
 		return a, tea.Batch(cmds...)
@@ -317,12 +346,18 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 	if !ok {
 		return a, nil
 	}
+
 	updated, cmd := item.Update(msg)
-	a.pages[a.currentPage] = updated.(util.Model)
+	if model, ok := updated.(util.Model); ok {
+		a.pages[a.currentPage] = model
+	}
 
 	if a.dialog.HasDialogs() {
 		u, dialogCmd := a.dialog.Update(msg)
-		a.dialog = u.(dialogs.DialogCmp)
+		if model, ok := u.(dialogs.DialogCmp); ok {
+			a.dialog = model
+		}
+
 		cmds = append(cmds, dialogCmd)
 	}
 	cmds = append(cmds, cmd)
@@ -332,27 +367,38 @@ func (a *appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 // handleWindowResize processes window resize events and updates all components.
 func (a *appModel) handleWindowResize(width, height int) tea.Cmd {
 	var cmds []tea.Cmd
+
+	// TODO: clean up these magic numbers.
 	if a.showingFullHelp {
 		height -= 5
 	} else {
 		height -= 2
 	}
+
 	a.width, a.height = width, height
 	// Update status bar
 	s, cmd := a.status.Update(tea.WindowSizeMsg{Width: width, Height: height})
-	a.status = s.(status.StatusCmp)
+	if model, ok := s.(status.StatusCmp); ok {
+		a.status = model
+	}
 	cmds = append(cmds, cmd)
 
-	// Update the current page
+	// Update the current view.
 	for p, page := range a.pages {
 		updated, pageCmd := page.Update(tea.WindowSizeMsg{Width: width, Height: height})
-		a.pages[p] = updated.(util.Model)
+		if model, ok := updated.(util.Model); ok {
+			a.pages[p] = model
+		}
+
 		cmds = append(cmds, pageCmd)
 	}
 
 	// Update the dialogs
 	dialog, cmd := a.dialog.Update(tea.WindowSizeMsg{Width: width, Height: height})
-	a.dialog = dialog.(dialogs.DialogCmp)
+	if model, ok := dialog.(dialogs.DialogCmp); ok {
+		a.dialog = model
+	}
+
 	cmds = append(cmds, cmd)
 
 	return tea.Batch(cmds...)
@@ -443,7 +489,9 @@ func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
 		}
 
 		updated, cmd := item.Update(msg)
-		a.pages[a.currentPage] = updated.(util.Model)
+		if model, ok := updated.(util.Model); ok {
+			a.pages[a.currentPage] = model
+		}
 		return cmd
 	}
 }