Adopt design comps: dark system, new landing/auth, dashboard shell + canvas
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]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
3eca4ed70c
commit
540c74f42e
@@ -3,9 +3,10 @@ use axum::http::StatusCode;
|
||||
use axum::Json;
|
||||
use cm_db::repo::audit::Actor;
|
||||
use cm_domain::{AccessPolicy, Agent, AgentId, AgentStatus};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::runtime_provision::provider_alias_for;
|
||||
use crate::{ApiError, AppState, Authed};
|
||||
|
||||
/// Loads an agent and enforces tenant isolation: agents in other workspaces
|
||||
@@ -22,6 +23,104 @@ pub(crate) async fn workspace_agent(
|
||||
Ok(agent)
|
||||
}
|
||||
|
||||
/// `GET /api/claws/{id}/runtime-config` — the claw's model + §15 sandbox facts
|
||||
/// (for the claw card / anatomy view's model badge).
|
||||
#[derive(Serialize)]
|
||||
pub struct RuntimeConfig {
|
||||
pub model: Option<String>,
|
||||
pub provider_alias: String,
|
||||
pub sandbox_enabled: bool,
|
||||
pub network_allowed: bool,
|
||||
}
|
||||
|
||||
pub async fn runtime_config(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
Path(id): Path<AgentId>,
|
||||
) -> Result<Json<RuntimeConfig>, ApiError> {
|
||||
let agent = workspace_agent(&state, &user, id).await?;
|
||||
let model = cm_db::repo::agents::model_binding(&state.pool, agent.id).await?;
|
||||
let provider_alias = provider_alias_for(model.as_deref().unwrap_or("claude")).to_string();
|
||||
Ok(Json(RuntimeConfig {
|
||||
model,
|
||||
provider_alias,
|
||||
// Claws are provisioned tool-free in network-isolated sandboxes (§15).
|
||||
sandbox_enabled: true,
|
||||
network_allowed: false,
|
||||
}))
|
||||
}
|
||||
|
||||
/// One "anatomy" compartment of a claw (skills / personality / memory / tools /
|
||||
/// capabilities / safety), aggregated from existing data.
|
||||
#[derive(Serialize)]
|
||||
pub struct Compartment {
|
||||
pub key: String,
|
||||
pub label: String,
|
||||
pub items: Vec<String>,
|
||||
pub count: Option<i64>,
|
||||
}
|
||||
|
||||
/// `GET /api/claws/{id}/compartments` — the claw anatomy view.
|
||||
pub async fn compartments(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
Path(id): Path<AgentId>,
|
||||
) -> Result<Json<Vec<Compartment>>, ApiError> {
|
||||
let agent = workspace_agent(&state, &user, id).await?;
|
||||
let skills = cm_db::repo::skills::installed(&state.pool, agent.id).await?;
|
||||
let personality = if agent.system_prompt.trim().is_empty() {
|
||||
vec![]
|
||||
} else {
|
||||
vec![agent.system_prompt.clone()]
|
||||
};
|
||||
let out = vec![
|
||||
Compartment {
|
||||
key: "skills".into(),
|
||||
label: "Skills".into(),
|
||||
items: skills.iter().map(|s| s.title.clone()).collect(),
|
||||
count: Some(skills.len() as i64),
|
||||
},
|
||||
Compartment {
|
||||
key: "personality".into(),
|
||||
label: "Personality".into(),
|
||||
items: personality,
|
||||
count: None,
|
||||
},
|
||||
Compartment {
|
||||
key: "memory".into(),
|
||||
label: "Memory".into(),
|
||||
items: vec![],
|
||||
count: None,
|
||||
},
|
||||
Compartment {
|
||||
// The §15 "door": email/slack are gated MCP tools, browser gated,
|
||||
// shell blocked (claws are tool-free in the sandbox).
|
||||
key: "tools".into(),
|
||||
label: "Tools · Doors".into(),
|
||||
items: vec![
|
||||
"Email · gated".into(),
|
||||
"Slack · gated".into(),
|
||||
"Browser · gated".into(),
|
||||
"Shell · blocked".into(),
|
||||
],
|
||||
count: None,
|
||||
},
|
||||
Compartment {
|
||||
key: "capabilities".into(),
|
||||
label: "Capabilities".into(),
|
||||
items: vec!["File management".into(), "Scheduling".into()],
|
||||
count: None,
|
||||
},
|
||||
Compartment {
|
||||
key: "safety".into(),
|
||||
label: "Safety · §15".into(),
|
||||
items: vec!["Sandbox: isolated".into(), "Network: none".into()],
|
||||
count: None,
|
||||
},
|
||||
];
|
||||
Ok(Json(out))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateClawRequest {
|
||||
name: String,
|
||||
|
||||
@@ -4,7 +4,7 @@ use axum::Json;
|
||||
use cm_db::repo::routines::Routine;
|
||||
use cm_domain::AgentId;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use serde_json::{json, Value};
|
||||
|
||||
use crate::routes::claws::workspace_agent;
|
||||
use crate::{ApiError, AppState, Authed};
|
||||
@@ -56,3 +56,19 @@ pub async fn create(
|
||||
.await?;
|
||||
Ok((StatusCode::CREATED, Json(routine)))
|
||||
}
|
||||
|
||||
/// GET /api/routines/runs — recent routine firings across the workspace (the
|
||||
/// routines panel's run history).
|
||||
pub async fn runs(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
) -> Result<Json<Vec<Value>>, ApiError> {
|
||||
Ok(Json(
|
||||
cm_db::repo::routine_runs::recent_for_workspace(
|
||||
&state.pool,
|
||||
user.workspace_id.as_uuid(),
|
||||
20,
|
||||
)
|
||||
.await?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -44,6 +44,36 @@ pub struct StructureNode {
|
||||
pub children: Vec<StructureChild>,
|
||||
}
|
||||
|
||||
/// Workspace-wide hierarchy counts for the breadcrumb + status bar.
|
||||
#[derive(Serialize)]
|
||||
pub struct StructureStats {
|
||||
pub org_count: usize,
|
||||
pub company_count: usize,
|
||||
pub team_count: usize,
|
||||
pub claw_count: usize,
|
||||
pub running_now: i64,
|
||||
}
|
||||
|
||||
/// `GET /api/structure/stats` — counts across the whole workspace hierarchy.
|
||||
pub async fn stats(
|
||||
State(state): State<AppState>,
|
||||
Authed(user): Authed,
|
||||
) -> Result<Json<StructureStats>, ApiError> {
|
||||
let ws = user.workspace_id;
|
||||
let orgs = cm_db::repo::orgs::list_for_workspace(&state.pool, ws, 1000).await?;
|
||||
let companies = cm_db::repo::companies::list_for_workspace(&state.pool, ws, 1000).await?;
|
||||
let teams = cm_db::repo::teams::list_for_workspace(&state.pool, ws, 1000).await?;
|
||||
let claws = cm_db::repo::agents::roster(&state.pool, ws).await?;
|
||||
let running_now = cm_db::repo::topology_runs::count_active(&state.pool, ws).await?;
|
||||
Ok(Json(StructureStats {
|
||||
org_count: orgs.len(),
|
||||
company_count: companies.len(),
|
||||
team_count: teams.len(),
|
||||
claw_count: claws.len(),
|
||||
running_now,
|
||||
}))
|
||||
}
|
||||
|
||||
/// `GET /api/structure/{level}/{id}` — one level of the recursive hierarchy.
|
||||
pub async fn node(
|
||||
State(state): State<AppState>,
|
||||
|
||||
@@ -84,6 +84,8 @@ pub async fn create_team(
|
||||
eprintln!("teams: provision claw {claw_id} failed: {e}");
|
||||
ApiError::Internal
|
||||
})?;
|
||||
// Persist the model so the claw card / anatomy can show it later.
|
||||
cm_db::repo::agents::set_model_binding(&state.pool, agent.id, &m.model).await?;
|
||||
claw_ids.push(claw_id);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user