Re-skins the whole app to the dark design comps and wires the new surfaces to
the backend (the /api proxy + auth + schemas are unchanged).
Design system:
- globals.css: remapped @theme tokens to the comp palette (#08080a base, coral
#ff6f61, status cyan/green/amber/purple/teal); token names preserved
- MeshMark: triangle + 3-node brand glyph; cm-flow/cm-blink/cm-halo keyframes
- marketing flipped light → dark
Backend (migration 0012):
- agents.model_binding (persisted on team deploy) + GET /api/claws/{id}/runtime-config
- routine_runs table + scheduler journaling + GET /api/routines/runs
- GET /api/claws/{id}/compartments (anatomy aggregate)
- GET /api/structure/stats (workspace counts)
Frontend:
- Landing: full dark marketing page (hero constellation, deploy ladder,
12-topology taxonomy, recursive execution, compare/Pareto, safety, self-host)
- Auth: dark split-panel AuthShell + comp LoginForm + Clerk SignIn themed dark
- Dashboard shell: TopBar (breadcrumb + live stats + deploy + user) + StatusBar
(runner/sandbox/doors); rail slimmed to 60px + 252px context column
- ConstellationCanvas (radial recursive) replaces the graph view in StructureCanvas;
selecting a claw opens ComputerPanel (apps/now-running/dock); RoutinesPanel
- Claw anatomy view (/claws/[id]/anatomy) from compartments + runtime-config
Co-Authored-By: Claude Opus 4.8 <[email protected]>
110 lines
3.2 KiB
Rust
110 lines
3.2 KiB
Rust
//! Journal of routine firings — one row per execution, powering the dashboard
|
|
//! routines panel's run history + live ("now running") loops.
|
|
|
|
use serde_json::Value;
|
|
use sqlx::PgPool;
|
|
use time::OffsetDateTime;
|
|
use uuid::Uuid;
|
|
|
|
use crate::DbError;
|
|
|
|
/// One routine execution.
|
|
#[derive(Debug, Clone, serde::Serialize)]
|
|
pub struct RoutineRun {
|
|
pub id: Uuid,
|
|
pub routine_id: Uuid,
|
|
pub status: String, // running | ok | error
|
|
#[serde(with = "time::serde::rfc3339")]
|
|
pub started_at: OffsetDateTime,
|
|
#[serde(with = "time::serde::rfc3339::option")]
|
|
pub completed_at: Option<OffsetDateTime>,
|
|
pub error: Option<String>,
|
|
}
|
|
|
|
/// Record the start of a routine firing; returns the run id to finish later.
|
|
pub async fn start(pool: &PgPool, routine_id: Uuid) -> Result<Uuid, DbError> {
|
|
let id = Uuid::now_v7();
|
|
sqlx::query!(
|
|
"INSERT INTO routine_runs (id, routine_id) VALUES ($1, $2)",
|
|
id,
|
|
routine_id,
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(id)
|
|
}
|
|
|
|
/// Mark a routine run terminal (`ok`/`error`).
|
|
pub async fn finish(
|
|
pool: &PgPool,
|
|
id: Uuid,
|
|
status: &str,
|
|
error: Option<&str>,
|
|
) -> Result<(), DbError> {
|
|
sqlx::query!(
|
|
"UPDATE routine_runs SET status = $2, completed_at = now(), error = $3 WHERE id = $1",
|
|
id,
|
|
status,
|
|
error,
|
|
)
|
|
.execute(pool)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Recent runs for one routine, newest first.
|
|
pub async fn list_by_routine(
|
|
pool: &PgPool,
|
|
routine_id: Uuid,
|
|
limit: i64,
|
|
) -> Result<Vec<RoutineRun>, DbError> {
|
|
let rows = sqlx::query_as!(
|
|
RoutineRun,
|
|
r#"SELECT id, routine_id, status, started_at, completed_at, error
|
|
FROM routine_runs WHERE routine_id = $1
|
|
ORDER BY started_at DESC LIMIT $2"#,
|
|
routine_id,
|
|
limit,
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
Ok(rows)
|
|
}
|
|
|
|
/// Recent runs across all of a workspace's routines (the panel's run history).
|
|
/// Joined through `routines` → `agents` to scope to the workspace. Each row also
|
|
/// carries its routine name for display.
|
|
pub async fn recent_for_workspace(
|
|
pool: &PgPool,
|
|
workspace_id: Uuid,
|
|
limit: i64,
|
|
) -> Result<Vec<Value>, DbError> {
|
|
let rows = sqlx::query!(
|
|
r#"SELECT rr.id, rr.routine_id, r.name AS routine_name, rr.status,
|
|
rr.started_at, rr.completed_at, rr.error
|
|
FROM routine_runs rr
|
|
JOIN routines r ON r.id = rr.routine_id
|
|
JOIN agents a ON a.id = r.agent_id
|
|
WHERE a.workspace_id = $1
|
|
ORDER BY rr.started_at DESC LIMIT $2"#,
|
|
workspace_id,
|
|
limit,
|
|
)
|
|
.fetch_all(pool)
|
|
.await?;
|
|
Ok(rows
|
|
.into_iter()
|
|
.map(|r| {
|
|
serde_json::json!({
|
|
"id": r.id,
|
|
"routine_id": r.routine_id,
|
|
"routine_name": r.routine_name,
|
|
"status": r.status,
|
|
"started_at": r.started_at.format(&time::format_description::well_known::Rfc3339).unwrap_or_default(),
|
|
"completed_at": r.completed_at.and_then(|t| t.format(&time::format_description::well_known::Rfc3339).ok()),
|
|
"error": r.error,
|
|
})
|
|
})
|
|
.collect())
|
|
}
|