From 751bf380d77ed4680a814f125d4e9bf35786e73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Monta=C3=B1es=20Ojados?= Date: Mon, 19 Jan 2026 16:25:32 +0100 Subject: [PATCH] fix: Refactor Update loop to ensure packets are processed in all views --- internal/tui/model.go | 79 ++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 24 deletions(-) diff --git a/internal/tui/model.go b/internal/tui/model.go index 577beb2..32a1087 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -299,11 +299,61 @@ type ErrorMsg struct { // Update handles messages and updates the model func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var cmd tea.Cmd + var cmds []tea.Cmd - // Handle subview updates first + // GLOBAL HANDLERS: Handle signals independent of view + switch msg := msg.(type) { + case PacketMsg: + if msg.Packet != nil { + m.packetCount++ + summary := formatPacketSummary(msg.Packet, m.networkMap) + m.lastPackets = append(m.lastPackets, summary) + if len(m.lastPackets) > 50 { + m.lastPackets = m.lastPackets[1:] + } + m.callFlowStore.AddPacket(msg.Packet) + + // If we are in Call Detail view, we might need to update the viewport content dynamically! + if m.subView == SubViewCallDetail { + // Re-render subview content effectively updates the strings, but + // we need to set the content on viewport again if it changed. + // This is handled in View() normally, but viewport needs SetContent. + // Let's force a viewport update by triggering a dummy message or just re-setting it. + // Actually, View() calls renderCallDetail which calls SetContent. + // But View() is only called if Update returns a modified model. + // We modified the store, so that counts. + } + } + + // PROCESS NEXT PACKET - CRITICAL loop + if m.capturing { + cmds = append(cmds, waitForPacket(m.packetChan)) + } + + // If we processed a packet, we typically don't need to pass this msg to subviews + // UNLESS the subview reacts to it explicitly. + // For now, we return here to avoid double processing, BUT we must ensure + // UI refreshes. + return m, tea.Batch(cmds...) + + case ErrorMsg: + m.captureError = msg.Error.Error() + return m, nil + } + + // Handle standard key/window messages dependent on view + // Handle subview updates if m.subView != SubViewNone { - return m.updateSubView(msg) + newM, cmd := m.updateSubView(msg) + // We need to type assert back to Model because updateSubView follows the interface but returns concrete logic + // Actually updateSubView returns tea.Model, tea.Cmd. + // Since we are inside Model.Update, we can cast or just return. + realM, ok := newM.(Model) + if ok { + m = realM + } + cmds = append(cmds, cmd) + return m, tea.Batch(cmds...) } switch msg := msg.(type) { @@ -321,7 +371,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "4": m.currentView = ViewNetworkMap default: - cmd = m.handleViewKeys(msg) + cmds = append(cmds, m.handleViewKeys(msg)) } case tea.WindowSizeMsg: @@ -342,28 +392,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.viewport.Width = m.width / 2 // Right pane width m.viewport.Height = contentHeight - - case PacketMsg: - if msg.Packet != nil { - m.packetCount++ - summary := formatPacketSummary(msg.Packet, m.networkMap) - m.lastPackets = append(m.lastPackets, summary) - if len(m.lastPackets) > 50 { - m.lastPackets = m.lastPackets[1:] - } - m.callFlowStore.AddPacket(msg.Packet) - } - - // Continue waiting if we are still capturing - if m.capturing { - return m, waitForPacket(m.packetChan) - } - - case ErrorMsg: - m.captureError = msg.Error.Error() } - return m, cmd + return m, tea.Batch(cmds...) } func (m *Model) handleViewKeys(msg tea.KeyMsg) tea.Cmd {