feat(yahweh-launcher): F3 — extracción del shell standard de explorers
Iter 19. Patrón con 4 consumers idénticos: cada main() repetía el mismo ~20 líneas de boot (Application::new + Theme::install_default + cx.open_window + WindowOptions + cx.activate). Sólo varían título, tamaño y root factory. crates/modules/ui_engine/libs/launcher/: - pub fn launch_app(title, size, root_factory) → 1-line boot. - pub fn launch_app_with(config, root_factory) → variante con config armado afuera (env-var driven, etc). - pub struct AppLaunchConfig::new(title, size). - 2 tests cubren normalización del config. Migración 4 consumers (nakui/nouser/minga/brahman-broker explorer): - main() pasa de ~20 líneas a 1: launch_app(...). - Imports gpui podados (no más App/Application/Bounds/WindowOpts/etc). - Cada uno agrega dep yahweh-launcher. Naming: yahweh-shell ya existe (bootstrap heavyweight con file/db/text viewers en crates/apps/). Helper liviano queda como yahweh-launcher. Ahorro ~75 líneas de boot hardcoded. Cambios de window/theme boot ahora en un solo lugar. 2/2 tests launcher; 4 consumer suites intactas, todo verde. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,44 @@ ratio/diff ver `git show <sha>`.
|
||||
|
||||
## 2026-05-10
|
||||
|
||||
### feat(yahweh-launcher): F3 — extracción del shell standard de explorers
|
||||
Iter 19. Patrón con 4 consumers idénticos (nakui-explorer,
|
||||
nouser-explorer, minga-explorer, brahman-broker-explorer) declaraban
|
||||
~20 líneas de boot:
|
||||
`Application::new + Theme::install_default + cx.open_window
|
||||
+ WindowOptions{ window_bounds, titlebar } + cx.activate(true)`.
|
||||
Todo idéntico salvo título, tamaño, y root entity factory.
|
||||
|
||||
Crate nuevo `crates/modules/ui_engine/libs/launcher/`
|
||||
(`yahweh-launcher`):
|
||||
- **Deps**: `gpui`, `yahweh-theme`. Sin más.
|
||||
- **`pub fn launch_app(title, size, root_factory)`**: 1-line boot.
|
||||
- **`pub fn launch_app_with(config, root_factory)`**: variante con
|
||||
`AppLaunchConfig` armado afuera, para casos donde título/tamaño se
|
||||
computan condicionalmente (env var, etc).
|
||||
- **`AppLaunchConfig::new(title, size)`**: builder normalizador.
|
||||
- 2 tests `#[test]` cubren la normalización de config (no se testea
|
||||
`launch_app` per se porque bloquea el thread main hasta que la
|
||||
ventana se cierra).
|
||||
|
||||
Migración 4 consumers:
|
||||
- `main()` pasa de 20 líneas a 1: `launch_app("Title", (W, H), Explorer::new)`.
|
||||
- Imports de gpui se podan: ya no necesitan `App, Application,
|
||||
Bounds, WindowBounds, WindowOptions` ni `SharedString` ni `prelude::*`.
|
||||
- Cada consumer agrega dep `yahweh-launcher` (path local).
|
||||
|
||||
Naming: el slot natural era `yahweh-shell`, pero ya existe un crate
|
||||
`yahweh-shell` en `crates/apps/` (bootstrap heavyweight con file
|
||||
explorer + DB explorer + viewers). El helper es liviano y específico
|
||||
al patrón de launch, así que `yahweh-launcher` evita confusión.
|
||||
|
||||
Total ahorro: ~75 líneas hardcoded de boilerplate UI a 4 líneas en
|
||||
total. Cambios de boot (window opts, theme, etc) ahora viven en un
|
||||
solo lugar.
|
||||
|
||||
Tests stack: 2 nuevos en launcher; suites de los 4 consumers
|
||||
intactas. Todo verde.
|
||||
|
||||
### chore(.gitignore): excluir .claude/ (state local de Claude Code)
|
||||
Iter 18. Side cleanup tras debugging: `.claude/` aparecía en
|
||||
`git status` cada sesión (contenía `scheduled_tasks.lock` y
|
||||
|
||||
Generated
+12
@@ -1242,6 +1242,7 @@ dependencies = [
|
||||
"brahman-handshake",
|
||||
"brahman-sidecar",
|
||||
"gpui",
|
||||
"yahweh-launcher",
|
||||
"yahweh-theme",
|
||||
"yahweh-widget-app-header",
|
||||
"yahweh-widget-banner",
|
||||
@@ -6215,6 +6216,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"minga-store",
|
||||
"yahweh-launcher",
|
||||
"yahweh-theme",
|
||||
"yahweh-widget-app-header",
|
||||
"yahweh-widget-banner",
|
||||
@@ -6484,6 +6486,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"uuid",
|
||||
"yahweh-launcher",
|
||||
"yahweh-meta-runtime",
|
||||
"yahweh-theme",
|
||||
"yahweh-widget-app-header",
|
||||
@@ -6882,6 +6885,7 @@ dependencies = [
|
||||
"brahman-sidecar",
|
||||
"gpui",
|
||||
"nouser-card",
|
||||
"yahweh-launcher",
|
||||
"yahweh-theme",
|
||||
"yahweh-widget-app-header",
|
||||
"yahweh-widget-banner",
|
||||
@@ -12944,6 +12948,14 @@ dependencies = [
|
||||
"yahweh-theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yahweh-launcher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"gpui",
|
||||
"yahweh-theme",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yahweh-meta-runtime"
|
||||
version = "0.1.0"
|
||||
|
||||
@@ -51,6 +51,7 @@ members = [
|
||||
# ============================================================
|
||||
"crates/modules/ui_engine/libs/core",
|
||||
"crates/modules/ui_engine/libs/theme",
|
||||
"crates/modules/ui_engine/libs/launcher",
|
||||
"crates/modules/ui_engine/libs/bus",
|
||||
"crates/modules/ui_engine/libs/meta-schema",
|
||||
"crates/modules/ui_engine/libs/meta-runtime",
|
||||
|
||||
@@ -9,6 +9,7 @@ description = "Probe GUI del broker brahman: conecta cada N segundos vía await_
|
||||
brahman-handshake = { path = "../../core/brahman-handshake" }
|
||||
brahman-sidecar = { path = "../../shared/brahman-sidecar" }
|
||||
yahweh-theme = { path = "../../modules/ui_engine/libs/theme" }
|
||||
yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" }
|
||||
yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" }
|
||||
yahweh-widget-stat-card = { path = "../../modules/ui_engine/widgets/stat-card" }
|
||||
yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" }
|
||||
|
||||
@@ -29,35 +29,19 @@ use std::time::{Duration, Instant};
|
||||
use brahman_handshake::transport;
|
||||
use brahman_sidecar::{await_provider_blocking, build_consumer_card, ConsumerError};
|
||||
use gpui::{
|
||||
div, prelude::*, px, App, Application, Bounds, Context, IntoElement, Render, SharedString,
|
||||
Window, WindowBounds, WindowOptions,
|
||||
div, prelude::*, px, Context, IntoElement, Render, SharedString, Window,
|
||||
};
|
||||
use yahweh_launcher::launch_app;
|
||||
use yahweh_theme::Theme;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
use yahweh_widget_banner::{banner_themed, Banner};
|
||||
use yahweh_widget_stat_card::stat_card;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
|
||||
const POLL_INTERVAL: Duration = Duration::from_secs(5);
|
||||
const PROBE_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
Theme::install_default(cx);
|
||||
let bounds = Bounds::centered(None, gpui::size(px(720.), px(480.)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
title: Some(SharedString::from("Brahman Broker — Probe")),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_w, cx| cx.new(Explorer::new),
|
||||
)
|
||||
.expect("open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
launch_app("Brahman Broker — Probe", (720., 480.), Explorer::new);
|
||||
}
|
||||
|
||||
/// Snapshot de un probe.
|
||||
|
||||
@@ -8,6 +8,7 @@ description = "Dashboard GPUI del repo Minga: counts de nodos AST, atestaciones,
|
||||
[dependencies]
|
||||
minga-store = { path = "../../modules/semantic_dht/minga-store" }
|
||||
yahweh-theme = { path = "../../modules/ui_engine/libs/theme" }
|
||||
yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" }
|
||||
yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" }
|
||||
yahweh-widget-stat-card = { path = "../../modules/ui_engine/widgets/stat-card" }
|
||||
yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" }
|
||||
|
||||
@@ -27,36 +27,20 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{
|
||||
div, prelude::*, px, App, Application, Bounds, Context, IntoElement, Render, SharedString,
|
||||
Window, WindowBounds, WindowOptions,
|
||||
div, prelude::*, px, Context, IntoElement, Render, SharedString, Window,
|
||||
};
|
||||
use minga_store::PersistentRepo;
|
||||
use yahweh_launcher::launch_app;
|
||||
use yahweh_theme::Theme;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
use yahweh_widget_banner::{banner_themed, Banner};
|
||||
use yahweh_widget_stat_card::stat_card;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
|
||||
const REFRESH_INTERVAL: Duration = Duration::from_secs(2);
|
||||
const REPO_DIRNAME: &str = "repo";
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
Theme::install_default(cx);
|
||||
let bounds = Bounds::centered(None, gpui::size(px(800.), px(560.)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
title: Some(SharedString::from("Minga — Repo")),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_w, cx| cx.new(Explorer::new),
|
||||
)
|
||||
.expect("open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
launch_app("Minga — Repo", (800., 560.), Explorer::new);
|
||||
}
|
||||
|
||||
/// Cuántos items recientes mostrar por sección. Los stores no
|
||||
|
||||
@@ -11,6 +11,7 @@ yahweh-meta-runtime = { path = "../../modules/ui_engine/libs/meta-runtime" }
|
||||
yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" }
|
||||
yahweh-widget-card = { path = "../../modules/ui_engine/widgets/card" }
|
||||
yahweh-theme = { path = "../../modules/ui_engine/libs/theme" }
|
||||
yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" }
|
||||
yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" }
|
||||
gpui = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
@@ -26,39 +26,20 @@ use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, App, Application, Bounds, Context, IntoElement, Render,
|
||||
SharedString, Window, WindowBounds, WindowOptions,
|
||||
div, prelude::*, px, rgb, Context, IntoElement, Render, SharedString, Window,
|
||||
};
|
||||
use nakui_core::event_log::{EventLog, LogEntry};
|
||||
use yahweh_launcher::launch_app;
|
||||
use yahweh_meta_runtime::{preview_value, short_hash, short_uuid};
|
||||
use yahweh_theme::Theme;
|
||||
use yahweh_widget_banner::{banner_themed, Banner};
|
||||
use yahweh_widget_app_header::app_header;
|
||||
use yahweh_widget_banner::{banner_themed, Banner};
|
||||
use yahweh_widget_card::card_themed;
|
||||
|
||||
const REFRESH_INTERVAL: Duration = Duration::from_secs(2);
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
// Theme global instalado al boot — los widgets themed lo
|
||||
// requieren, y simplifica el chrome del app a una paleta
|
||||
// consistente.
|
||||
Theme::install_default(cx);
|
||||
let bounds = Bounds::centered(None, gpui::size(px(900.), px(640.)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
title: Some(SharedString::from("Nakui — Event Log")),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_w, cx| cx.new(Explorer::new),
|
||||
)
|
||||
.expect("open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
launch_app("Nakui — Event Log", (900., 640.), Explorer::new);
|
||||
}
|
||||
|
||||
/// Estado de la vista. `entries` se reescribe en cada tick (el log
|
||||
|
||||
@@ -10,6 +10,7 @@ brahman-card = { path = "../../core/brahman-card" }
|
||||
brahman-sidecar = { path = "../../shared/brahman-sidecar" }
|
||||
nouser-card = { path = "../../modules/nouser/card" }
|
||||
yahweh-theme = { path = "../../modules/ui_engine/libs/theme" }
|
||||
yahweh-launcher = { path = "../../modules/ui_engine/libs/launcher" }
|
||||
yahweh-widget-banner = { path = "../../modules/ui_engine/widgets/banner" }
|
||||
yahweh-widget-card = { path = "../../modules/ui_engine/widgets/card" }
|
||||
yahweh-widget-app-header = { path = "../../modules/ui_engine/widgets/app-header" }
|
||||
|
||||
@@ -23,41 +23,23 @@ use std::time::Duration;
|
||||
|
||||
use brahman_sidecar::{await_provider_blocking, build_consumer_card, ConsumerError};
|
||||
use gpui::{
|
||||
div, prelude::*, px, rgb, App, Application, Bounds, Context, IntoElement, Render, SharedString,
|
||||
Window, WindowBounds, WindowOptions,
|
||||
div, prelude::*, px, rgb, Context, IntoElement, Render, SharedString, Window,
|
||||
};
|
||||
use nouser_card::query::client as query_client;
|
||||
use nouser_card::query::{transport, ListMonadsResponse, FLOW_MONAD_LIST, FLOW_TYPE_NAME};
|
||||
use nouser_card::Lens;
|
||||
use yahweh_launcher::launch_app;
|
||||
use yahweh_theme::Theme;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
use yahweh_widget_banner::{banner_themed, Banner};
|
||||
use yahweh_widget_card::card_themed;
|
||||
use yahweh_widget_app_header::app_header;
|
||||
|
||||
const REFRESH_INTERVAL: Duration = Duration::from_secs(2);
|
||||
const DISCOVERY_TIMEOUT: Duration = Duration::from_secs(3);
|
||||
const QUERY_TIMEOUT: Duration = Duration::from_secs(2);
|
||||
|
||||
fn main() {
|
||||
Application::new().run(|cx: &mut App| {
|
||||
// Theme global instalado al boot — los widgets themed lo
|
||||
// requieren y unifica el chrome del app.
|
||||
Theme::install_default(cx);
|
||||
let bounds = Bounds::centered(None, gpui::size(px(900.), px(640.)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(gpui::TitlebarOptions {
|
||||
title: Some(SharedString::from("Nouser — Mónadas")),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_w, cx| cx.new(Explorer::new),
|
||||
)
|
||||
.expect("open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
launch_app("Nouser — Mónadas", (900., 640.), Explorer::new);
|
||||
}
|
||||
|
||||
/// Vista raíz: cachea el socket descubierto, el último snapshot y el
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "yahweh-launcher"
|
||||
version = { workspace = true }
|
||||
edition = { workspace = true }
|
||||
license = { workspace = true }
|
||||
description = "Launcher GPUI reusable: Application::new + Theme::install_default + cx.open_window + cx.activate. Las explorer apps lo invocan en una sola línea (`launch_app(title, size, root_factory)`)."
|
||||
|
||||
[dependencies]
|
||||
gpui = { workspace = true }
|
||||
yahweh-theme = { path = "../theme" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
@@ -0,0 +1,123 @@
|
||||
//! Yahweh shell — reduce el boot de un app GPUI temed a una línea.
|
||||
//!
|
||||
//! Las 4 (próximamente más) apps explorer del repo declaran el mismo
|
||||
//! patrón: `Application::new + Theme::install_default + cx.open_window
|
||||
//! + cx.activate(true)`. Sólo varían el título, el tamaño inicial y la
|
||||
//! fábrica del root entity.
|
||||
//!
|
||||
//! Antes (~20 líneas):
|
||||
//!
|
||||
//! ```ignore
|
||||
//! Application::new().run(|cx: &mut App| {
|
||||
//! Theme::install_default(cx);
|
||||
//! let bounds = Bounds::centered(None, gpui::size(px(900.), px(640.)), cx);
|
||||
//! cx.open_window(
|
||||
//! WindowOptions {
|
||||
//! window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
//! titlebar: Some(gpui::TitlebarOptions {
|
||||
//! title: Some(SharedString::from("Nakui — Event Log")),
|
||||
//! ..Default::default()
|
||||
//! }),
|
||||
//! ..Default::default()
|
||||
//! },
|
||||
//! |_w, cx| cx.new(Explorer::new),
|
||||
//! ).expect("open window");
|
||||
//! cx.activate(true);
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! Ahora (1 línea):
|
||||
//!
|
||||
//! ```ignore
|
||||
//! launch_app("Nakui — Event Log", (900., 640.), Explorer::new);
|
||||
//! ```
|
||||
|
||||
use gpui::{
|
||||
App, AppContext, Application, Bounds, Context, Render, SharedString, TitlebarOptions,
|
||||
WindowBounds, WindowOptions, px,
|
||||
};
|
||||
use yahweh_theme::Theme;
|
||||
|
||||
/// Configuración del primer (y normalmente único) ventana del app.
|
||||
///
|
||||
/// `size` es `(ancho, alto)` en píxeles lógicos. La ventana queda
|
||||
/// centrada en el monitor primario.
|
||||
pub struct AppLaunchConfig {
|
||||
pub title: SharedString,
|
||||
pub size: (f32, f32),
|
||||
}
|
||||
|
||||
impl AppLaunchConfig {
|
||||
pub fn new(title: impl Into<SharedString>, size: (f32, f32)) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Levanta un app GPUI con tema instalado y root entity construido.
|
||||
///
|
||||
/// El root debe implementar `Render`. La fábrica `root_factory` recibe
|
||||
/// el `Context<T>` del nuevo entity para que pueda usar `cx.spawn`,
|
||||
/// suscribirse a eventos, etc — lo mismo que en el patrón directo.
|
||||
///
|
||||
/// Bloquea el thread main hasta que se cierre la ventana
|
||||
/// (`Application::run` no retorna).
|
||||
pub fn launch_app<T, F>(title: impl Into<SharedString>, size: (f32, f32), root_factory: F)
|
||||
where
|
||||
T: Render + 'static,
|
||||
F: FnOnce(&mut Context<T>) -> T + Send + 'static,
|
||||
{
|
||||
launch_app_with(AppLaunchConfig::new(title, size), root_factory);
|
||||
}
|
||||
|
||||
/// Variante que acepta un `AppLaunchConfig` armado afuera. Útil cuando
|
||||
/// el config se calcula condicionalmente (env var para tamaño, etc).
|
||||
pub fn launch_app_with<T, F>(config: AppLaunchConfig, root_factory: F)
|
||||
where
|
||||
T: Render + 'static,
|
||||
F: FnOnce(&mut Context<T>) -> T + Send + 'static,
|
||||
{
|
||||
Application::new().run(move |cx: &mut App| {
|
||||
Theme::install_default(cx);
|
||||
let bounds = Bounds::centered(None, gpui::size(px(config.size.0), px(config.size.1)), cx);
|
||||
cx.open_window(
|
||||
WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(bounds)),
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: Some(config.title.clone()),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
},
|
||||
|_w, cx| cx.new(root_factory),
|
||||
)
|
||||
.expect("open window");
|
||||
cx.activate(true);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn config_new_normalizes_inputs() {
|
||||
let c = AppLaunchConfig::new("My App", (800.0, 600.0));
|
||||
assert_eq!(c.title.as_ref(), "My App");
|
||||
assert_eq!(c.size, (800.0, 600.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn config_accepts_owned_string_title() {
|
||||
let owned = String::from("Owned Title");
|
||||
let c = AppLaunchConfig::new(owned, (400.0, 300.0));
|
||||
assert_eq!(c.title.as_ref(), "Owned Title");
|
||||
}
|
||||
|
||||
// No hay test de `launch_app` aquí: bloquea el thread main hasta
|
||||
// que la ventana se cierre, y en sandbox no hay DISPLAY. La
|
||||
// cobertura real es que cada explorer app lo invoque y arranque
|
||||
// (smoke test manual o con DISPLAY).
|
||||
}
|
||||
Reference in New Issue
Block a user