dashboard: surface peer questions + operator override

questions pane now shows both operator-targeted threads
(target IS NULL) and agent-to-agent threads (target = some
agent). filter chips above the list: all / @operator / @peer /
per-participant. peer rows get a mauve left rule + a 0V3RR1D3
button that POSTs the same /answer-question endpoint
(OperatorQuestions::answer already permits the operator as
answerer on any target).

wire changes: OperatorQuestions gains pending_all +
recent_answered_all; QuestionAdded + QuestionResolved events
carry target: Option<String>; emit sites drop their
target.is_none() guard. answered-history rows show the
answerer prefix so override answers are auditable at a glance.
This commit is contained in:
müde 2026-05-17 22:06:53 +02:00
parent e7ce35c503
commit a15fafb5de
9 changed files with 187 additions and 71 deletions

View file

@ -86,9 +86,10 @@ pub fn handle_ask(
multi,
},
);
} else {
coord.emit_question_added(id, asker, question, options, multi, deadline_at);
}
// Always fire on the dashboard channel — both operator-targeted
// and peer threads now surface in the dashboard's questions pane.
coord.emit_question_added(id, asker, question, options, multi, deadline_at, target);
if let Some(t) = ttl {
spawn_question_watchdog(coord, id, t);
}
@ -120,13 +121,11 @@ pub fn handle_answer(
answerer: answerer.to_owned(),
},
);
// Only operator-targeted questions surface on the dashboard;
// peer-to-peer answers are invisible to it. `cancelled = false`
// because this path is a real answer (operator cancel goes
// through `post_cancel_question` directly).
if target.is_none() {
coord.emit_question_resolved(id, answer, answerer, false);
}
// Dashboard surfaces both operator-targeted and peer threads;
// emit unconditionally so the derived store moves the row.
// `cancelled = false` because this path is a real answer (the
// operator-cancel button goes through `post_cancel_question`).
coord.emit_question_resolved(id, answer, answerer, false, target.as_deref());
Ok(())
}