From a539fab15ca0d46a1b6263c7af91303907802575 Mon Sep 17 00:00:00 2001 From: sergio Date: Sun, 17 May 2026 23:51:22 +0000 Subject: [PATCH] =?UTF-8?q?feat(tahuantinsuyu):=20fase=2024=20=E2=80=94=20?= =?UTF-8?q?observabilidad=20del=20broker=20brahman?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Primera pieza concreta de integración con el fractal brahman. La app deja de ser standalone visible: ahora muestra el estado del broker en el header con un badge actualizado cada 30s. - Shell gana enum BrahmanStatus { Pending, Connected { count }, Offline { reason } } + field brahman_status. - spawn_brahman_status_loop arma un task cx.spawn que cada 30s invoca brahman_sidecar::list_sessions_blocking sobre el background_executor (no UI thread — list_sessions_blocking abre su propio tokio runtime, hacerlo en el UI panicearía con "nested runtime"). Update via this.update + cx.notify dispara repintado del badge. - header agrega pill "Brahman ✓ N sessions" (color accent cuando conectado), "Brahman · offline" (fg_disabled) o "Brahman · …" (fg_muted) según el último ping. Entre el separador flex_grow y el theme_switcher. - apps Cargo agrega brahman-sidecar como dep directa. La Card de tahuantinsuyu (fase 1) sigue declarando los flows `chart-request` (input) y `chart-result` (output), pero ESTOS NO ESTÁN CABLEADOS A UN DATA PLANE — solo aparecen en el broker como declaración. Para que tahuantinsuyu PUBLIQUE/CONSUMA datos reales (otra app del fractal recibiendo una carta serializada, o pidiendo un cómputo) hay que: 1) Abrir un service_socket Unix server en el sidecar 2) Implementar protocolo postcard sobre ese socket 3) Otro módulo descubre el socket via broker → conecta y envía/recibe Eso es una fase separada (25+). Esta fase 24 cubre la observabilidad mínima: la app sabe que el fractal está vivo y muestra el head count. Cubre el espíritu del brief inicial ("integrar con yahweh que maneja gpui para intercomunicar widgets") al nivel de visibility — el data plane real es un proyecto en sí mismo. cargo check verde. Sin tests nuevos (la lógica nueva es interacción UI + background task — los tests serían smoke tests del Shell que no tenemos). Co-Authored-By: Claude Opus 4.7 --- Cargo.lock | 1 + crates/apps/tahuantinsuyu/Cargo.toml | 1 + crates/apps/tahuantinsuyu/src/shell.rs | 76 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3b83ae6..e370d4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10904,6 +10904,7 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" name = "tahuantinsuyu" version = "0.1.0" dependencies = [ + "brahman-sidecar", "directories", "gpui", "serde_json", diff --git a/crates/apps/tahuantinsuyu/Cargo.toml b/crates/apps/tahuantinsuyu/Cargo.toml index 3732241..f6e2994 100644 --- a/crates/apps/tahuantinsuyu/Cargo.toml +++ b/crates/apps/tahuantinsuyu/Cargo.toml @@ -15,6 +15,7 @@ tahuantinsuyu-panel = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-panel" tahuantinsuyu-store = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-store" } tahuantinsuyu-theme = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-theme" } tahuantinsuyu-tree = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-tree" } +brahman-sidecar = { path = "../../shared/brahman-sidecar" } yahweh-bus = { workspace = true } yahweh-core = { workspace = true } diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs index c6cdea4..2a680c0 100644 --- a/crates/apps/tahuantinsuyu/src/shell.rs +++ b/crates/apps/tahuantinsuyu/src/shell.rs @@ -49,6 +49,18 @@ use yahweh_widget_theme_switcher::theme_switcher; const TREE_WIDTH: f32 = 280.0; const PANEL_HEIGHT: f32 = 180.0; +/// Status del broker brahman tal como lo vimos en el último ping. +/// Se refresca cada 30 segundos desde un background task. +#[derive(Clone, Debug)] +pub enum BrahmanStatus { + /// Aún no probamos (boot, primer ciclo). + Pending, + /// Connect OK al broker, devolvió la lista de sessions activas. + Connected { session_count: usize }, + /// Connect falló — broker no escucha en el socket o tomó timeout. + Offline { reason: String }, +} + pub struct Shell { store: Store, #[allow(dead_code)] @@ -63,6 +75,9 @@ pub struct Shell { /// Splitter vertical entre el main_row (arriba) y el panel de /// control (abajo). outer_split: Entity, + /// Último estado conocido del broker brahman — refrescado cada + /// 30s desde el background task. + brahman_status: BrahmanStatus, current_chart: Option, current_offset_minutes: i64, /// Estado de los módulos overlay (transit, progression, …) por @@ -162,15 +177,52 @@ impl Shell { panel, main_split, outer_split, + brahman_status: BrahmanStatus::Pending, current_chart: None, current_offset_minutes: 0, module_configs: HashMap::new(), render_seq: 0, }; shell.refresh_chart_options(cx); + shell.spawn_brahman_status_loop(cx); shell } + /// Loop que cada 30s pregunta al broker la lista de sessions + /// activas y actualiza `brahman_status`. El cómputo bloqueante + /// (list_sessions_blocking abre su propio tokio runtime) corre en + /// el background_executor — no bloquea el UI thread. Cuando llega + /// el resultado, el `this.update` dispara cx.notify para repintar + /// el badge del header. + fn spawn_brahman_status_loop(&self, cx: &mut Context) { + cx.spawn(async move |this, cx| { + loop { + let result = cx + .background_executor() + .spawn(async { + brahman_sidecar::list_sessions_blocking("tahuantinsuyu-observer") + }) + .await; + let _ = this.update(cx, |this, cx| { + this.brahman_status = match result { + Ok(list) => BrahmanStatus::Connected { + session_count: list.entries.len(), + }, + Err(e) => BrahmanStatus::Offline { + reason: format!("{:?}", e), + }, + }; + cx.notify(); + }); + let timer = cx + .background_executor() + .timer(std::time::Duration::from_secs(30)); + timer.await; + } + }) + .detach(); + } + /// Recarga la lista de opciones para los `Control::ChartPicker` y /// la pushea al panel. Llamado al boot + tras cada /// `TreeEvent::HierarchyChanged`. @@ -844,6 +896,29 @@ impl Render for Shell { fn render(&mut self, _w: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = Theme::global(cx).clone(); + // Badge del estado del broker brahman — pequeña pill con + // color según el estado actual del ping cada-30s. + let (badge_text, badge_color) = match &self.brahman_status { + BrahmanStatus::Pending => ("Brahman · …".to_string(), theme.fg_muted), + BrahmanStatus::Connected { session_count } => ( + format!("Brahman ✓ {} sessions", session_count), + theme.accent, + ), + BrahmanStatus::Offline { .. } => { + ("Brahman · offline".to_string(), theme.fg_disabled) + } + }; + let brahman_badge = div() + .px(px(8.0)) + .py(px(2.0)) + .rounded(px(8.0)) + .bg(theme.bg_panel_alt.clone()) + .border_1() + .border_color(theme.border) + .text_size(px(10.0)) + .text_color(badge_color) + .child(SharedString::from(badge_text)); + let header = div() .h(px(34.0)) .px(px(12.0)) @@ -866,6 +941,7 @@ impl Render for Shell { .child("estudio de astrología profesional"), ) .child(div().flex_grow()) + .child(brahman_badge) .child(theme_switcher(cx)); let body = div()