diff options
Diffstat (limited to 'crates/dinggerd/src/watcher.rs')
| -rw-r--r-- | crates/dinggerd/src/watcher.rs | 79 |
1 files changed, 76 insertions, 3 deletions
diff --git a/crates/dinggerd/src/watcher.rs b/crates/dinggerd/src/watcher.rs index f9c987e..b76f53c 100644 --- a/crates/dinggerd/src/watcher.rs +++ b/crates/dinggerd/src/watcher.rs @@ -105,6 +105,9 @@ pub struct PaneTrack { /// serves done and blocked oldest first; working and idle are listed /// most recent first. pub since: Option<u64>, + /// Marked unread while the user looked at it: it stays done until they + /// look away, and is seen when they come back. + pub held: bool, } impl PaneTrack { @@ -113,6 +116,7 @@ impl PaneTrack { state: AgentState::Unknown, seen: true, since: None, + held: false, }; pub fn status(&self) -> Status { @@ -135,7 +139,7 @@ impl PaneTrack { "blocked" => (AgentState::Blocked, true), _ => return PaneTrack::NEW, }; - PaneTrack { state, seen, since } + PaneTrack { state, seen, since, held: false } } } @@ -146,7 +150,9 @@ impl PaneTrack { /// - Completion (working/blocked to idle): seen only if the user is looking /// at the pane's window; otherwise the pane is done. /// - Any other transition to idle (first observation): seen unchanged. -/// - Looking at the pane's window, or jumping to it (`acked`): seen. +/// - Looking at the pane's window, or jumping to it (`acked`): seen. A pane +/// marked unread while looked at (`held`) is seen only once the user +/// looks away and back, or jumps to it. pub fn advance( prev: PaneTrack, observed: AgentState, @@ -157,14 +163,18 @@ pub fn advance( let mut next = prev; if observed != prev.state { next.state = observed; + next.held = false; if observed != AgentState::Idle { next.seen = true; } else if matches!(prev.state, AgentState::Working | AgentState::Blocked) { next.seen = attended; } } - if attended || acked { + if acked || (attended && !next.held) { next.seen = true; + next.held = false; + } else if !attended { + next.held = false; } if next.status() != prev.status() || next.since.is_none() { next.since = Some(now); @@ -172,6 +182,16 @@ pub fn advance( next } +/// Mark an idle agent unread: done again, listed last among done agents of +/// its importance (from now). If the user is looking at it, it stays done +/// until they look away. Any other status is left alone. +pub fn mark_unread(track: PaneTrack, attended: bool, now: u64) -> PaneTrack { + if track.status() != Status::Idle { + return track; + } + PaneTrack { seen: false, held: attended, since: Some(now), ..track } +} + /// The order agents are listed in: herdr's status priority; within a /// status, the more important first; then done and blocked oldest first /// (the queue: nothing waits forever), working and idle most recent first; @@ -298,6 +318,16 @@ impl Watcher { } track = next; } + if hooks::take_unread(&pane.pane_id) { + let next = mark_unread(track, pane.attended, now); + info!( + target = %pane.display_target, + applied = next != track, + status = track.status().label(), + "mark unread" + ); + track = next; + } self.tracks.insert(pane.pane_id.clone(), track); // Update what the user sees first, then alert. @@ -561,6 +591,49 @@ mod tests { assert_eq!(seen.since, Some(170), "idle since the user looked"); } + fn seen_while_looking() -> PaneTrack { + step(step(PaneTrack::NEW, Working, true), Idle, true) + } + + #[test] + fn marking_unread_while_away_waits_for_the_user_to_look() { + let idle = seen_while_looking(); + let marked = mark_unread(idle, false, 300); + assert_eq!(marked.status(), Status::Done); + assert_eq!(marked.since, Some(300), "last among done agents"); + assert_eq!(step(marked, Idle, false).status(), Status::Done); + assert_eq!(step(marked, Idle, true).status(), Status::Idle, "seen when looked at"); + } + + #[test] + fn marking_unread_while_looking_holds_until_the_user_looks_away_and_back() { + let marked = mark_unread(seen_while_looking(), true, 300); + let still = step(step(marked, Idle, true), Idle, true); + assert_eq!(still.status(), Status::Done, "still looking: stays done"); + let away = step(still, Idle, false); + assert_eq!(away.status(), Status::Done); + assert_eq!(step(away, Idle, true).status(), Status::Idle, "back: seen"); + } + + #[test] + fn a_held_mark_clears_on_a_jump_or_a_new_turn() { + let marked = mark_unread(seen_while_looking(), true, 300); + assert_eq!(advance(marked, Idle, true, true, 310).status(), Status::Idle, "jumped to it"); + let working = step(marked, Working, true); + assert_eq!(working.status(), Status::Working); + assert_eq!(step(working, Idle, true).status(), Status::Idle, "finished while looking"); + } + + #[test] + fn only_an_idle_agent_can_be_marked_unread() { + let working = step(PaneTrack::NEW, Working, false); + assert_eq!(mark_unread(working, false, 300), working); + let done = step(working, Idle, false); + assert_eq!(mark_unread(done, false, 300), done, "done keeps its place"); + let blocked = step(PaneTrack::NEW, Blocked, false); + assert_eq!(mark_unread(blocked, true, 300), blocked); + } + // herdr: agent_focus_marks_already_focused_done_agent_seen #[test] fn jumping_to_a_pane_turns_done_into_idle() { |
