Skip to main content

nmbrs_runtime/readouts/builtins/
session_notice.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `session_notice` — the SRD-106 sticky-session announcement,
5//! seeded onto `on_session_start` by the runner when the
6//! `stick_session` rung re-attached the run to `sessions/latest`.
7//!
8//! Reads [`ReadoutContext::stick_reattached_session`]; renders
9//! nothing when it is empty (the readout may be bound in a
10//! workload's `readouts:` block, and stays quiet on runs where
11//! stick did not engage). The line renders in the palette's
12//! ACCENT — bold bright magenta — on color surfaces, and
13//! undecorated on monochrome sinks. It names the copy-pasteable
14//! override (`--session new`) so the escape hatch travels with
15//! the announcement.
16
17use std::fmt::Write as _;
18
19use crate::lifecycle::SubjectKind;
20use crate::readouts::buf::ReadoutBuf;
21use crate::readouts::context::ReadoutContext;
22use crate::readouts::readout::{ContentMode, Lod, Readout, ReadoutOptions};
23
24pub struct SessionNotice;
25
26impl Readout for SessionNotice {
27    fn name(&self) -> &'static str {
28        "session_notice"
29    }
30    fn accepts(&self) -> &'static [SubjectKind] {
31        &[SubjectKind::Session]
32    }
33
34    fn render(
35        &self,
36        ctx: &dyn ReadoutContext,
37        _lod: Lod,
38        mode: ContentMode,
39        _opts: &ReadoutOptions,
40        out: &mut dyn ReadoutBuf,
41    ) -> usize {
42        if mode == ContentMode::Explanation {
43            return render_explanation(out);
44        }
45        let id = ctx.stick_reattached_session();
46        if id.is_empty() {
47            return 0;
48        }
49        let color = ctx.use_color();
50        let accent = if color { "\x1b[1;95m" } else { "" };
51        let reset = if color { "\x1b[0m" } else { "" };
52        let mut tmp = String::with_capacity(id.len() + 96);
53        let _ = write!(
54            &mut tmp,
55            "{accent}\u{25cf} sticky session: re-attached to {id} \
56             (stick_session: true) — pass --session new to start fresh{reset}",
57        );
58        let len = tmp.len();
59        let _ = out.write_str(&tmp);
60        len
61    }
62}
63
64fn render_explanation(out: &mut dyn ReadoutBuf) -> usize {
65    let s = "sticky session: re-attached to <session-id> \
66             (stick_session: true) — pass --session new to start fresh";
67    let _ = out.write_str(s);
68    s.len()
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::readouts::buf::StringBuf;
75
76    #[derive(Default)]
77    struct TestCtx {
78        reattached: String,
79        color: bool,
80    }
81    impl ReadoutContext for TestCtx {
82        fn subject_name(&self) -> &str {
83            "session"
84        }
85        fn subject_seq(&self) -> Option<(usize, usize)> {
86            None
87        }
88        fn subject_labels(&self) -> &str {
89            ""
90        }
91        fn cycles_completed(&self) -> u64 {
92            0
93        }
94        fn cycles_total(&self) -> u64 {
95            0
96        }
97        fn ops_ok(&self) -> u64 {
98            0
99        }
100        fn errors(&self) -> u64 {
101            0
102        }
103        fn retries(&self) -> u64 {
104            0
105        }
106        fn concurrency(&self) -> usize {
107            0
108        }
109        fn elapsed_secs(&self) -> f64 {
110            0.0
111        }
112        fn consumed(&self) -> u64 {
113            0
114        }
115        fn status_metric_chips(&self) -> String {
116            String::new()
117        }
118        fn depth_indent(&self) -> &str {
119            ""
120        }
121        fn use_color(&self) -> bool {
122            self.color
123        }
124        fn event(&self) -> crate::lifecycle::EventType {
125            crate::lifecycle::EventType::SessionStart
126        }
127        fn stick_reattached_session(&self) -> &str {
128            &self.reattached
129        }
130    }
131
132    fn render(ctx: &TestCtx) -> String {
133        let mut s = String::new();
134        let mut buf = StringBuf::new(&mut s);
135        SessionNotice.render(
136            ctx,
137            Lod::Labeled,
138            ContentMode::Value,
139            &ReadoutOptions::new(),
140            &mut buf,
141        );
142        s
143    }
144
145    #[test]
146    fn names_the_session_and_the_override() {
147        let ctx = TestCtx {
148            reattached: "vsuite_20260805_1200".into(),
149            color: false,
150        };
151        let out = render(&ctx);
152        assert!(out.contains("re-attached to vsuite_20260805_1200"));
153        assert!(out.contains("--session new"));
154        assert!(out.contains("stick_session: true"));
155    }
156
157    #[test]
158    fn accent_wraps_the_whole_line_when_colored() {
159        let ctx = TestCtx {
160            reattached: "s1".into(),
161            color: true,
162        };
163        let out = render(&ctx);
164        assert!(
165            out.starts_with("\x1b[1;95m"),
166            "ACCENT opens the line: {out:?}"
167        );
168        assert!(out.ends_with("\x1b[0m"), "reset closes the line: {out:?}");
169    }
170
171    #[test]
172    fn silent_when_stick_did_not_engage() {
173        let ctx = TestCtx::default();
174        assert_eq!(render(&ctx), "");
175    }
176}