nmbrs_runtime/readouts/builtins/
session_notice.rs1use 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}