feat(mission-control): rename sessions inline in the Recent sessions flyout

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
mika kuns
2026-10-01 17:35:13 +02:00
co-authored by Claude Sonnet 5.5
parent d9355383a4
commit 5bf08873bd
8 changed files with 198 additions and 11 deletions
@@ -326,14 +326,18 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
{
var time = DateTime.SpecifyKind(s.StartedAt, DateTimeKind.Utc).ToLocalTime().ToString("g");
var briefPath = s.BriefPath;
RecentSessions.Add(new RecentSessionViewModel
RecentSessionViewModel row = null!;
row = new RecentSessionViewModel
{
SessionId = s.SessionId,
Title = s.Title,
Meta = isHandoff ? $"{Loc.T("missionControl.handoffSession")} · {time}" : $"{time} · {s.Cwd}",
IsHandoff = isHandoff,
ResumeCommand = new RelayCommand(() => ResumeSession(s, taskCurrent.Contains(s.SessionId))),
ResumeCommand = new RelayCommand(() => ResumeSession(s, row.Title, taskCurrent.Contains(s.SessionId))),
ShowBriefCommand = briefPath is null ? null : new RelayCommand(() => ShowBrief(briefPath)),
});
CommitRenameCommand = new RelayCommand(() => _ = RenameSessionAsync(row)),
};
RecentSessions.Add(row);
}
}
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("missionControl.queueRefreshFailed", ex.Message)); }
@@ -365,7 +369,7 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
// A task's current session reopens its task tile (deduped, same worktree guards); any other
// session gets a fresh ad-hoc tile resuming it in its recorded cwd.
private void ResumeSession(MissionControlSessionEntity session, bool isTaskCurrent)
private void ResumeSession(MissionControlSessionEntity session, string title, bool isTaskCurrent)
{
if (session.TaskId is { } taskId && isTaskCurrent)
{
@@ -374,10 +378,27 @@ public sealed partial class MissionControlViewModel : ViewModelBase, IDisposable
}
var paneId = Guid.NewGuid().ToString("n");
var sessionId = session.SessionId;
AddConPtyPane(new ConPtyPaneViewModel(null, session.Title,
AddConPtyPane(new ConPtyPaneViewModel(null, title,
() => DescribeAsync(() => _worker.GetResumeSessionLaunchSpecAsync(sessionId, paneId))) { IdentityKey = paneId });
}
/// <summary>Commits an inline rename: trims, ignores empty/unchanged input, persists the title.</summary>
internal async System.Threading.Tasks.Task RenameSessionAsync(RecentSessionViewModel row)
{
if (!row.IsEditing) return;
row.IsEditing = false;
var title = row.EditText.Trim();
if (title.Length == 0 || title == row.Title) return;
try
{
await using var ctx = await _dbFactory.CreateDbContextAsync();
await new MissionControlSessionRepository(ctx).RenameAsync(row.SessionId, title);
row.Title = title;
}
catch (Exception ex) { ErrorReported?.Invoke(Loc.T("missionControl.renameFailed", ex.Message)); }
}
private void ShowBrief(string briefPath)
{
var (ok, error) = ShellOpen.Path(briefPath);
@@ -941,9 +962,28 @@ public sealed partial class RunningOperationViewModel : ObservableObject
}
/// <summary>One row in the "Recent sessions" flyout.</summary>
public sealed class RecentSessionViewModel
public sealed partial class RecentSessionViewModel : ObservableObject
{
public required string Title { get; init; }
public required string SessionId { get; init; }
/// <summary>Display title; updated in place once a rename is persisted.</summary>
[ObservableProperty] private string _title = "";
/// <summary>True while the inline title TextBox is shown.</summary>
[ObservableProperty] private bool _isEditing;
/// <summary>The TextBox's working value, kept apart from <see cref="Title"/> so Escape needs no restore.</summary>
[ObservableProperty] private string _editText = "";
/// <summary>Commits <see cref="EditText"/> as the new title.</summary>
public required IRelayCommand CommitRenameCommand { get; init; }
/// <summary>Switches the row into inline edit mode, seeded with the current title.</summary>
public void BeginEdit()
{
EditText = Title;
IsEditing = true;
}
/// <summary>Leaves edit mode without saving.</summary>
public void CancelEdit() => IsEditing = false;
/// <summary>Start time plus cwd (origin) or the handoff label (handoff).</summary>
public required string Meta { get; init; }
/// <summary>Indents the row under its chain's origin session.</summary>