feat(tahuantinsuyu): fase 24 — observabilidad del broker brahman
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 }
|
||||
|
||||
@@ -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<SplitContainer>,
|
||||
/// Último estado conocido del broker brahman — refrescado cada
|
||||
/// 30s desde el background task.
|
||||
brahman_status: BrahmanStatus,
|
||||
current_chart: Option<Chart>,
|
||||
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<Self>) {
|
||||
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<Self>) -> 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()
|
||||
|
||||
Reference in New Issue
Block a user