Skip to content

Commit 2e3f092

Browse files
authored
Merge pull request #473 from bborn/task/1849-clear-kanban-notification-after-task-inp
Clear kanban notification after task input from detail view
2 parents 4140847 + 6dd674c commit 2e3f092

2 files changed

Lines changed: 94 additions & 1 deletion

File tree

internal/ui/app.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -832,7 +832,13 @@ func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
832832
continue
833833
}
834834
if m.tasksNeedingInput[t.ID] {
835-
continue // already detected, cleared by executorRespondedMsg or next status change
835+
// Re-validate: if task is no longer blocked, the user provided input
836+
// (e.g., from the detail view tmux pane). Also re-check permission prompts.
837+
if t.Status != db.StatusBlocked && m.latestPermissionPrompt(t.ID) == "" {
838+
delete(m.tasksNeedingInput, t.ID)
839+
delete(m.executorPrompts, t.ID)
840+
}
841+
continue
836842
}
837843
if prompt := m.latestPermissionPrompt(t.ID); prompt != "" {
838844
m.tasksNeedingInput[t.ID] = true
@@ -3388,6 +3394,15 @@ func (m *AppModel) updateRetry(msg tea.Msg) (tea.Model, tea.Cmd) {
33883394
feedback := m.retryView.GetFeedback()
33893395
attachments := m.retryView.GetAttachments()
33903396
taskID := m.retryView.task.ID
3397+
// Clear kanban notification immediately for instant UI feedback
3398+
delete(m.tasksNeedingInput, taskID)
3399+
delete(m.executorPrompts, taskID)
3400+
m.kanban.SetTasksNeedingInput(m.tasksNeedingInput)
3401+
// Clear the notification banner if it's for this task
3402+
if m.notifyTaskID == taskID {
3403+
m.notification = ""
3404+
m.notifyTaskID = 0
3405+
}
33913406
m.currentView = ViewDashboard
33923407
m.retryView = nil
33933408
if m.detailView != nil {

internal/ui/app_test.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,84 @@ func TestTaskEventDetectsPermissionPrompt(t *testing.T) {
14981498
}
14991499
}
15001500

1501+
// TestTaskSyncClearsNeedsInputWhenNoLongerBlocked verifies that the task sync
1502+
// loop clears tasksNeedingInput when a task is no longer blocked (e.g., the user
1503+
// provided input from the detail view tmux pane).
1504+
func TestTaskSyncClearsNeedsInputWhenNoLongerBlocked(t *testing.T) {
1505+
database, err := db.Open(":memory:")
1506+
if err != nil {
1507+
t.Fatalf("Failed to create test database: %v", err)
1508+
}
1509+
defer database.Close()
1510+
1511+
task := &db.Task{Title: "Test task", Status: db.StatusProcessing}
1512+
if err := database.CreateTask(task); err != nil {
1513+
t.Fatalf("Failed to create task: %v", err)
1514+
}
1515+
1516+
m := &AppModel{
1517+
width: 100,
1518+
height: 50,
1519+
currentView: ViewDashboard,
1520+
keys: DefaultKeyMap(),
1521+
tasks: []*db.Task{task},
1522+
tasksNeedingInput: map[int64]bool{task.ID: true},
1523+
executorPrompts: map[int64]string{task.ID: "What should I do?"},
1524+
kanban: NewKanbanBoard(100, 50),
1525+
prevStatuses: map[int64]string{task.ID: db.StatusBlocked},
1526+
db: database,
1527+
notification: "⚠ Task #1 needs input",
1528+
notifyTaskID: task.ID,
1529+
}
1530+
m.kanban.SetTasks(m.tasks)
1531+
1532+
// Simulate tasksLoadedMsg with the task now in processing status
1533+
msg := tasksLoadedMsg{tasks: []*db.Task{task}}
1534+
m.Update(msg)
1535+
1536+
if m.tasksNeedingInput[task.ID] {
1537+
t.Error("tasksNeedingInput should be cleared when task is no longer blocked")
1538+
}
1539+
if _, exists := m.executorPrompts[task.ID]; exists {
1540+
t.Error("executorPrompts should be cleared when task is no longer blocked")
1541+
}
1542+
}
1543+
1544+
// TestRetrySubmitClearsKanbanNotification verifies that submitting the retry
1545+
// form clears both the tasksNeedingInput flag and the notification banner.
1546+
func TestRetrySubmitClearsKanbanNotification(t *testing.T) {
1547+
task := &db.Task{ID: 42, Title: "Test task", Status: db.StatusBlocked}
1548+
1549+
m := &AppModel{
1550+
width: 100,
1551+
height: 50,
1552+
currentView: ViewRetry,
1553+
keys: DefaultKeyMap(),
1554+
tasksNeedingInput: map[int64]bool{42: true},
1555+
executorPrompts: map[int64]string{42: "What should I do?"},
1556+
kanban: NewKanbanBoard(100, 50),
1557+
prevStatuses: make(map[int64]string),
1558+
notification: "⚠ Task #42 needs input: Test task (g to jump)",
1559+
notifyTaskID: 42,
1560+
retryView: &RetryModel{task: task, submitted: true},
1561+
}
1562+
1563+
m.Update(nil)
1564+
1565+
if m.tasksNeedingInput[42] {
1566+
t.Error("tasksNeedingInput[42] should be cleared after retry submit")
1567+
}
1568+
if _, exists := m.executorPrompts[42]; exists {
1569+
t.Error("executorPrompts[42] should be cleared after retry submit")
1570+
}
1571+
if m.notification != "" {
1572+
t.Errorf("notification should be cleared after retry submit, got %q", m.notification)
1573+
}
1574+
if m.notifyTaskID != 0 {
1575+
t.Error("notifyTaskID should be cleared after retry submit")
1576+
}
1577+
}
1578+
15011579
// TestFilterChipDeletion tests the chip deletion feature in filter input.
15021580
func TestFilterChipDeletion(t *testing.T) {
15031581
tests := []struct {

0 commit comments

Comments
 (0)