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:
Generated
+1
@@ -10904,6 +10904,7 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417"
|
|||||||
name = "tahuantinsuyu"
|
name = "tahuantinsuyu"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"brahman-sidecar",
|
||||||
"directories",
|
"directories",
|
||||||
"gpui",
|
"gpui",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ tahuantinsuyu-panel = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-panel"
|
|||||||
tahuantinsuyu-store = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-store" }
|
tahuantinsuyu-store = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-store" }
|
||||||
tahuantinsuyu-theme = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-theme" }
|
tahuantinsuyu-theme = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-theme" }
|
||||||
tahuantinsuyu-tree = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-tree" }
|
tahuantinsuyu-tree = { path = "../../modules/tahuantinsuyu/tahuantinsuyu-tree" }
|
||||||
|
brahman-sidecar = { path = "../../shared/brahman-sidecar" }
|
||||||
|
|
||||||
yahweh-bus = { workspace = true }
|
yahweh-bus = { workspace = true }
|
||||||
yahweh-core = { workspace = true }
|
yahweh-core = { workspace = true }
|
||||||
|
|||||||
@@ -49,6 +49,18 @@ use yahweh_widget_theme_switcher::theme_switcher;
|
|||||||
const TREE_WIDTH: f32 = 280.0;
|
const TREE_WIDTH: f32 = 280.0;
|
||||||
const PANEL_HEIGHT: f32 = 180.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 {
|
pub struct Shell {
|
||||||
store: Store,
|
store: Store,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -63,6 +75,9 @@ pub struct Shell {
|
|||||||
/// Splitter vertical entre el main_row (arriba) y el panel de
|
/// Splitter vertical entre el main_row (arriba) y el panel de
|
||||||
/// control (abajo).
|
/// control (abajo).
|
||||||
outer_split: Entity<SplitContainer>,
|
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_chart: Option<Chart>,
|
||||||
current_offset_minutes: i64,
|
current_offset_minutes: i64,
|
||||||
/// Estado de los módulos overlay (transit, progression, …) por
|
/// Estado de los módulos overlay (transit, progression, …) por
|
||||||
@@ -162,15 +177,52 @@ impl Shell {
|
|||||||
panel,
|
panel,
|
||||||
main_split,
|
main_split,
|
||||||
outer_split,
|
outer_split,
|
||||||
|
brahman_status: BrahmanStatus::Pending,
|
||||||
current_chart: None,
|
current_chart: None,
|
||||||
current_offset_minutes: 0,
|
current_offset_minutes: 0,
|
||||||
module_configs: HashMap::new(),
|
module_configs: HashMap::new(),
|
||||||
render_seq: 0,
|
render_seq: 0,
|
||||||
};
|
};
|
||||||
shell.refresh_chart_options(cx);
|
shell.refresh_chart_options(cx);
|
||||||
|
shell.spawn_brahman_status_loop(cx);
|
||||||
shell
|
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
|
/// Recarga la lista de opciones para los `Control::ChartPicker` y
|
||||||
/// la pushea al panel. Llamado al boot + tras cada
|
/// la pushea al panel. Llamado al boot + tras cada
|
||||||
/// `TreeEvent::HierarchyChanged`.
|
/// `TreeEvent::HierarchyChanged`.
|
||||||
@@ -844,6 +896,29 @@ impl Render for Shell {
|
|||||||
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let theme = Theme::global(cx).clone();
|
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()
|
let header = div()
|
||||||
.h(px(34.0))
|
.h(px(34.0))
|
||||||
.px(px(12.0))
|
.px(px(12.0))
|
||||||
@@ -866,6 +941,7 @@ impl Render for Shell {
|
|||||||
.child("estudio de astrología profesional"),
|
.child("estudio de astrología profesional"),
|
||||||
)
|
)
|
||||||
.child(div().flex_grow())
|
.child(div().flex_grow())
|
||||||
|
.child(brahman_badge)
|
||||||
.child(theme_switcher(cx));
|
.child(theme_switcher(cx));
|
||||||
|
|
||||||
let body = div()
|
let body = div()
|
||||||
|
|||||||
Reference in New Issue
Block a user