From 6986ab0627935e434e215445c63612cc12eb6f70 Mon Sep 17 00:00:00 2001 From: Bruno Bornsztein Date: Fri, 9 Jan 2026 11:44:33 -0500 Subject: [PATCH] Make task notifications clickable to open task detail view - Add notificationTaskID field to track which task triggered the notification - Handle mouse click events on the notification banner (row 0) - Navigate to task detail view when notification is clicked - Add "(click to view)" hint to notification text Co-Authored-By: Claude Opus 4.5 --- internal/ui/app.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/internal/ui/app.go b/internal/ui/app.go index c7dab0b3..f9545aa8 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -210,8 +210,9 @@ type AppModel struct { kanban *KanbanBoard loading bool err error - notification string // Notification banner text - notifyUntil time.Time // When to hide notification + notification string // Notification banner text + notifyUntil time.Time // When to hide notification + notificationTaskID int64 // Task ID associated with current notification (for click navigation) // Track task statuses to detect changes prevStatuses map[int64]string @@ -384,6 +385,18 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m.updateAttachments(msg) } + case tea.MouseMsg: + // Handle notification click on dashboard + if m.currentView == ViewDashboard && msg.Action == tea.MouseActionRelease && msg.Button == tea.MouseButtonLeft { + // Check if notification banner is visible and click is on it (row 0) + if m.notification != "" && time.Now().Before(m.notifyUntil) && m.notificationTaskID > 0 { + if msg.Y == 0 { + // Click on notification banner - open task detail view + return m, m.loadTask(m.notificationTaskID) + } + } + } + case tea.WindowSizeMsg: m.width = msg.Width m.height = msg.Height @@ -418,11 +431,13 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Task just became blocked - ring bell and show notification m.notification = fmt.Sprintf("⚠ Task #%d needs input: %s", t.ID, t.Title) m.notifyUntil = time.Now().Add(10 * time.Second) + m.notificationTaskID = t.ID fmt.Print("\a") // Ring terminal bell } else if t.Status == db.StatusDone && db.IsInProgress(prevStatus) { // Task completed - ring bell and show notification m.notification = fmt.Sprintf("✓ Task #%d complete: %s", t.ID, t.Title) m.notifyUntil = time.Now().Add(5 * time.Second) + m.notificationTaskID = t.ID fmt.Print("\a") // Ring terminal bell } } @@ -484,14 +499,17 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if event.Task.Status == db.StatusBlocked { m.notification = fmt.Sprintf("⚠ Task #%d needs input: %s", event.TaskID, event.Task.Title) m.notifyUntil = time.Now().Add(10 * time.Second) + m.notificationTaskID = event.TaskID fmt.Print("\a") // Ring terminal bell } else if event.Task.Status == db.StatusDone && db.IsInProgress(prevStatus) { m.notification = fmt.Sprintf("✓ Task #%d complete: %s", event.TaskID, event.Task.Title) m.notifyUntil = time.Now().Add(5 * time.Second) + m.notificationTaskID = event.TaskID fmt.Print("\a") // Ring terminal bell } else if db.IsInProgress(event.Task.Status) { m.notification = fmt.Sprintf("▶ Task #%d started: %s", event.TaskID, event.Task.Title) m.notifyUntil = time.Now().Add(3 * time.Second) + m.notificationTaskID = event.TaskID } m.prevStatuses[event.TaskID] = event.Task.Status } @@ -518,6 +536,7 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Clear expired notifications if !m.notifyUntil.IsZero() && time.Now().After(m.notifyUntil) { m.notification = "" + m.notificationTaskID = 0 } // Refresh detail view if active (for logs which may update frequently) if m.currentView == ViewDetail && m.detailView != nil { @@ -626,7 +645,12 @@ func (m *AppModel) viewDashboard() string { Foreground(lipgloss.Color("#000000")). Bold(true). Padding(0, 2) - headerParts = append(headerParts, notifyStyle.Render(m.notification)) + notifyText := m.notification + // Add click hint if notification has associated task + if m.notificationTaskID > 0 { + notifyText += " (click to view)" + } + headerParts = append(headerParts, notifyStyle.Render(notifyText)) } else { m.notification = "" // Clear expired notification }