[pitboss] phase 23: Track F.4 — nyx surface subcommand + human-readable output

This commit is contained in:
pitboss 2026-05-15 14:19:45 -05:00
parent 66a59200ae
commit 655ec45b21
13 changed files with 1248 additions and 1 deletions

View file

@ -8,6 +8,7 @@ pub mod health;
pub mod overview;
pub mod rules;
pub mod scans;
pub mod surface;
pub mod triage;
use crate::server::app::AppState;
@ -26,5 +27,6 @@ pub fn api_routes() -> Router<AppState> {
.merge(triage::routes())
.merge(overview::routes())
.merge(explorer::routes())
.merge(surface::routes())
.merge(debug::routes())
}

View file

@ -0,0 +1,43 @@
//! `GET /api/surface` — serve the project's [`SurfaceMap`].
//!
//! Loads the map persisted by the most recent indexed scan from
//! SQLite, falling back to building a fresh entry-point-only map from
//! the on-disk source when no scan has populated one yet. The
//! response shape is the canonical `SurfaceMap` JSON — identical to
//! `nyx surface --format json` — so the frontend can reuse the same
//! deserialisation in both surfaces.
use crate::commands::surface::load_or_build;
use crate::server::app::AppState;
use crate::server::error::{ApiError, ApiResult};
use axum::extract::State;
use axum::routing::get;
use axum::{Json, Router};
use serde_json::Value;
pub fn routes() -> Router<AppState> {
Router::new().route("/surface", get(get_surface))
}
async fn get_surface(State(state): State<AppState>) -> ApiResult<Json<Value>> {
let scan_root = state.scan_root.clone();
let database_dir = state.database_dir.clone();
let cfg = state.config.read().clone();
// Building the surface map can do filesystem IO + tree-sitter
// parsing; keep it off the async runtime.
let join_result = tokio::task::spawn_blocking(move || {
load_or_build(&scan_root, &database_dir, &cfg)
})
.await
.map_err(|e| ApiError::internal(format!("surface map task failed: {e}")))?;
let mut map = join_result
.map_err(|e| ApiError::internal(format!("failed to build surface map: {e}")))?;
let bytes = map
.to_json()
.map_err(|e| ApiError::internal(format!("encode surface map: {e}")))?;
let value: Value = serde_json::from_slice(&bytes)
.map_err(|e| ApiError::internal(format!("re-parse surface map JSON: {e}")))?;
Ok(Json(value))
}