Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions internal/ui/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
Expand All @@ -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 {
Expand Down Expand Up @@ -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
}
Expand Down