diff --git a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
index 24cb5ae8..dd3f4ee9 100644
--- a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
+++ b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml
@@ -31,58 +31,8 @@
Margin="0"
Classes.selected="{Binding IsSelected}"
Classes.dragging="{Binding IsDragging}"
- Classes.done="{Binding Done}">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Classes.done="{Binding Done}"
+ ContextRequested="OnRowContextRequested">
diff --git a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
index 8fc3e09a..e7f2524c 100644
--- a/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
+++ b/src/ClaudeDo.Ui/Views/Islands/TaskRowView.axaml.cs
@@ -1,9 +1,11 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
using ClaudeDo.Data.Models;
+using ClaudeDo.Ui.Localization;
using ClaudeDo.Ui.ViewModels.Islands;
using TaskStatus = ClaudeDo.Data.Models.TaskStatus;
@@ -19,6 +21,57 @@ public partial class TaskRowView : UserControl
this.GetVisualAncestors().OfType()
.Select(ic => ic.DataContext).OfType().FirstOrDefault();
+ // The context menu is built here instead of declared in XAML so the ~18 MenuItems only
+ // exist while a row's menu is actually open, not for every row rendered in the list.
+ private void OnRowContextRequested(object? sender, ContextRequestedEventArgs e)
+ {
+ if (sender is not Border border) return;
+ if (DataContext is not TaskRowViewModel row) return;
+ if (FindTasksVm() is not { } vm) return;
+
+ // OnTunnelPointerPressed (TasksIslandView) only selects on the left button, so
+ // right-click needs its own explicit selection before the menu opens.
+ vm.SelectedTask = row;
+
+ var menu = new ContextMenu { DataContext = row };
+
+ MenuItem MakeItem(string key, EventHandler click, bool isVisible = true, string? tag = null)
+ {
+ var item = new MenuItem { Header = Loc.T(key), DataContext = row, IsVisible = isVisible };
+ if (tag is not null) item.Tag = tag;
+ item.Click += click;
+ return item;
+ }
+
+ menu.Items.Add(MakeItem("tasks.ctxSendToQueue", OnSendToQueueClick, row.CanSendToQueue));
+ menu.Items.Add(MakeItem("tasks.ctxRemoveFromQueue", OnRemoveFromQueueClick, row.CanRemoveFromQueue));
+ menu.Items.Add(MakeItem("tasks.ctxCancelExecution", OnCancelExecutionClick, row.IsRunning));
+ menu.Items.Add(new Separator());
+
+ var markAs = new MenuItem { Header = Loc.T("tasks.ctxMarkAs"), DataContext = row };
+ markAs.Items.Add(MakeItem("tasks.ctxMarkDone", OnSetStatusClick, tag: "Done"));
+ markAs.Items.Add(MakeItem("tasks.ctxMarkCancelled", OnSetStatusClick, tag: "Cancelled"));
+ markAs.Items.Add(new Separator());
+ markAs.Items.Add(MakeItem("tasks.ctxMarkManual", OnToggleManualClick, !row.IsManual));
+ markAs.Items.Add(MakeItem("tasks.ctxMarkClaudeTask", OnToggleManualClick, row.IsManual));
+ menu.Items.Add(markAs);
+
+ menu.Items.Add(new Separator());
+ menu.Items.Add(MakeItem("tasks.ctxOpenConPtySession", OnOpenConPtySessionClick));
+ menu.Items.Add(MakeItem("tasks.ctxOpenPlanningSession", OnOpenPlanningSessionClick, row.CanOpenPlanningSession));
+ menu.Items.Add(MakeItem("tasks.ctxResumePlanningSession", OnResumePlanningSessionClick, row.CanResumeOrDiscardPlanning));
+ menu.Items.Add(MakeItem("tasks.ctxFinalizePlanningSession", OnFinalizePlanningSessionClick, row.CanFinalizePlanning));
+ menu.Items.Add(MakeItem("tasks.ctxDiscardPlanningSession", OnDiscardPlanningSessionClick, row.CanResumeOrDiscardPlanning));
+ menu.Items.Add(new Separator());
+ menu.Items.Add(MakeItem("tasks.ctxScheduleFor", OnScheduleForClick));
+ menu.Items.Add(MakeItem("tasks.ctxClearSchedule", OnClearScheduleClick, row.HasSchedule));
+ menu.Items.Add(MakeItem("tasks.ctxAddToMyDay", OnAddToMyDayClick, row.CanAddToMyDay));
+ menu.Items.Add(MakeItem("tasks.ctxRemoveFromMyDay", OnRemoveFromMyDayClick, row.IsMyDay));
+
+ menu.Open(border);
+ e.Handled = true;
+ }
+
private async void OnSendToQueueClick(object? sender, RoutedEventArgs e)
{
if (DataContext is TaskRowViewModel row && FindTasksVm() is { } vm)