feat(backlog): add staleness markers
This commit is contained in:
parent
f84d5661c0
commit
de05eafe21
3 changed files with 94 additions and 1 deletions
|
|
@ -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
|
||||
|
|
@ -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<Task> tasks;
|
||||
final DateTime now;
|
||||
final Duration staleAfter;
|
||||
final BacklogStalenessSettings stalenessSettings;
|
||||
|
||||
List<Task> get backlogTasks {
|
||||
return tasks.where((task) => task.isBacklog).toList(growable: false);
|
||||
|
|
@ -48,6 +85,10 @@ class BacklogView {
|
|||
return List<Task>.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',
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue