research canvas: collapsible sidebar + live topology-run logs
Two operator-visibility improvements on the research canvas: **A. Collapsible sidebar (RosterColumn)** - Adds a PanelLeftClose/Open chevron toggle on the roster column header. When collapsed the 252px column shrinks to a 32px handle, giving the main canvas the extra real estate. - Persists the choice in localStorage (cm.sidebar.rosterCollapsed) so it sticks across nav and reloads. Client-only hydration keeps SSR + client render in sync. **B. Live run logs panel** - New LiveRunLogs component under ResearchCanvas that renders when the topic has at least one active topology_run. Reuses the existing SSE endpoint /api/topology-runs/:id/events (Last-Event-ID resume already supported), so no backend streaming work needed. - Multi-run support (rare — usually one, but topology fan-out can produce N): tab strip picks which run's stream to view; one EventSource at a time. - Terminal-style panel with timestamped lines, auto-scroll pinned to the tail unless the user scrolls up, 'Jump to latest' button when unpinned. Header pill mirrors stream state (connecting/streaming/done/error). Small backend addition: - cm_db::repo::topology_runs::active_run_ids_for_research_topic — companion to active_runs_for_research_topic that returns ids. Uses dynamic sqlx::query() so cm-db builds air-gapped without a fresh cargo sqlx prepare. - GET /api/research/:id/active-runs — workspace-scoped list feeder for the new panel.
This commit is contained in:
@@ -167,6 +167,31 @@ pub async fn active_runs_for_research_topic(
|
||||
Ok(row.n.unwrap_or(0))
|
||||
}
|
||||
|
||||
/// Live-run panel companion to `active_runs_for_research_topic`: return
|
||||
/// the actual run ids (queued + running) so the UI can subscribe to
|
||||
/// their SSE event streams. Ordered newest first — the freshest run is
|
||||
/// the one the user just kicked off.
|
||||
pub async fn active_run_ids_for_research_topic(
|
||||
pool: &PgPool,
|
||||
research_topic_id: Uuid,
|
||||
) -> Result<Vec<Uuid>, DbError> {
|
||||
use sqlx::Row;
|
||||
// Dynamic query (not `sqlx::query!`) so cm-db builds air-gapped
|
||||
// without a fresh `cargo sqlx prepare` round-trip. Schema shape is
|
||||
// identical to `active_runs_for_research_topic` above.
|
||||
let rows: Vec<sqlx::postgres::PgRow> = sqlx::query(
|
||||
"SELECT id
|
||||
FROM topology_runs
|
||||
WHERE research_topic_id = $1
|
||||
AND status IN ('queued', 'running')
|
||||
ORDER BY created_at DESC",
|
||||
)
|
||||
.bind(research_topic_id)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
Ok(rows.into_iter().map(|r| r.get::<Uuid, _>("id")).collect())
|
||||
}
|
||||
|
||||
/// The research topic this run belongs to, if any. Used by the topology
|
||||
/// worker's `freeze_research_outcome` post-hook to snapshot the run's
|
||||
/// final synthesis into `research_outcomes`.
|
||||
|
||||
Reference in New Issue
Block a user