use gwk_domain::command::KernelCommand;
use gwk_domain::entity::{AttentionItem, Gate, Message, Receipt};
use gwk_domain::fsm::{GateVerdict, MessageState};
use gwk_domain::ids::{AttentionItemId, GateId, Seq, Timestamp};
use gwk_theme::marks::{GlyphSet, StateBinding};
use gwk_theme::tier::ColorTier;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
use ratatui::style::{Modifier, Style};
use crate::input::HitMap;
use crate::theme;
use crate::theme::binding;
#[derive(Debug, Clone)]
pub struct QueueState {
pub attention: Vec<AttentionItem>,
pub gates: Vec<Gate>,
pub receipts: Vec<Receipt>,
pub messages: Vec<Message>,
pub watermark: Option<Seq>,
pub now: Timestamp,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum QueueTarget {
Attention(AttentionItemId),
Gate(GateId),
}
pub fn target_order(state: &QueueState) -> Vec<QueueTarget> {
rows(state, ColorTier::Mono)
.into_iter()
.filter_map(|row| row.target)
.collect()
}
const MAIL_BUDGET: usize = 3;
pub fn ack(target: &QueueTarget) -> Option<KernelCommand> {
match target {
QueueTarget::Attention(id) => Some(KernelCommand::AckAttention {
attention_item_id: id.clone(),
}),
QueueTarget::Gate(_) => None,
}
}
pub fn mute(target: &QueueTarget, until: Timestamp) -> Option<KernelCommand> {
match target {
QueueTarget::Attention(id) => Some(KernelCommand::MuteAttention {
attention_item_id: id.clone(),
muted_until: until,
}),
QueueTarget::Gate(_) => None,
}
}
pub fn unmute(target: &QueueTarget) -> Option<KernelCommand> {
match target {
QueueTarget::Attention(id) => Some(KernelCommand::UnmuteAttention {
attention_item_id: id.clone(),
}),
QueueTarget::Gate(_) => None,
}
}
pub fn resolve(target: &QueueTarget, resolution: Option<String>) -> Option<KernelCommand> {
match target {
QueueTarget::Attention(id) => Some(KernelCommand::ResolveAttention {
attention_item_id: id.clone(),
resolution,
}),
QueueTarget::Gate(_) => None,
}
}
pub fn decide(gate: &Gate, option: &str) -> Option<KernelCommand> {
if gate.verdict != GateVerdict::Pending
|| !gate
.options
.as_ref()
.is_some_and(|options| options.iter().any(|candidate| candidate == option))
{
return None;
}
Some(KernelCommand::DecideGate {
gate_id: gate.id.clone(),
expected_version: gate.version,
verdict: verdict_for_option(option)?,
chosen_option: Some(option.to_owned()),
evidence_ref: None,
})
}
pub fn verdict_for_option(option: &str) -> Option<GateVerdict> {
match option.trim().to_ascii_lowercase().as_str() {
"accept" | "acceptforsession" | "allow" | "always" | "approve" | "approved"
| "continue" | "once" | "pass" | "proceed" | "yes" => Some(GateVerdict::Pass),
"cancel" | "decline" | "deny" | "fail" | "no" | "reject" | "rejected" | "stop" => {
Some(GateVerdict::Fail)
}
"ask" | "defer" | "hold" | "partial" => Some(GateVerdict::Partial),
_ => None,
}
}
fn audible(item: &AttentionItem, now: &Timestamp) -> bool {
item.resolved_at.is_none()
&& item.acked_at.is_none()
&& !item.muted_until.as_ref().is_some_and(|until| until > now)
}
fn ranked(items: &[AttentionItem]) -> Vec<&AttentionItem> {
let mut open: Vec<&AttentionItem> = items.iter().filter(|i| i.resolved_at.is_none()).collect();
open.sort_by(|a, b| {
match (a.priority, b.priority) {
(Some(x), Some(y)) => x.cmp(&y),
(Some(_), None) => std::cmp::Ordering::Less,
(None, Some(_)) => std::cmp::Ordering::Greater,
(None, None) => std::cmp::Ordering::Equal,
}
.then_with(|| a.id.as_str().cmp(b.id.as_str()))
});
open
}
fn occurrences<'a>(
item: &AttentionItem,
receipts: &'a [Receipt],
) -> Option<(usize, &'a Timestamp)> {
if item.kind != "authority" {
return None;
}
let subject_ref = item.subject_ref.as_deref()?;
let mut count = 0;
let mut since: Option<&Timestamp> = None;
for receipt in receipts {
let matched = subject_ref
.strip_prefix(receipt.subject_type.as_str())
.and_then(|rest| rest.strip_prefix('/'))
== Some(receipt.subject_id.as_str());
if matched {
count += 1;
since = Some(match since {
Some(s) if s <= &receipt.ts => s,
_ => &receipt.ts,
});
}
}
since.map(|s| (count, s))
}
fn hhmm(ts: &Timestamp) -> &str {
ts.as_str().get(11..16).unwrap_or(ts.as_str())
}
fn open_prompt(gate: &Gate) -> bool {
gate.question.is_some() && gate.verdict == GateVerdict::Pending
}
fn audible_count(state: &QueueState) -> usize {
state.gates.iter().filter(|g| open_prompt(g)).count()
+ state
.attention
.iter()
.filter(|i| audible(i, &state.now))
.count()
}
struct Row {
mark: Option<&'static StateBinding>,
text: String,
right: Option<String>,
style: Style,
target: Option<QueueTarget>,
}
impl Row {
fn plain(text: String, style: Style) -> Self {
Row {
mark: None,
text,
right: None,
style,
target: None,
}
}
}
fn rows(state: &QueueState, tier: ColorTier) -> Vec<Row> {
let mut out = Vec::new();
let muted_style = theme::state_style(binding("idle"), tier);
let warn = binding("needs_attention");
let done = binding("done");
let open_gates: Vec<&Gate> = state.gates.iter().filter(|g| open_prompt(g)).collect();
let count = audible_count(state);
if count == 0 {
out.push(Row::plain(
"all clear -- nothing needs attention".into(),
theme::state_style(done, tier),
));
} else {
let oldest = open_gates
.iter()
.map(|g| &g.created_at)
.chain(
state
.attention
.iter()
.filter(|i| audible(i, &state.now))
.map(|i| &i.raised_at),
)
.min();
out.push(Row {
mark: None,
text: format!("{count} need attention"),
right: oldest.map(|t| format!("oldest {}", hhmm(t))),
style: theme::state_style(warn, tier).add_modifier(Modifier::BOLD),
target: None,
});
}
for gate in &open_gates {
let question = gate.question.as_deref().unwrap_or_default();
let kind = gate.kind.as_deref().unwrap_or("gate");
out.push(Row {
mark: Some(warn),
text: format!("gate {kind} {question}"),
right: None,
style: theme::state_style(warn, tier),
target: Some(QueueTarget::Gate(gate.id.clone())),
});
if let Some(options) = &gate.options {
out.push(Row::plain(
format!(" options: {}", options.join(" / ")),
muted_style,
));
}
}
for item in ranked(&state.attention) {
let mut text = item.summary.clone();
if let Some((count, since)) = occurrences(item, &state.receipts) {
text.push_str(&format!(" {count}x since {}", hhmm(since)));
}
let live_mute = item
.muted_until
.as_ref()
.filter(|until| **until > state.now);
let (mark, style) = if live_mute.is_some() || item.acked_at.is_some() {
(binding("idle"), muted_style)
} else {
(warn, theme::state_style(warn, tier))
};
if let Some(until) = live_mute {
text.push_str(&format!(" muted until {}", hhmm(until)));
} else if item.acked_at.is_some() {
text.push_str(" acked");
}
out.push(Row {
mark: Some(mark),
text: format!("{text} ({})", item.kind),
right: None,
style,
target: Some(QueueTarget::Attention(item.id.clone())),
});
}
let arrived: Vec<&Message> = state
.messages
.iter()
.filter(|m| {
matches!(
m.state,
MessageState::Delivered | MessageState::Acknowledged | MessageState::Applied
)
})
.collect();
let failed: Vec<&Message> = state
.messages
.iter()
.filter(|message| {
matches!(
message.state,
MessageState::DeadLetter | MessageState::Rejected
)
})
.collect();
if arrived.is_empty() && failed.is_empty() {
out.push(Row::plain("no messages".into(), muted_style));
} else {
for message in arrived.iter().take(MAIL_BUDGET) {
let sender = message.sender.as_deref().unwrap_or("?");
let recipient = message.recipient.as_deref().unwrap_or("?");
let kind = message.kind.as_deref().unwrap_or("message");
out.push(Row::plain(
format!(
"msg {} {sender} to {recipient} ({kind})",
hhmm(&message.updated_at)
),
muted_style,
));
}
if arrived.len() > MAIL_BUDGET {
out.push(Row::plain(
format!("+{} more", arrived.len() - MAIL_BUDGET),
muted_style,
));
}
for message in failed.iter().take(MAIL_BUDGET) {
let sender = message.sender.as_deref().unwrap_or("?");
let recipient = message.recipient.as_deref().unwrap_or("?");
let kind = message.kind.as_deref().unwrap_or("message");
let state_word = match message.state {
MessageState::DeadLetter => "dead",
MessageState::Rejected => "rejected",
_ => "failed",
};
out.push(Row {
mark: Some(warn),
text: format!(
"{state_word} {} {sender} to {recipient} ({kind})",
hhmm(&message.updated_at)
),
right: None,
style: theme::state_style(warn, tier),
target: None,
});
let attempts = message.delivery_attempts;
out.push(Row::plain(
format!(
" {attempts} attempt{} -- {}",
if attempts == 1 { "" } else { "s" },
message
.dead_letter_reason
.as_deref()
.unwrap_or("no reason recorded")
),
muted_style,
));
}
if failed.len() > MAIL_BUDGET {
out.push(Row::plain(
format!("+{} more failed deliveries", failed.len() - MAIL_BUDGET),
muted_style,
));
}
}
out.push(Row::plain(
"recent -- resolve is re-arm: a recurring problem raises anew".into(),
muted_style,
));
let mut recent = 0usize;
for gate in state
.gates
.iter()
.filter(|g| !open_prompt(g) && g.question.is_some())
{
let chosen = gate
.chosen_option
.as_deref()
.unwrap_or("(no option recorded)");
let verdict = format!("{:?}", gate.verdict).to_lowercase();
out.push(Row {
mark: Some(done),
text: format!("decided: {chosen} ({verdict})"),
right: None,
style: muted_style,
target: None,
});
recent += 1;
}
for item in state.attention.iter().filter(|i| i.resolved_at.is_some()) {
let resolution = item.resolution.as_deref().unwrap_or("resolved");
out.push(Row {
mark: Some(done),
text: format!("{} -- {resolution}", item.summary),
right: None,
style: muted_style,
target: None,
});
recent += 1;
}
if recent == 0 {
out.push(Row::plain(" nothing recent".into(), muted_style));
}
out
}
pub fn render(
area: Rect,
buf: &mut Buffer,
state: &QueueState,
selected: Option<&QueueTarget>,
tier: ColorTier,
glyphs: GlyphSet,
hits: &mut HitMap<QueueTarget>,
) {
hits.clear();
if area.height == 0 || area.width == 0 {
return;
}
let body_rows = area.height - 1;
let built = rows(state, tier);
let capacity = body_rows as usize;
let needs_notice = built.len() > capacity && capacity > 1;
let visible = capacity
.saturating_sub(usize::from(needs_notice))
.min(built.len());
let selected_row = selected.and_then(|target| {
built
.iter()
.position(|row| row.target.as_ref() == Some(target))
});
let start = if visible == 0 {
0
} else {
selected_row
.filter(|index| *index >= visible)
.map_or(0, |index| index + 1 - visible)
.min(built.len().saturating_sub(visible))
};
let end = (start + visible).min(built.len());
let selection_fg = match tier {
ColorTier::Truecolor | ColorTier::Xterm256 => gwk_theme::SIGNAL
.iter()
.find(|t| t.name == "gws_selection")
.map(|t| theme::token_style(t, tier)),
ColorTier::Ansi16 | ColorTier::Mono => None,
};
let accent_style = gwk_theme::SIGNAL
.iter()
.find(|t| t.name == "gws_selection")
.map(|t| theme::token_style(t, tier))
.unwrap_or_default()
.add_modifier(Modifier::BOLD);
for (i, row) in built[start..end].iter().enumerate() {
let y = area.y + i as u16;
let is_selected = matches!((selected, &row.target), (Some(sel), Some(t)) if *sel == *t);
let text_style = match (is_selected, selection_fg) {
(true, Some(fg)) => row.style.patch(fg),
_ => row.style,
};
if is_selected {
buf.set_string(area.x, y, ">", accent_style);
}
let mut x = area.x + 1;
if let Some(state_binding) = row.mark {
let mark =
gwk_theme::marks::mark(state_binding.mark).expect("the mark inventory is pinned");
let glyph = theme::glyph(mark, 0, glyphs);
let budget = (area.x + area.width).saturating_sub(x);
buf.set_stringn(x, y, glyph.to_string(), budget as usize, row.style);
x += 2;
}
let width = (area.x + area.width).saturating_sub(x);
let safe_text = theme::safe_text(&row.text, width as usize);
buf.set_stringn(x, y, safe_text.as_ref(), width as usize, text_style);
if let Some(right) = &row.right {
crate::row::paint_tail(
buf,
area,
y,
x.saturating_add(u16::try_from(safe_text.chars().count()).unwrap_or(u16::MAX)),
right,
text_style,
);
}
if let Some(target) = &row.target {
hits.register(Rect::new(area.x, y, area.width, 1), target.clone());
}
}
if needs_notice {
let y = area.y + body_rows.saturating_sub(1);
let notice = if start == 0 {
format!("+{} more", built.len() - end)
} else if end == built.len() {
format!("+{start} before")
} else {
format!("+{start} before +{} more", built.len() - end)
};
buf.set_stringn(
area.x + 1,
y,
notice,
area.width.saturating_sub(1) as usize,
theme::state_style(binding("idle"), tier),
);
}
let as_of = state
.watermark
.as_ref()
.map_or_else(|| "-".to_string(), |w| w.to_string());
let status = format!("QUEUE !{} as-of {as_of}", audible_count(state));
let safe_status = theme::safe_text(&status, area.width as usize);
buf.set_stringn(
area.x,
area.y + area.height - 1,
safe_status.as_ref(),
area.width as usize,
Style::default(),
);
}
#[cfg(test)]
mod tests {
use gwk_domain::envelope::Actor;
use ratatui::Terminal;
use ratatui::backend::TestBackend;
use super::*;
const NOW: &str = "2026-08-06T12:00:00Z";
fn ts(s: &str) -> Timestamp {
Timestamp::new(s)
}
fn item(id: &str, summary: &str) -> AttentionItem {
AttentionItem {
id: AttentionItemId::new(id),
kind: "watchdog".into(),
summary: summary.into(),
subject_ref: None,
raised_by: None,
priority: None,
raised_at: ts("2026-08-06T09:00:00Z"),
acked_at: None,
muted_until: None,
resolved_at: None,
resolution: None,
}
}
fn gate(id: &str, question: &str) -> Gate {
Gate {
id: GateId::new(id),
version: 1,
attempt_id: None,
phase_ref: None,
kind: Some("permission".into()),
question: Some(question.into()),
options: Some(vec!["allow".into(), "deny".into()]),
verdict: GateVerdict::Pending,
chosen_option: None,
decided_by: None,
evidence_ref: None,
created_at: ts("2026-08-06T09:30:00Z"),
updated_at: ts("2026-08-06T09:30:00Z"),
}
}
fn receipt(subject_type: &str, subject_id: &str, at: &str) -> Receipt {
Receipt {
id: gwk_domain::ids::ReceiptId::new(format!("r-{at}")),
actor: Actor {
kind: "kernel".into(),
id: None,
},
action: "deploy".into(),
subject_type: subject_type.into(),
subject_id: subject_id.into(),
from: None,
to: None,
observed_basis: None,
ts: ts(at),
}
}
fn empty_state() -> QueueState {
QueueState {
attention: Vec::new(),
gates: Vec::new(),
receipts: Vec::new(),
messages: Vec::new(),
watermark: None,
now: ts(NOW),
}
}
fn dump_frame(
w: u16,
h: u16,
state: &QueueState,
selected: Option<&QueueTarget>,
) -> (String, HitMap<QueueTarget>, Buffer) {
dump_frame_tier(w, h, state, selected, ColorTier::Mono)
}
fn dump_frame_tier(
w: u16,
h: u16,
state: &QueueState,
selected: Option<&QueueTarget>,
tier: ColorTier,
) -> (String, HitMap<QueueTarget>, Buffer) {
let mut terminal = Terminal::new(TestBackend::new(w, h)).expect("terminal");
let mut hits = HitMap::new();
terminal
.draw(|frame| {
let area = frame.area();
render(
area,
frame.buffer_mut(),
state,
selected,
tier,
GlyphSet::Unicode,
&mut hits,
);
})
.expect("draw");
let buf = terminal.backend().buffer().clone();
let mut out = String::new();
for y in 0..buf.area.height {
let mut line = String::new();
for x in 0..buf.area.width {
line.push_str(buf[(x, y)].symbol());
}
out.push_str(line.trim_end());
out.push('\n');
}
(out, hits, buf)
}
fn assert_matches_golden(name: &str, rendered: &str) {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("goldens")
.join(format!("{name}.txt"));
let bless = std::env::var_os("BLESS").is_some();
if bless {
if let Some(dir) = path.parent() {
std::fs::create_dir_all(dir).expect("create goldens dir");
}
std::fs::write(&path, rendered).expect("write golden");
} else {
let committed = std::fs::read_to_string(&path)
.unwrap_or_else(|why| panic!("{}: {why} (BLESS=1 to create)", path.display()));
if committed != rendered {
let at = committed
.lines()
.zip(rendered.lines())
.position(|(a, b)| a != b);
panic!(
"{} drifted at line {:?}.\n golden: {:?}\n rendered: {:?}\nIf the \
frame is right, re-run with BLESS=1.",
path.display(),
at.map(|l| l + 1),
at.and_then(|l| committed.lines().nth(l)),
at.and_then(|l| rendered.lines().nth(l)),
);
}
}
assert!(
!bless,
"BLESS=1 rewrites the goldens; it is not a passing run"
);
}
fn workday_state() -> QueueState {
let mut kek = item("a-kek", "KEK rotation is overdue");
kek.priority = Some(0);
let mut deploy = item("a-deploy", "deploy blocked on grant");
deploy.kind = "authority".into();
deploy.subject_ref = Some("command/cmd-7".into());
deploy.priority = Some(1);
let mut disk = item("a-disk", "disk pressure on the build host");
disk.acked_at = Some(ts("2026-08-06T10:00:00Z"));
let mut flap = item("a-flap", "mcp facade flapping");
flap.muted_until = Some(ts("2026-08-06T18:00:00Z"));
let mut fixed = item("a-fixed", "runner pool exhausted");
fixed.resolved_at = Some(ts("2026-08-06T11:00:00Z"));
fixed.resolution = Some("pool widened".into());
let mut decided = gate("g-old", "push to main?");
decided.verdict = GateVerdict::Pass;
decided.chosen_option = Some("allow".into());
QueueState {
attention: vec![disk, kek, flap, deploy, fixed],
gates: vec![gate("g-1", "rm -rf build/ in worktree?"), decided],
receipts: vec![
receipt("command", "cmd-7", "2026-08-06T09:14:00Z"),
receipt("command", "cmd-7", "2026-08-06T10:20:00Z"),
receipt("command", "cmd-7", "2026-08-06T11:41:00Z"),
receipt("command", "other", "2026-08-06T09:00:00Z"),
],
messages: vec![Message {
id: gwk_domain::ids::MessageId::new("m-1"),
version: 1,
state: gwk_domain::fsm::MessageState::Delivered,
idempotency_key: gwk_domain::ids::IdempotencyKey::new("k-1"),
correlation_id: None,
reply_to: None,
sender: Some("researcher".into()),
recipient: Some("orchestrator".into()),
channel: None,
kind: Some("findings".into()),
payload: None,
deadline: None,
delivery_attempts: 1,
dead_letter_reason: None,
delivery_refs: None,
created_at: ts("2026-08-06T11:58:00Z"),
updated_at: ts("2026-08-06T11:58:00Z"),
}],
watermark: Some(Seq::new(213)),
now: ts(NOW),
}
}
#[test]
fn queue_rank_orders_priority_first_then_byte_order_with_unranked_last() {
let mut b = item("b", "ranked one");
b.priority = Some(1);
let mut a = item("a", "ranked zero late id");
a.priority = Some(0);
let mut z = item("z", "ranked zero");
z.priority = Some(0);
let unranked = item("0-first-by-id", "unranked");
let items = [b, a, z, unranked];
let order: Vec<&str> = ranked(&items).iter().map(|i| i.id.as_str()).collect();
assert_eq!(order, vec!["a", "z", "b", "0-first-by-id"]);
}
#[test]
fn queue_the_all_none_frame_renders_words_not_blank() {
let (dump, _, _) = dump_frame(72, 7, &empty_state(), None);
assert!(
dump.contains("all clear -- nothing needs attention"),
"the verdict line states the all-clear:\n{dump}"
);
assert!(dump.contains("no messages"), "absence in words:\n{dump}");
assert!(dump.contains("nothing recent"), "absence in words:\n{dump}");
assert!(
dump.contains("as-of -"),
"an empty log's watermark is said, not skipped:\n{dump}"
);
assert_matches_golden("queue-empty", &dump);
}
#[test]
fn queue_a_workday_frame_matches_its_golden() {
let state = workday_state();
let selected = QueueTarget::Gate(GateId::new("g-1"));
let (dump, _, _) = dump_frame(72, 14, &state, Some(&selected));
assert_matches_golden("queue-workday", &dump);
}
#[test]
fn queue_a_decided_gate_renders_a_typed_result_never_a_stale_prompt() {
let mut decided = gate("g-done", "grant the deploy?");
decided.verdict = GateVerdict::Pass;
decided.chosen_option = Some("allow".into());
let mut state = empty_state();
state.gates = vec![decided];
let (dump, hits, _) = dump_frame(72, 10, &state, None);
assert!(
dump.contains("decided: allow (pass)"),
"a decided gate is a typed result:\n{dump}"
);
assert!(
!dump.contains("options:"),
"a decided gate must not offer options:\n{dump}"
);
assert!(
!dump.contains("gate permission grant the deploy?"),
"a decided gate must not render as an open prompt:\n{dump}"
);
assert!(
hits.targets().next().is_none(),
"a decided gate is not an actionable row"
);
}
#[test]
fn queue_an_open_gate_never_seizes_the_pane() {
let mut state = empty_state();
state.gates = vec![gate("g-1", "proceed?")];
state.attention = vec![item("a-1", "still visible")];
let (dump, _, _) = dump_frame(72, 10, &state, None);
assert!(dump.contains("proceed?"));
assert!(
dump.contains("still visible"),
"an open gate must not displace the rest of the queue:\n{dump}"
);
}
#[test]
fn queue_the_status_bar_uses_the_admitted_glyph() {
let mut state = empty_state();
state.gates = vec![gate("g-1", "proceed?")];
state.attention = vec![item("a-1", "one more")];
let (dump, _, _) = dump_frame(72, 10, &state, None);
assert_eq!(
dump.lines().nth(9),
Some("QUEUE !2 as-of -"),
"the status bar counts what is audible, in its own cells:\n{dump}"
);
assert!(
!dump.contains('\u{26a0}'),
"U+26A0 is banned from the cell buffer"
);
}
#[test]
fn queue_an_authority_row_counts_its_receipts() {
let state = workday_state();
let (dump, _, _) = dump_frame(72, 14, &state, None);
assert!(
dump.contains("3x since 09:14"),
"the receipt trail is the occurrence count:\n{dump}"
);
let mut plain = item("a-plain", "not authority");
plain.subject_ref = Some("command/cmd-7".into());
assert_eq!(occurrences(&plain, &state.receipts), None);
}
#[test]
fn queue_message_arrival_is_a_standing_row_without_motion() {
let state = workday_state();
let (dump, _, _) = dump_frame(72, 14, &state, None);
assert!(
dump.contains("msg 11:58 researcher to orchestrator (findings)"),
"arrival is a standing row, not a motion event:\n{dump}"
);
}
#[test]
fn queue_resolved_items_demote_to_recent_under_the_rearm_header() {
let state = workday_state();
let (dump, _, _) = dump_frame(72, 14, &state, None);
let recent_at = dump
.find("recent -- resolve is re-arm")
.expect("the recent header says what demotion means");
let resolved_at = dump
.find("runner pool exhausted")
.expect("the resolved item renders");
assert!(
resolved_at > recent_at,
"a resolved item lives below the recent header, not among the live rows"
);
}
#[test]
fn queue_muted_and_acked_rows_stay_visible_in_words() {
let state = workday_state();
let (dump, _, _) = dump_frame(72, 14, &state, None);
assert!(
dump.contains("disk pressure on the build host acked"),
"an acked row is quiet, not gone, summary first:\n{dump}"
);
assert!(
dump.contains("mcp facade flapping muted until 18:00"),
"a muted row says when it comes back, after the summary:\n{dump}"
);
let mut woke = item("a-woke", "was muted");
woke.muted_until = Some(ts("2026-08-06T11:00:00Z"));
assert!(audible(&woke, &ts(NOW)));
}
#[test]
fn queue_selection_is_a_visible_marker_in_column_zero() {
let mut state = empty_state();
state.attention = vec![item("a-1", "the row")];
let target = QueueTarget::Attention(AttentionItemId::new("a-1"));
let (_, _, buf) = dump_frame(40, 6, &state, Some(&target));
let accent = &buf[(0, 1)];
assert_eq!(accent.symbol(), ">", "selection survives symbol goldens");
assert!(
accent.style().add_modifier.contains(Modifier::BOLD),
"the marker keeps the focus emphasis"
);
let unselected = &buf[(0, 0)];
assert_eq!(
unselected.symbol(),
" ",
"only selection carries the marker"
);
}
#[test]
fn queue_clicks_land_on_rows_and_a_miss_stays_a_miss() {
let mut state = empty_state();
state.attention = vec![item("a-1", "clickable")];
let (_, hits, _) = dump_frame(40, 6, &state, None);
assert_eq!(
hits.hit(5, 1),
Some(&QueueTarget::Attention(AttentionItemId::new("a-1"))),
"the attention row is a click target"
);
assert_eq!(hits.hit(5, 0), None, "the verdict line is not actionable");
assert_eq!(hits.hit(5, 2), None, "one row off is a miss");
}
#[test]
fn queue_ack_and_mute_build_the_typed_commands_and_refuse_gates() {
let attention = QueueTarget::Attention(AttentionItemId::new("a-1"));
let gate_target = QueueTarget::Gate(GateId::new("g-1"));
assert_eq!(
ack(&attention),
Some(KernelCommand::AckAttention {
attention_item_id: AttentionItemId::new("a-1"),
})
);
let until = ts("2026-08-06T13:00:00Z");
assert_eq!(
mute(&attention, until.clone()),
Some(KernelCommand::MuteAttention {
attention_item_id: AttentionItemId::new("a-1"),
muted_until: until,
})
);
assert_eq!(
unmute(&attention),
Some(KernelCommand::UnmuteAttention {
attention_item_id: AttentionItemId::new("a-1"),
})
);
assert_eq!(
resolve(&attention, Some("fixed in #71".into())),
Some(KernelCommand::ResolveAttention {
attention_item_id: AttentionItemId::new("a-1"),
resolution: Some("fixed in #71".into()),
})
);
assert_eq!(
resolve(&attention, None),
Some(KernelCommand::ResolveAttention {
attention_item_id: AttentionItemId::new("a-1"),
resolution: None,
}),
"a close with no words is still a close, never an empty string"
);
for refused in [
ack(&gate_target),
mute(&gate_target, ts(NOW)),
unmute(&gate_target),
resolve(&gate_target, None),
] {
assert_eq!(refused, None);
}
}
#[test]
fn queue_gate_options_map_to_typed_verdicts_without_guessing_unknown_words() {
let gate = gate("g-1", "ship it?");
assert_eq!(
decide(&gate, "allow"),
Some(KernelCommand::DecideGate {
gate_id: GateId::new("g-1"),
expected_version: 1,
verdict: GateVerdict::Pass,
chosen_option: Some("allow".into()),
evidence_ref: None,
})
);
assert_eq!(verdict_for_option("deny"), Some(GateVerdict::Fail));
assert_eq!(verdict_for_option("hold"), Some(GateVerdict::Partial));
for option in ["once", "always", "accept", "acceptForSession"] {
assert_eq!(verdict_for_option(option), Some(GateVerdict::Pass));
}
for option in ["reject", "decline", "cancel"] {
assert_eq!(verdict_for_option(option), Some(GateVerdict::Fail));
}
assert_eq!(verdict_for_option("ask"), Some(GateVerdict::Partial));
assert_eq!(verdict_for_option("restart later"), None);
assert_eq!(decide(&gate, "not offered"), None);
}
#[test]
fn queue_reaches_every_attention_verb_the_contract_carries() {
let attention = QueueTarget::Attention(AttentionItemId::new("a-1"));
let issued: Vec<&'static str> = [
ack(&attention),
mute(&attention, ts(NOW)),
unmute(&attention),
resolve(&attention, None),
]
.into_iter()
.map(|command| command.expect("attention verb").command_type())
.collect();
assert_eq!(
issued,
[
"ack_attention",
"mute_attention",
"unmute_attention",
"resolve_attention"
]
);
}
#[test]
fn queue_overflow_names_the_cut() {
let mut state = empty_state();
state.attention = (0..12)
.map(|i| item(&format!("a-{i:02}"), &format!("problem {i}")))
.collect();
let (dump, _, _) = dump_frame(40, 6, &state, None);
assert!(
dump.contains("+12 more"),
"an overflowing body names the cut instead of silently truncating:\n{dump}"
);
}
#[test]
fn queue_every_cell_glyph_is_ascii_or_an_admitted_mark() {
let (dump, _, _) = dump_frame(72, 14, &workday_state(), None);
for ch in dump.chars() {
assert!(
ch.is_ascii() || gwk_theme::marks::is_admissible(ch),
"unadmitted glyph {ch:?} in the cell buffer"
);
}
}
#[test]
fn queue_unsafe_wire_glyphs_are_escaped_before_paint() {
let mut state = empty_state();
state.attention = vec![item("a-unsafe", "unsafe ◆ 你好 ⚠")];
let (dump, _, _) = dump_frame(96, 8, &state, None);
for unsafe_glyph in ['◆', '你', '好', '⚠'] {
assert!(
!dump.contains(unsafe_glyph),
"unsafe glyph {unsafe_glyph:?} reached the buffer:\n{dump}"
);
}
assert!(
dump.contains("\\u{25C6}"),
"the value stays retypable:\n{dump}"
);
assert!(
dump.contains("\\u{4F60}"),
"the value stays retypable:\n{dump}"
);
}
#[test]
fn queue_ack_quiets_the_count_with_the_row() {
let mut seen = item("a-seen", "already seen");
seen.acked_at = Some(ts("2026-08-06T10:00:00Z"));
let mut state = empty_state();
state.attention = vec![seen, item("a-loud", "still loud")];
let (dump, _, _) = dump_frame(60, 8, &state, None);
assert!(
dump.contains("1 need attention"),
"the acked item leaves the count:\n{dump}"
);
assert!(
dump.contains("!1"),
"the status bar agrees with the verdict line:\n{dump}"
);
assert!(
dump.contains("already seen acked"),
"the acked row is quiet, not gone:\n{dump}"
);
}
#[test]
fn queue_selection_layers_the_foreground_and_the_mark_keeps_its_colour() {
let mut state = empty_state();
state.attention = vec![item("a-1", "the row")];
let target = QueueTarget::Attention(AttentionItemId::new("a-1"));
let (_, _, buf) = dump_frame_tier(40, 6, &state, Some(&target), ColorTier::Truecolor);
let warn_fg = theme::state_style(binding("needs_attention"), ColorTier::Truecolor).fg;
let selection_fg = gwk_theme::SIGNAL
.iter()
.find(|t| t.name == "gws_selection")
.and_then(|t| theme::token_style(t, ColorTier::Truecolor).fg);
assert!(warn_fg.is_some() && selection_fg.is_some(), "tier emits fg");
assert_eq!(buf[(1, 1)].style().fg, warn_fg, "the mark keeps its state");
assert_eq!(
buf[(3, 1)].style().fg,
selection_fg,
"the text takes the selection foreground"
);
assert_eq!(
buf[(0, 1)].style().fg,
selection_fg,
"the ratified token paints ON TOP of the accent"
);
let (_, _, mono) = dump_frame_tier(40, 6, &state, Some(&target), ColorTier::Mono);
let accent = &mono[(0, 1)];
assert_eq!(accent.symbol(), ">");
assert!(
accent.style().add_modifier.contains(Modifier::REVERSED),
"the selection token's reverse-video expression rides the accent at mono"
);
}
#[test]
fn queue_a_narrow_area_stays_inside_its_rect() {
let mut state = empty_state();
state.attention = vec![item("a-1", "a row wider than the pane")];
let target = QueueTarget::Attention(AttentionItemId::new("a-1"));
let mut hits = HitMap::new();
let mut buf = Buffer::empty(Rect::new(0, 0, 40, 6));
render(
Rect::new(0, 0, 2, 6),
&mut buf,
&state,
Some(&target),
ColorTier::Mono,
GlyphSet::Unicode,
&mut hits,
);
for y in 0..6 {
for x in 2..40 {
assert_eq!(
buf[(x, y)].symbol(),
" ",
"painted outside its area at ({x},{y})"
);
}
}
let mut empty_buf = Buffer::empty(Rect::new(0, 0, 40, 6));
render(
Rect::new(0, 0, 0, 6),
&mut empty_buf,
&state,
Some(&target),
ColorTier::Mono,
GlyphSet::Unicode,
&mut hits,
);
assert_eq!(empty_buf, Buffer::empty(Rect::new(0, 0, 40, 6)));
}
}