feat(notes): notes get their own list, a day log and an Enter chain

The editor was reachable from one button that only appeared in My Day,
which is how it got forgotten. It is now a permanent sidebar entry next
to My Day and Planned, and it fills the tasks island rather than the
details pane, because a note list is content.

Inside, the date picker is gone: today sits at the top with the capture
box, older days follow as dated groups. Enter in a note saves it and
opens the next one, Shift+Enter breaks the line so notes can be several,
Backspace on an empty note deletes it and steps back up. Deleting also
has a visible hover button instead of only "clear the text and hope".
Agent-written notes carry a marker.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
CubeGameLP
2026-08-28 21:19:37 +02:00
co-authored by Claude Opus 5
parent ae0c7d3771
commit 0f04b796f4
34 changed files with 533 additions and 192 deletions
@@ -20,7 +20,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
private readonly IWorkerClient _worker;
private readonly IServiceProvider _services;
private readonly INotesApi _notesApi;
private readonly MergeCoordinator _merge;
// ── Section view models ───────────────────────────────────────────────────
@@ -39,29 +38,20 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
private readonly Action<string, string?> _workerPlanningMergeAbortedHandler;
private readonly Action<string> _workerPlanningCompletedHandler;
[ObservableProperty] private bool _isNotesMode;
[ObservableProperty] private bool _isPrepMode;
public bool IsTaskDetailVisible => !IsNotesMode && !IsPrepMode;
public bool IsTaskDetailVisible => !IsPrepMode;
/// <summary>Centered placeholder shown when no task is selected — hidden in Notes/Prep mode,
/// <summary>Centered placeholder shown when no task is selected — hidden in Prep mode,
/// where <see cref="IsTaskDetailVisible"/> is already false.</summary>
public bool IsEmptyStateVisible => Task is null && IsTaskDetailVisible;
partial void OnIsNotesModeChanged(bool value)
{
OnPropertyChanged(nameof(IsTaskDetailVisible));
OnPropertyChanged(nameof(IsEmptyStateVisible));
}
partial void OnIsPrepModeChanged(bool value)
{
OnPropertyChanged(nameof(IsTaskDetailVisible));
OnPropertyChanged(nameof(IsEmptyStateVisible));
}
public NotesEditorViewModel Notes { get; private set; } = null!;
// Current task row (set by IslandsShellViewModel via Bind)
[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(EnqueueCommand))]
@@ -324,13 +314,11 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
IDbContextFactory<ClaudeDoDbContext> dbFactory,
IWorkerClient worker,
IServiceProvider services,
INotesApi notesApi,
MergeCoordinator merge)
{
_dbFactory = dbFactory;
_worker = worker;
_services = services;
_notesApi = notesApi;
_merge = merge;
Monitor = new TaskMonitorViewModel(dbFactory, worker);
@@ -374,8 +362,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
if (e.PropertyName == nameof(OperationStatus.IsRunning)) ParkReviewCommand.NotifyCanExecuteChanged();
};
Notes = new NotesEditorViewModel(_notesApi);
Notes.ErrorReported += msg => { if (ShowErrorAsync is not null) _ = ShowErrorAsync(msg); };
Subtasks.CollectionChanged += (_, _) => NotifyStepsChanged();
Subtasks.CollectionChanged += (_, _) => Merge.SyncChildOutcomes(HasChildOutcomes, Subtasks.Count);
Attachments.CollectionChanged += (_, _) => OnPropertyChanged(nameof(FilesBadge));
@@ -592,18 +578,9 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
catch { }
}
public void ShowNotes()
{
Bind(null, "notes");
IsPrepMode = false;
IsNotesMode = true;
_ = Notes.LoadDayAsync(DateOnly.FromDateTime(DateTime.Today));
}
public void ShowPrep()
{
Bind(null, "prep");
IsNotesMode = false;
IsPrepMode = true;
_ = Prep.LoadLastPrepLogIfEmptyAsync();
}
@@ -612,7 +589,6 @@ public sealed partial class DetailsIslandViewModel : ViewModelBase, IDisposable
/// logged with the bind timing so rebind churn can be attributed to a trigger.</param>
public void Bind(TaskRowViewModel? row, string source = "?")
{
IsNotesMode = false;
IsPrepMode = false;
_loadCts?.Cancel();
_loadCts?.Dispose();