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.