research: pipeline-running signal + spinners so users aren't guessing
ci / gates (push) Successful in 7s
ci / frontend (push) Failing after 19s
ci / rust (push) Successful in 4m0s
ci / e2e (push) Has been skipped
ci / publish (push) Has been skipped

The prior flow was ambiguous: after hitting Start research, status
flipped to "processing" and a "Submit for review" button appeared
immediately with no indication that anything was actually running.
Users had to guess whether the pipeline was working or stalled.

Backend surfaces the truth as a signal:
- new topology_runs::active_runs_for_research_topic counts
  queued+running runs whose research_topic_id matches
- TopicDetail includes runs_in_flight: i64 alongside the existing
  status field, so the canvas can distinguish "pipeline still
  working" from "runner stalled".

ResearchCanvas is now honest about state:
- while runs_in_flight > 0, the header status pill grows a cyan
  "N runs in flight" badge with an inline SVG spinner
- the stage-explainer card turns cyan-bordered and shows a
  "pipeline is running" hint, plus copy pointing the user at the
  Agents tier where each teammate's activity streams live
- the "Submit for review (manual)" button is HIDDEN while any run
  is in flight — it's an escape hatch for stalled runs only, not
  the happy-path action. It reappears if runs_in_flight drops to
  zero but the topic is still marked processing, so a stalled
  runner can still be nudged along.
- the canvas polls getTopic every 4s while status is processing/
  publishing or runs_in_flight > 0, so the spinner + outcome swap
  in automatically when the pipeline completes.

ResearchList sidebar:
- each row's status dot becomes a spinner when the topic's status
  is processing or publishing, matching the canvas at a glance
- the list also polls every 6s while ANY topic is active, so
  transitions land in the sidebar without waiting on a parent bump.
  The poll is gated on a derived boolean to avoid effect thrash.

Follow-up: same pattern belongs on LoopsList / LoopsCanvas for
loop iterations in flight — same signal (queued+running runs per
loop) but not wired here.
This commit is contained in:
Omar Sobh
2026-07-08 18:36:34 -07:00
parent 316cdbf929
commit 7c1af2e070
6 changed files with 247 additions and 22 deletions
+8
View File
@@ -245,6 +245,11 @@ pub struct TopicDetail {
/// beyond so reviewers see what actually needs approval.
#[serde(skip_serializing_if = "Option::is_none")]
pub latest_outcome: Option<cm_db::repo::research_outcomes::Outcome>,
/// queued + running topology_runs bound to this topic. > 0 means the
/// pipeline is still working — the canvas shows a running badge with a
/// spinner and hides the manual "Submit for review" button, which is
/// only offered when this is 0 (as an escape hatch for stalled runs).
pub runs_in_flight: i64,
}
pub async fn get_topic(
@@ -261,11 +266,14 @@ pub async fn get_topic(
.await?
.is_some();
let latest_outcome = cm_db::repo::research_outcomes::latest(&state.pool, id).await?;
let runs_in_flight =
cm_db::repo::topology_runs::active_runs_for_research_topic(&state.pool, id).await?;
Ok(Json(TopicDetail {
topic,
agents,
has_pending_publish_request,
latest_outcome,
runs_in_flight,
}))
}