diff --git a/Codex Documentation/Current Software Plan/V1_BLOCK_04_Backlog_Quick_Capture.md b/Codex Documentation/Archived plans/V1_BLOCK_04_Backlog_Quick_Capture.md similarity index 89% rename from Codex Documentation/Current Software Plan/V1_BLOCK_04_Backlog_Quick_Capture.md rename to Codex Documentation/Archived plans/V1_BLOCK_04_Backlog_Quick_Capture.md index 3d0f09e..aad3549 100644 --- a/Codex Documentation/Current Software Plan/V1_BLOCK_04_Backlog_Quick_Capture.md +++ b/Codex Documentation/Archived plans/V1_BLOCK_04_Backlog_Quick_Capture.md @@ -1,6 +1,6 @@ # V1 Block 04 — Backlog and Quick Capture -Status: Planned +Status: Completed Purpose: Support low-friction task capture and a unified backlog/wishlist model. @@ -116,6 +116,8 @@ BREAKPOINT: Stop here. Confirm `high` mode before implementing scheduling integr Recommended Codex level: low +Status: Completed + Tasks: Implement age/staleness calculation for backlog display: @@ -130,6 +132,15 @@ Acceptance criteria: - Tests cover fresh, aging, stale, and configurable thresholds. +Execution notes: + +- Added `BacklogStalenessMarker` values for green, blue, and purple display buckets. +- Added `BacklogStalenessSettings` with configurable green and blue thresholds. +- Added `BacklogView.stalenessMarkerFor` to calculate display marker state without side effects or prompts. +- Default thresholds are green through 7 days, blue through 30 days, and purple from 31 days onward. +- Added tests for fresh, aging, stale, and custom threshold behavior. +- `dart analyze` and `dart test` passed. + Commit suggestion: ```text diff --git a/lib/src/backlog.dart b/lib/src/backlog.dart index a8b62f4..0a3cef5 100644 --- a/lib/src/backlog.dart +++ b/lib/src/backlog.dart @@ -19,17 +19,54 @@ enum BacklogSortKey { timesPushed, } +/// Visual age bucket for backlog display. +enum BacklogStalenessMarker { + green, + blue, + purple, +} + +/// Configurable thresholds for backlog age markers. +class BacklogStalenessSettings { + const BacklogStalenessSettings({ + this.greenMaxAge = const Duration(days: 7), + this.blueMaxAge = const Duration(days: 30), + }); + + final Duration greenMaxAge; + final Duration blueMaxAge; + + BacklogStalenessMarker markerFor({ + required Task task, + required DateTime now, + }) { + final age = now.difference(task.createdAt); + + if (age <= greenMaxAge) { + return BacklogStalenessMarker.green; + } + + if (age <= blueMaxAge) { + return BacklogStalenessMarker.blue; + } + + return BacklogStalenessMarker.purple; + } +} + /// Read-only backlog projection over the unified task list. class BacklogView { const BacklogView({ required this.tasks, required this.now, this.staleAfter = const Duration(days: 31), + this.stalenessSettings = const BacklogStalenessSettings(), }); final List tasks; final DateTime now; final Duration staleAfter; + final BacklogStalenessSettings stalenessSettings; List get backlogTasks { return tasks.where((task) => task.isBacklog).toList(growable: false); @@ -48,6 +85,10 @@ class BacklogView { return List.unmodifiable(sortedTasks); } + BacklogStalenessMarker stalenessMarkerFor(Task task) { + return stalenessSettings.markerFor(task: task, now: now); + } + bool _matchesFilter(Task task, BacklogFilter filter) { return switch (filter) { BacklogFilter.inbox => task.projectId == 'inbox', diff --git a/test/scheduling_engine_test.dart b/test/scheduling_engine_test.dart index 7660373..ea6b703 100644 --- a/test/scheduling_engine_test.dart +++ b/test/scheduling_engine_test.dart @@ -216,6 +216,47 @@ void main() { ]); }); + test('backlog staleness markers cover default thresholds', () { + final fresh = Task.quickCapture( + id: 'fresh', + title: 'Fresh', + createdAt: now.subtract(const Duration(days: 7)), + ); + final aging = Task.quickCapture( + id: 'aging', + title: 'Aging', + createdAt: now.subtract(const Duration(days: 8)), + ); + final stale = Task.quickCapture( + id: 'stale', + title: 'Stale', + createdAt: now.subtract(const Duration(days: 31)), + ); + final view = BacklogView(tasks: [fresh, aging, stale], now: now); + + expect(view.stalenessMarkerFor(fresh), BacklogStalenessMarker.green); + expect(view.stalenessMarkerFor(aging), BacklogStalenessMarker.blue); + expect(view.stalenessMarkerFor(stale), BacklogStalenessMarker.purple); + }); + + test('backlog staleness markers support configurable thresholds', () { + final task = Task.quickCapture( + id: 'custom', + title: 'Custom', + createdAt: now.subtract(const Duration(days: 5)), + ); + final view = BacklogView( + tasks: [task], + now: now, + stalenessSettings: const BacklogStalenessSettings( + greenMaxAge: Duration(days: 2), + blueMaxAge: Duration(days: 4), + ), + ); + + expect(view.stalenessMarkerFor(task), BacklogStalenessMarker.purple); + }); + test('unchecked quick capture goes to backlog', () { const service = QuickCaptureService();