research canvas: wider rail + stage explainer + 409 fix
ci / gates (push) Successful in 7s
ci / frontend (push) Successful in 38s
ci / rust (push) Successful in 3m13s
ci / e2e (push) Has been skipped
ci / publish (push) Successful in 3m58s

Three connected fixes from a single session's feedback:

- Structure rail widened from 60→76 px, tabs 42→58 px wide with a
  bit of left padding so labels ("Research", "Visualizations") no
  longer bump against the active-tab indicator strip.
- Research canvas: each stage now shows a small "STAGE · <status>"
  card explaining what state the topic is in and a "Next → …" hint
  describing what the primary button will do. No more guessing
  which of standby/processing/reviewing/publishing means what.
- Request-publish 409 fix:
    - Backend TopicDetail now includes has_pending_publish_request
      (SELECTs pending_for_topic when the topic loads). Frontend
      TopicDetail interface + ResearchCanvas honor the flag: when
      an approval is already pending the "Request publish" button
      is replaced with an amber "Awaiting reviewer approval" pill,
      so double-clicks can't 409 in the first place.
    - runAction() also catches 409 as a signal-of-success (the
      user's intent — "queue for approval" — is satisfied by the
      first attempt), refetches the topic, and lets the new
      awaiting-approval card render instead of surfacing a scary
      error to the user.
This commit is contained in:
Omar Sobh
2026-07-08 15:59:04 -07:00
parent 92e923e585
commit 81a436d221
4 changed files with 122 additions and 29 deletions
+14 -1
View File
@@ -138,6 +138,11 @@ pub struct TopicDetail {
#[serde(flatten)]
pub topic: cm_db::repo::research_topics::ResearchTopic,
pub agents: Vec<cm_db::repo::research_topics::AgentSlot>,
/// True when a publish approval is already pending for this topic.
/// Lets the frontend hide the "Request publish" button and show
/// "Awaiting reviewer approval" instead — avoids the 409 the user
/// gets from double-clicking.
pub has_pending_publish_request: bool,
}
pub async fn get_topic(
@@ -149,7 +154,15 @@ pub async fn get_topic(
.await?
.ok_or(ApiError::NotFound)?;
let agents = cm_db::repo::research_topics::agents(&state.pool, id).await?;
Ok(Json(TopicDetail { topic, agents }))
let has_pending_publish_request =
cm_db::repo::research_publish_approvals::pending_for_topic(&state.pool, id)
.await?
.is_some();
Ok(Json(TopicDetail {
topic,
agents,
has_pending_publish_request,
}))
}
#[derive(Deserialize)]