Desbloqueado por verbo. fana-semantic embebe los átomos y mide su
afinidad a un conjunto de conceptos.
- ConceptSet — embebe el texto de referencia de cada concepto como su
vector ancla (vía cualquier verbo Provider).
- SemanticScorer — embebe el contenido de un NarrativeAtom y llena
atom.semantic_vectors con la similitud coseno concepto→intensidad.
Limpia el scoring previo en cada pasada.
Agnóstico del backend (verbo_core::Provider). 3 tests verdes con
verbo-mock — incluye: texto idéntico al ancla puntúa coseno ≈ 1.
cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Backend sin modelo real: FNV-1a del texto siembra un LCG que genera el
vector. Mismo texto → mismo vector siempre; textos distintos → vectores
distintos. Dimensión configurable (default 384d, típica de modelos
ligeros).
Desbloquea desarrollar y testear los consumidores de verbo
(fana-semantic, badu, chasqui) sin descargar modelos ONNX ni pegarle a
Cohere. Los backends reales (cohere/bge/fastembed) son swaps de config.
4 tests verdes (determinismo, distinción, dimensión, batch).
cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primer crate de verbo (provider de embeddings compartido; desbloquea
fana-semantic, badu y la búsqueda de chasqui).
- ModelId — identidad de modelo (nombre + dimensión). Vectores de
distinto ModelId no son comparables.
- EmbeddingVector — vector + su ModelId; new() valida la dimensión,
cosine() rechaza comparar modelos distintos (error tipado, no
sinsentido silencioso), norm() euclidiana.
- EmbedError — ModelMismatch / BadDimension / Backend.
- trait Provider — model_id + embed + embed_batch (default secuencial).
Lo cumplen los backends concretos (cohere / bge / fastembed).
5 tests verdes (cosine idéntico/ortogonal/cross-model/zero, validación
de dimensión). cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
shuma-intent: el corazón agnóstico del shell shuma.
- parse — Intention: una línea del prompt parseada en etapas separadas
por pipe. Ref (%cN comando / %pN buffer) + Stage (Exec | Inject).
Parsea el ejemplo de la spec: `ssh nodo 'cat data.json' | %p1 | sort`.
- graph — SessionGraph: el grafo de contexto de la sesión. record()
registra una intención (%cN), complete() le asigna buffer de salida
(%pN) + estado, resolve() resuelve referencias, dangling_refs()
valida una intención antes de ejecutar (la validación previa del
prompt), collapse_succeeded() retrae nodos OK (quietud visual).
Todo puro y serializable (sesiones exportables). El front-end GPUI
(zonas RUN/SENS + lienzo central) lo rehidrata; la ejecución la hace
sandokan. 8 tests verdes. cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- fana-core: NarrativeAtom + CoherenceState ahora Serialize/Deserialize
(serde con feature rc para el Arc<String>; uuid con feature serde).
- fana-graph: + atoms() iterator + from_atoms() constructor.
- fana-store: GraphStore sobre sled. put/get/remove_atom por Uuid,
serialización bincode. save_graph persiste átomo por átomo;
load_graph reconstruye el grafo (la adjacency se re-cablea desde las
dependencies de cada átomo).
7 tests verdes (roundtrip put/get/remove + save/load_graph preserva
estructura). cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primer paso de fana (prioridad alta entre las apps Fase C).
- fana-core — NarrativeAtom: id + content_hash SHA-256 + content
Arc<String> (structural sharing: ramificar es O(1)) + semantic_vectors
+ dependencies + branch_id + CoherenceState (Valid/InConflict/
PendingEvaluation). Invariante hash↔content verificable; set_content
re-hashea y marca PendingEvaluation.
- fana-graph — NarrativeGraph: DAG de átomos + adjacency
dependencia→dependientes. propagate_mutation: BFS que marca
PendingEvaluation en cascada a todo descendiente (la "onda de choque
lógica" de la spec), agnóstico de UI — devuelve los ids afectados.
topological_order con detección de ciclo.
10 tests verdes. cargo check --workspace verde.
Pendiente fana: semantic (cliente verbo), store (sled), llm, render-plan,
editor-gpui.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Opción B: RemoteEngine orquesta en un host remoto tunelando el wire
del daemon sobre un canal SSH direct-streamlocal hacia el sandokan.sock
remoto. El protocolo es idéntico al de DaemonEngine (postcard
length-prefixed) — sólo cambia el transporte, así que read_frame/
write_frame se reusan tal cual.
- brahman-ssh-multiplex: + SshSession::forward_unix — abre un canal
direct-streamlocal y devuelve su ChannelStream (AsyncRead+AsyncWrite).
- sandokan-daemon: protocol ahora pub, exporta read_frame/write_frame.
- sandokan-remote: RemoteEngine { SshSession + remote_socket }.
connect() o with_session(); cada operación abre un canal nuevo
(multiplexado sobre la conexión maestra).
- sandokan umbrella re-exporta RemoteEngine.
Completa Fase B: sandokan tiene Local + Daemon + Remote + auto().
cargo check --workspace verde. RemoteEngine necesita un host remoto
con `sandokan daemon` para validación runtime (sin unit test).
Opción A (text-parse del CLI por compat) queda pendiente por decisión
del usuario.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Envuelve russh 0.54 con una API mínima: una SshSession mantiene el
Handle maestro; cada exec() concurrente abre su propio canal en
paralelo sobre la misma conexión TCP (SSH multiplexa canales por
diseño del protocolo).
- SshConfig (host/port/user/auth/keepalive) + SshAuth (Password | Key).
- SshSession::connect — config russh + keepalive + auth password o
clave privada en disco; verificación de host key TOFU por default.
- SshSession::exec — corre un comando en un canal nuevo, junta
stdout/stderr/exit_code.
- SshSession es Clone barato (comparte el Handle).
Base de sandokan RemoteEngine y del Linker SSH de matilda.
Compila contra russh 0.54. El test de conexión real requiere un
servidor SSH (fuera del unit test).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- index — CardIndex: índice en memoria con filtros (by_label
case-insensitive substring, by_kind, providing por Capability, by_id).
- registry — scan_dir: carga toda Card *.json de un directorio,
saltando ruido y archivos rotos.
- discovery — CardDiscovery: une el índice local con la malla P2P;
announce_all publica las Cards locales al DHT, find_remote busca
proveedores. Modo local-only sin DHT también soportado.
Lo consumen el card-browser de nahual-shell y agorapura.
7 tests verdes. cargo check --workspace verde.
settings.local.json: defaultMode bypassPermissions (sesión desatendida).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
brahman-net corre un único Kademlia para todo el ecosistema.
brahman-dht le pone arriba un esquema de claves namespaced para que
distintos dominios coexistan sin colisión en la misma malla.
- key — RecordKind (Code/Card/Persona/Service/Custom) + DhtKey.
Wire: [kind_tag] ++ blake3(id) = 33 bytes longitud fija. Custom(n)
usa 0x80|n: nunca choca con los kinds estándar.
- Dht — wrapper sobre BrahmanNet: announce/withdraw/find (modelo de
provider records).
Consumidores: minga (Code), brahman-card-discovery (Card), agorapura
(Persona). 5 tests verdes (incl. smoke async sobre un nodo libp2p real).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
apps/sandokan (binario `sandokan`): CLI para probar el orquestador.
Subcomandos: daemon, run <exec> [args], list, status, telemetry, stop.
Fix: Intent serializaba Card directo, pero Card tiene un campo
`#[serde(flatten)] extensions` incompatible con postcard ("sequence
length must be known"). Intent::card ahora usa #[serde(with)] que
proyecta Card↔WireCard en el límite de serialización (las extensions
locales se descartan al cruzar el wire — comportamiento correcto).
Smoke test verificado end-to-end: daemon + run /bin/sleep + list +
status Running + telemetry + stop + status Killed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Crate sandokan (umbrella): re-exporta core/local/daemon y provee la
selección de transporte.
- auto(socket) — patrón "el primero que arranca gana": prueba si hay
un daemon escuchando; si lo hay devuelve DaemonEngine, si no
LocalEngine. Box<dyn Engine> (el trait es object-safe vía async_trait).
- auto_default() — auto() con default_socket_path().
- default_socket_path() — $XDG_RUNTIME_DIR/sandokan.sock o
/run/brahman/sandokan.sock.
3 tests: fallback a Local sin daemon, pick Daemon con serve() activo,
default path absoluto. cargo check --workspace verde.
sandokan ya es usable end-to-end en modo local y daemon. Falta
RemoteEngine (B1.4, depende de brahman-ssh-multiplex).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DaemonEngine: implementación del trait Engine que delega a otro proceso
vía Unix socket. Materializa el patrón horizontal de sandokan (el
binario que arranca primero expone el engine; los demás se le suman).
- protocol.rs — DaemonRequest/DaemonResponse (espejan los métodos de
Engine) + framing postcard length-prefixed (u32 LE + bytes), con
MAX_FRAME 16 MiB defensivo.
- client.rs — DaemonEngine: stateless, un round-trip por llamada;
is_reachable() para el probe de auto().
- server.rs — serve(engine, socket): envuelve cualquier Engine, una
task por conexión, multi-request por conexión.
EngineError ahora es Serialize/Deserialize (viaja por el wire);
NotFound se propaga tipado a través del socket.
1 test de integración: roundtrip real DaemonEngine ↔ serve ↔ LocalEngine
(list vacío + NotFound propagado). cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primer crate de la Fase B. Define SOLO el contrato del orquestador
sandokan (library horizontal embebible, no daemon supremo):
- Intent / ExecContext / IsolationLevel — qué orquestar
- ExecHandle — referencia a una entidad encarnada
- LifecycleEvent / TelemetryFrame — observabilidad (wire types)
- EngineError — taxonomía de fallas
- trait Engine — run/stop/list/status/telemetry (poll-based, sin
streams sobre trait objects, para que las 3 impls lo cumplan
uniformemente)
Las impls concretas (LocalEngine, DaemonEngine, RemoteEngine) vendrán
en crates separados (sandokan-local, sandokan-daemon, sandokan-remote).
3 tests verdes. cargo check --workspace verde.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Nuevo crate runtime/sandokan-lifecycle: lógica pura reutilizable por
cualquier supervisor de procesos (shuma, matilda Ghost, charka-shadow,
mirada). Sin syscalls, sin proceso, sin UI.
Módulos:
- backoff — Backoff exponencial con tope
- ttl — Ttl anclado a Instant
- quota — ResourceQuota + check_quota + Breach + QuotaAction
- restart — RestartPolicy + RestartTracker (conteo + backoff)
- state — LifecycleState (Pending/Running/Exited/Failed/Killed)
15 tests verdes. cargo check --workspace verde.
Variante segura de A4: se crea la library limpia sin tocar shuma-core
(módulo maduro). La migración de WorkspaceManager a consumir estas
primitivas queda registrada como A4.2 (refactor diferido, no urgente).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El loader vivía partido: arje-brain/loader.rs cargaba EntityCards Y
Rules, mientras brahman-cards tenía su propia infra de card-loading.
Resolución por linaje:
- Card-loading (load_card_file, extract_card_from_json) → brahman-cards
(entity_loader.rs). Toda card-loading del ecosistema vive ahí.
- Rule-loading (load_rules_file, extract_rules_from_json) → arje-brain-rules
(loader.rs), junto a la definición de Rule.
- arje-brain/loader.rs eliminado.
arje-brain re-exporta ambos para compat de consumidores (arje-zero).
cargo check --workspace verde. Tests: 13 arje-brain-rules + 31 brahman-cards.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DAG de dependencias limpio (modularidad horizontal):
- arje-brain-rules — rules + engine + dispatch (motor determinista)
- arje-brain-cognitive — observer + crystallize (estadística)
- arje-brain-audit — audit chain → CAS (accountability)
- arje-brain — umbrella de integración (introspect +
autopromote + metrics + loader)
Habilitador clave: TimedEvent movido de observer.rs a rules.rs
(engine lo necesitaba, era el único acoplo que rompía el DAG).
arje-brain re-exporta la API de los 3 sub-crates: arje-zero y chasqui
(consumidores) no requieren cambios. cargo check --workspace verde.
24 tests del brain pasan (4 rules + 6 cognitive + 5 audit + 9 umbrella).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cierra el requerimiento del módulo web. El cliente puede correr en
modo WASM (render local, scrubbing instantáneo, sin round-trip) o
caer al SSR (server compone el SVG) si el bundle WASM no está
desplegado. Switch automático sin configuración.
cosmobiologia-web (crate nuevo, cdylib + rlib):
- `lib.rs` con un único export wasm-bindgen
`render_model_to_svg(json, size, rot_offset_deg) -> String` que
deserializa un `RenderModel`, llama `compose_wheel` +
`draw_commands_to_svg` de cosmobiologia-render, y devuelve el
SVG inline listo para `wheel.innerHTML = svg`.
- Cargo.toml con `wasm-bindgen` + `getrandom` con feature
`wasm_js` solo bajo `target_arch = "wasm32"` (en nativo no se
arrastran).
- `.cargo/config.toml` con `--cfg getrandom_backend="wasm_js"`
para que la transitividad
`uuid → cosmobiologia-model → cosmobiologia-render` compile a
wasm32-unknown-unknown.
- `cargo check -p cosmobiologia-web` pasa en nativo (valida la
signature). Build WASM real lo dispara el usuario con
`wasm-pack build --target web --out-dir ../../../apps/
cosmobiologia-server/static/wasm` — comando documentado en
DEPLOY.md y en doc del crate.
cosmobiologia-server — soporte cliente WASM:
- Nuevo flag `--static-wasm <dir>` (default = static/wasm relativo
al cwd). Si el directorio existe, los archivos WASM se sirven
en `/static/wasm/*`. Si no existe, devuelve 404 y el cliente
cae al SSR.
- ServeDir de `tower-http` para fileserver simple.
index.html:
- Nueva función `tryLoadWasm()` que hace `import dinámico` del
módulo WASM al boot. Si carga OK, `wasm` global queda set; si
falla (archivo no existe o error de WASM), se loguea info y
sigue.
- `refreshSelected()` ahora hace fetch del RenderModel JSON
(`/api/sky` o `/api/charts/:id/render`); si hay WASM, llama
`wasm.render_model_to_svg(json)` localmente; si no hay WASM o
el render WASM falla, hace fetch del SVG SSR como fallback.
- Info row muestra "WASM" o "SSR" según el modo activo —
visualmente claro qué pipeline está corriendo.
cosmobiologia-server/DEPLOY.md (nuevo):
- Build del binario + build del WASM (con wasm-pack).
- systemd service template (sandboxing básico: ProtectSystem
strict, ProtectHome, PrivateTmp, NoNewPrivileges).
- Caddyfile y nginx para reverse proxy con TLS.
- DNS: A records para cosmobiologia.gioser.net + api.*.
- CORS: warnings sobre permissive vs producción multi-usuario.
- Separación demo público (DB vacía en VPS) vs desktop personal
(DB compartida en `~/.local/share/cosmobiologia/`).
- Backup con SQLite `.backup`.
- Smoke test post-deploy con curl.
- Tabla de referencia de TODOS los endpoints.
Tests: 10 verdes (cosmobiologia-render::math). El cliente WASM
no agrega tests propios — la lógica testeable vive en render.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Crate nuevo `cosmobiologia-server` (binario axum, nativo) que
monta `cosmobiologia-engine` + `cosmobiologia-store` y expone
la rueda + el CRUD del tree por HTTP.
Endpoints v1:
- `GET /api/health`
- `GET /api/tree` tree completo anidado
- `POST /api/groups` crear grupo
- `PATCH /api/groups/:id` renombrar
- `DELETE /api/groups/:id` borrar
- `POST /api/contacts` crear contacto
- `PATCH /api/contacts/:id` renombrar
- `DELETE /api/contacts/:id` borrar
- `POST /api/charts` crear carta
- `GET /api/charts/:id` chart JSON
- `PATCH /api/charts/:id` editar (label/birth/config)
- `DELETE /api/charts/:id` borrar
- `GET /api/charts/:id/render` RenderModel JSON
- `GET /api/charts/:id/svg` SVG inline (reusa
svg_export del engine)
- `GET /api/sky` "Cielo ahora" — RenderModel
UTC actual sin chart_id real
Query params del render para activar overlays sin POST:
- `offset_min=<i64>` time scrubbing
- `transit=1` overlay de tránsito al now
- `prog_age=<f64>` progresión secundaria
- `sa_age=<f64>` solar arc
- `pd_age=<f64>` primary directions (Naibod)
Decisiones:
- Single-user, sin auth. Bind por default a `127.0.0.1:8787` —
el server NO debe exponerse a la red pública en esta fase.
- DB por default = misma del desktop (`$XDG_DATA_HOME/cosmobiologia/
charts.db`). `--db` permite override.
- CORS permissive (es localhost, single-user, sin auth).
- `ApiError` con mapeo a HTTP status: 404 NotFound,
400 BadRequest, 500 todo lo demás. Body JSON `{ "error": "..." }`.
Smoke test:
cargo run -p cosmobiologia-server -- --port 18787
curl /api/health → {"status":"ok",...}
curl POST /api/groups → {"id":"01KRYVP...","name":"Familia",...}
curl POST /api/contacts → {"id":"01KRYVP...","group_id":...}
curl /api/tree → árbol anidado
curl /api/sky → RenderModel con VSOP real
Pendiente (fase 3): cliente `cosmobiologia-web` (cdylib WASM)
que consuma estos endpoints y pinte SVG/Canvas2D.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rename clean del proyecto astrológico antes de empezar el módulo
web (fase 2 = server axum, fase 3 = cliente WASM). Hacerlo ahora
ahorra refactor de URLs, package.json, paths de assets HTML y
deploy configs que aparecerían con el nombre en cuanto exista el
server.
Mecánica:
- `git mv` de los 10 crates de módulo + 2 apps:
* `crates/modules/tahuantinsuyu/` → `cosmobiologia/`
* `crates/modules/tahuantinsuyu/tahuantinsuyu-*` →
`cosmobiologia/cosmobiologia-*`
* `crates/apps/tahuantinsuyu` y `tahuantinsuyu-cli` análogos.
- Sed sobre todos los `.rs` y `.toml`: `tahuantinsuyu` →
`cosmobiologia` (cubre crate names, deps paths, use
statements, ProjectDirs literals, binary names).
- Workspace `Cargo.toml`: members con paths nuevos.
- Memoria del proyecto (`~/.claude/.../memory/project_*.md`)
actualizada.
Cero leftovers: `grep -rn tahuantinsuyu --include="*.rs"
--include="*.toml" crates/` devuelve vacío.
DB & XDG: clean slate. La nueva app arranca con DB vacía en
`$XDG_DATA_HOME/cosmobiologia/charts.db`. Si tenías cartas
guardadas, viven todavía en `~/.local/share/tahuantinsuyu/` —
las podés migrar manualmente con un `cp`.
IDs UI inalterados: el prefijo `tts-` de gpui ElementIds queda
igual (cosmético, no afecta funcionalidad). Cambiarlo a `cb-`
ahora sería 3-4 líneas más de sed pero ningún beneficio
operativo.
Tests: 20 verdes (10 shell + 10 render math). Compila full:
`cargo check -p cosmobiologia` OK.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Reemplaza `Context<Self>` por `Context<'_, Self>` (y la misma
fórmula para `Context<TahuantinsuyuTree>`) en tree/panel/canvas:
60 warnings de "hidden lifetime parameters are deprecated" → 0.
- Borra `TREE_WIDTH` y `PANEL_HEIGHT` (constantes muertas) y el
campo `main_split` del shell (vive como child de outer_split,
no necesita retención aparte).
- Quita `yahweh-bus` de tahuantinsuyu — el `bus: Entity<AppBus>`
estaba con `#[allow(dead_code)]` sin cablear. Cuando lo
necesitemos para coordinación cross-app lo reagregamos.
- Suprime imports `Module` (panel), `AppContext` (canvas) y
prefija el `cx` no usado en `on_jog_down`.
- Marca `BrahmanStatus::Offline.reason` y `Shell.tree` con
`#[allow(dead_code)]` documentando por qué se retienen
(logs y subscripciones).
Workspace ahora compila limpio salvo un warning conocido de
`eternal-validation` (variable `sin_i` sin usar — fuera de
brahman).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cierra el círculo del Card: el flow.input "chart-request" y output
"chart-result" declarados desde fase 1 ahora tienen data plane real.
Otros módulos brahman (incluyendo el CLI nuevo) pueden conectar al
Unix socket de Tahuantinsuyu y pedir cómputos de cartas natales sin
abrir la GUI.
## Protocolo y server
Nuevo módulo `tahuantinsuyu_card::service` con:
- `ComputeRequest { Ping, Natal { birth, config, offset_minutes,
label } }` — postcard-serializable
- `ComputeResponse { Pong, Render { render }, Error { message } }`
- `serve(socket_path)` — async loop sobre tokio::UnixListener,
spawn por conexión, frame `u32 length` LE + postcard payload
(mismo molde que brahman-handshake). Cap defensivo a 1 MiB por
frame.
- `request(&socket, &req) -> Response` — cliente async one-shot
(abre, envía, recibe, cierra)
- `spawn_service_thread(path)` — thread dedicado con tokio
current_thread runtime; loggea warn si bind falla, la app sigue
standalone.
La Card ahora declara `service_socket: Some(default_service_socket())`
— el broker brahman puede revelar este path a consumidores que
matcheen el flow `chart-request`. Path canónico:
`$XDG_CACHE_HOME/tahuantinsuyu/service.sock` (con fallback a
/tmp).
## CLI nuevo
`crates/apps/tahuantinsuyu-cli` — binario standalone que usa el
helper cliente. Comandos:
- `tahuantinsuyu-cli ping` — health check
- `tahuantinsuyu-cli natal --year ... --month ... --day ... --hour
... --minute ... --tz-min ... --lat ... --lon ... [--alt ...]
[--label "..."] [--offset-minutes N]` — pide compute y emite
RenderModel como JSON pretty-print en stdout
Útil para:
- Smoke tests del data plane (CI puede levantar la app + ping)
- Scripts batch (computar 100 cartas y exportar JSON)
- Integraciones con otros tools del fractal brahman vía broker
## Cambios accesorios
- apps/tahuantinsuyu/main.rs: spawn_service_thread al boot junto al
sidecar. Loggea el path del socket a stderr para debug.
- Cargo workspace: agrega tahuantinsuyu-cli como member.
- tahuantinsuyu-card Cargo: agrega deps (engine, model, postcard,
tokio, tracing, directories, thiserror) para soportar el server.
Lo que falta para integración brahman 100%:
- Suscripción al broker como "consumer-aware" para detectar cuando
otros módulos publican `chart-request`s
- Publishing de eventos al broker cuando se crean/borran cartas
- Ambos requieren protocolo handshake bidireccional sobre el Init
socket (no service_socket) — fase posterior.
cargo check verde, 8 tests engine + 1 modules verdes. CLI compila;
prueba end-to-end (ping + natal) queda a manos del usuario que
levante la GUI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
Dos features de producción que mejoran la usabilidad sustancialmente.
## #7 — Layout reorganizable con SplitContainer
Los 3 paneles ya no tienen tamaños hardcodeados. Reusamos
yahweh-widget-splitter (mismo que usa yahweh-shell para sus layouts
JSON-config) con 2 niveles:
- outer (Vertical): main_split arriba (flex 4) + panel abajo (flex 1)
- main_split (Horizontal): tree (flex 1) + canvas (flex 4)
El usuario puede arrastrar los dos divisores para redimensionar
libremente. Por ejemplo: en una pantalla ancha, dar más al canvas; en
una sesión de lectura analítica, agrandar el panel abajo para ver más
módulos expandidos.
- Shell gana fields main_split + outer_split: Entity<SplitContainer>.
- new() construye ambos con ChildSlots envolviendo tree/canvas/panel
como AnyView (mismo patrón que LayoutHost de yahweh-shell).
- render() simplificado: header + body(outer_split). Las constants
TREE_WIDTH y PANEL_HEIGHT desaparecen.
- Cargo añade deps: yahweh-core (NodeId, LayoutDirection),
yahweh-widget-splitter, yahweh-widget-container-core (ChildSlot).
## #15 — Atlas de ciudades cargable desde TSV
El array `CITY_PRESETS` const de 90 ciudades hardcoded ahora es la
función `default_city_presets() -> Vec<CityPreset>`. CityPreset.name
pasa de `&'static str` a `String` para que el atlas sea construible
en runtime.
TahuantinsuyuTree gana `city_atlas: Vec<CityPreset>` + setter
`set_city_atlas(atlas, cx)`. Al boot, Shell intenta cargar
`$XDG_DATA_HOME/tahuantinsuyu/atlas.tsv` y, si existe + parsea bien,
reemplaza el atlas hardcoded.
Formato TSV (líneas):
name<TAB>lat<TAB>lon<TAB>tz_offset_minutes
Líneas vacías y `#` comentario se ignoran.
Líneas con cualquier parse fallido se descartan en silencio.
API pública: `parse_city_atlas_tsv(&str) -> Vec<CityPreset>` (en
tahuantinsuyu-tree), reusable por tests/scripts.
El usuario que quiera 50.000 ciudades de GeoNames cities5000.txt:
1. wget cities5000.zip de geonames.org
2. awk para extraer (name, lat, lon, tz_offset) y escribir TSV
3. mover a $XDG_DATA_HOME/tahuantinsuyu/atlas.tsv
4. relanzar la app
Sin fricción adicional para el usuario común (los 90 hardcoded cubren
99% de casos típicos en español/inglés).
cargo check verde, 8 tests engine + 1 test modules verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase completa con 4 mejoras independientes que aprovechan toda la
infraestructura previa. La aplicación ahora cubre lecturas profundas
(midpoints uranianos), accesibilidad visual (tooltips de cusps),
personalización (6 themes vía yahweh-widget-theme-switcher) y
usabilidad pragmática (city presets en el form).
## C — Theme switcher en header
- apps/tahuantinsuyu: nueva dep yahweh-widget-theme-switcher.
- shell render(): theme_switcher(cx) en el extremo derecho del header
(con flex_grow del divider del medio). Click cicla entre los 6
presets de yahweh-theme (Nebula, Aurora, Sunset, FlatDark,
SolarizedLight, HighContrast). AstroPalette::for_theme(theme) lee
is_dark, así toda la rueda se re-tinta automáticamente.
## B — Tooltips sobre house cusps
- canvas: HoverInfo deja de ser struct para ser enum con variantes
Body { ... } y HouseCusp { house_number, deg, local_x, local_y }.
Helpers .local() y .key() unifican el acceso.
- on_hover_check: primero hit-test bodies (threshold 14px); si no hubo
match Y el mouse está dentro del anillo de casas
(houses_inner..houses_outer ± 6px), calcula la longitud zodiacal
desde el ángulo de pantalla (inversa de polar_to_screen) y busca el
cusp más cercano (proximidad angular < 2.5°). HoverInfo::HouseCusp.
- Tooltip render: "Cusp Casa N · Signo XX.X°".
## D — MidpointsModule (Uranian-lite)
- engine: PipelineRequest::Midpoints (sin parámetros, default empty).
- bridge: build_midpoints_overlay computa midpoints entre todos los
pares de placements donde involucran Sol o Luna (~10 puntos según
body set). Fórmula: si |a-b|>180, mid=((a+b)/2+180) mod 360, sino
(a+b)/2 mod 360. Emite como Layer { kind: Midpoints, module_id:
"midpoints", ring: 0.62 } con Glyph.symbol="sun/jupiter" y
annotation="Sun/Jupiter".
- modules: midpoints::MidpointsModule con toggle "Activar". Registry
pasa a 7 módulos. Test actualizado.
- shell: build_requests detecta midpoints.enabled, pushea
PipelineRequest::Midpoints (no toma age ni body — es derivado puro).
- canvas: Radii agrega midpoints: r * 0.62 (entre houses_inner y
bodies natales). body_ring("midpoints") y aspect_endpoints retornan
ese radio. paint_wheel agrega un loop para LayerKind::Midpoints
pintando dots pequeños (r=0.012, alpha 0.7 sobre house_cusp color)
— los midpoints no llevan unicode symbol propio (no existe en
Unicode astrológico estándar). El detalle del par viene en hover.
- Hover sobre un midpoint: tooltip muestra "☉/♄ Tauro 14.3° ·
Sun/Jupiter" (display_symbol parsea "a/b" en dos unicodes;
annotation incluye nombres completos eternal).
## A — City presets en el ChartForm
- tree: nueva const CITY_PRESETS con 25 ciudades (Latinoamérica
capitales + 5 europeas + 5 anglosajonas + Tokyo/Sydney/Mumbai/Cairo)
con (name, lat, lon, tz_offset_minutes) sin DST. CityPreset struct.
- tree: TahuantinsuyuTree gana city_picker_open: bool. close_modal
lo resetea. toggle_city_picker + apply_city_preset(preset) helpers.
apply_city_preset lee el Modal activo (CreateChart o EditChart),
llama TextInput::set_text en place/lat/lon/tz del ChartForm,
cierra el picker.
- render_chart_form: title_row ahora tiene "📍 Ciudad rápida ▾"
button a la derecha del title. Click → toggle. Cuando picker_open,
popup absoluto debajo con la lista de presets. Click en preset →
autocompleta + cierra. El usuario sigue pudiendo editar manualmente
cualquier campo después; el preset es solo un punto de partida
rápido para evitar tipear coordenadas a mano.
cargo check verde, 8 tests engine + 1 test modules (7 módulos)
verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El shell ya no carga el flag `show_transits: bool` ni hardcodea qué
pipeline corre. La engine expone una sola API `compose(chart, offset,
&[PipelineRequest])` que la shell alimenta a partir de un map
`module_configs: HashMap<String, serde_json::Value>`. Los toggles de
overlay (transit hoy, progression/synastry/solar_arc en fase 7) viven
como módulos propios en el panel.
- engine: PipelineRequest enum (variante Transit por ahora; comentarios
con el roadmap de SecondaryProgression/SolarArc/Synastry). compose()
es la nueva entrada canónica; compute / compute_at_offset /
compute_with_transits_at_now quedan como atajos retrocompatibles que
delegan en compose. bridge.rs refactor: extraído build_transit_overlay
como helper que muta &mut RenderModel, listo para que más pipelines
apilen capas encima.
- modules: nuevo módulo `transit::TransitModule` (id "transit", toggle
"enabled" con hotkey [T], applies_to Natal). Sacado el toggle
show_transits de NatalModule — ahora cada módulo declara lo suyo.
Registry::with_builtins() registra ambos. Test asegura los dos
aplican a Natal.
- panel: sin cambios — ya itera Registry::for_kind(kind) y renderea
cada módulo aplicable con sus controls. La adición del TransitModule
aparece automática como segunda card en el panel.
- shell: replace show_transits por module_configs map. build_requests()
deriva PipelineRequest::Transit cuando module_configs["transit"]
["enabled"] == true. on_panel_event: toggles del NatalModule afectan
solo visibility del canvas; toggles de otros módulos van al
module_configs y disparan render_current. on_canvas_event: [T]
hotkey → flip transit.enabled + sync panel + recompose. apps Cargo
agrega serde_json como dep directa.
Todos los tests verdes. Fase 7 puede sumar overlays adicionales
(progression, solar_arc) solo agregando variantes a PipelineRequest +
helpers en bridge + módulos declarativos — sin tocar el shell.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Right-click sobre el explorador izquierdo abre menú contextual cuyas
opciones dependen del target (raíz, group, contact o chart). Modales
flotantes para crear/renombrar usando yahweh-widget-text-input; un
form más completo de 11 campos para la birth data al crear cartas
natales. Borrar pide confirmación por window.prompt nativo.
- tahuantinsuyu-store: rename_contact, rename_chart, move_group,
move_contact (los `move_*` para fase posterior de drag-to-nest).
- tahuantinsuyu-tree: estado interno (Menu, Modal enum, ChartForm),
handlers de ContextMenuRequested, render overlays.
Soporta seis modales: rename de g/c/h, create group/contact, form
natal completo con parseo + reporte de errores inline.
Auto-expande el contact tras crear una carta.
Nuevo evento TreeEvent::HierarchyChanged tras cada mutación.
- shell: maneja HierarchyChanged sin propagar selección.
`cargo check` y `cargo test` verdes. Fase 3 viene con engine real
contra eternal-astrology + pintado de la rueda.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Módulo nuevo `modules/tahuantinsuyu/` con 9 crates reusables + app
`apps/tahuantinsuyu` ejecutable que abre la ventana del explorador y
coordina los widgets:
- tahuantinsuyu-card: Card Brahman + spawn_sidecar (flows
chart-request/chart-result).
- tahuantinsuyu-model: tipos agnósticos (Group/Contact/Chart,
StoredBirthData, StoredChartConfig, ChartKind, TreeSelection).
- tahuantinsuyu-store: persistencia SQLite (rusqlite) con migración v1,
CRUD por entidad y descenso recursivo `charts_under_group`.
- tahuantinsuyu-engine: bridge agnóstico al canvas vía `RenderModel`
(Layer/Glyph/Geometry). Feature `eternal-bridge` (off por default)
reservada para enchufar eternal-astrology desde ~/eternal.
- tahuantinsuyu-modules: registry de módulos pluggables (Module trait
+ Control schema) con `NatalModule` placeholder.
- tahuantinsuyu-theme: AstroPalette (elementos / modos / planetas /
aspectos) con variantes dark + light sobre yahweh-theme.
- tahuantinsuyu-canvas: widget GPUI con CanvasState (Empty / Wheel /
Thumbnails). Render placeholder hasta cablear la rueda real.
- tahuantinsuyu-tree: explorador izquierdo sobre yahweh-widget-tree,
prefijos g:/c:/h: para Group/Contact/Chart.
- tahuantinsuyu-panel: control panel inferior que lee Controls de los
módulos del registry y los pinta.
- apps/tahuantinsuyu: binario `tahuantinsuyu` (launch_app-style) con
Shell coordinador (tree↔canvas↔panel), DB en $XDG_DATA_HOME.
Workspace Cargo.toml actualizado con los 10 miembros. `cargo check`
verde, tests unitarios verdes (model/store/engine/modules/theme/card).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Mobile drag fix (vista-web):
- pointermove listener ahora con `AddEventListenerOptions { passive: false }`.
Sin esto, en navegadores móviles `preventDefault()` es no-op y el browser
se traga el gesto horizontal como pan/scroll antes de que JS pueda
detectar la dirección y capturar el pointer.
- CSS: `.deck-strip` y `.deck-strip *` y `.deck-page` con
`touch-action: pan-y`. El touch-action del target inmediato es lo que
el browser consulta; sin esto, sobre un <p> dentro del strip el browser
asume `auto` y reclama horizontal.
Taskbar agnóstica (barra-web):
- Nuevo crate `crates/modules/barra/barra-web` que maneja sólo el LIST
dinámico de tareas; el resto del layout (home, brand, credits) es del
host. Misma filosofía que vista-web: separar lo reusable.
- API: Task::new(id, label).active() builder; TaskList::mount(ul) +
set_tasks/on_click/task_center. Click delegado, callback recibe
(id, cx, cy) en CSS pixels para origin de animaciones.
- Sanitiza IDs a [a-zA-Z0-9_-] y HTML-escapa labels.
- 3 tests unitarios.
- gioser-web refactoreado para consumir TaskList: sync_taskbar arma
Vec<Task> y delega; on_click del taskbar dispara minimize/restore_from_tab
según estado. install_taskbar reducido a sólo home buttons.
Trazos zodiacales (gioser-shaders + canvas-web):
- 12 líneas radiales muy sutiles entre la chacana y el aro principal, una
por signo, con colores significativos:
Aries→fuego rojo, Tauro→tierra verde, Géminis→aire amarillo,
Cáncer→agua plata, Leo→fuego dorado, Virgo→tierra marrón,
Libra→aire rosa, Escorpio→agua rojo profundo, Sagitario→fuego púrpura,
Capricornio→tierra verde oscuro, Acuario→aire celeste, Piscis→agua
verde mar.
- Aries empieza en el norte, giran en sentido horario (rueda zodiacal
clásica). Banda radial r∈[1.05*L, 0.96*ringR_main], gauss angular
con σ=0.0042 rad (~0.24° de ancho), multiplier 0.55 → apenas visible.
- Uniform `vec3 u_zodiac[12]` subido como array plano de 36 floats vía
uniform3fv. Constante ZODIAC_COLORS expuesta en canvas-web por si otros
callers la quieren.
Workspace verde + 21 tests (geom 6 + palette 4 + physics 3 + pluma-md 5
+ barra-web 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Visual de la chacana retrabajado contra chakana.png de referencia:
- Sol detrás (gauss + corona, masked al interior de la chacana — sólo
asoma por la superficie de la cruz, no se cuela afuera).
- Doble outline dorado (línea principal + paralela offset 0.020), color
CHACANA_LINE pasa de cyan helado a dorado-ámbar del logo.
- Interior con niebla violeta-noche (u_dark_color) y rayos radiales
sutiles desde el centro, modulados por sin(t * 0.3).
- Aro doble exterior: ring fino interior + ring grueso con 4 grupos de
3 puntos cardinales (calculados angularmente, no rayos largos).
- WORLD_SCALE 1.45→1.05, MAX_TILT 35°→28° (más sólido, menos caricaturesco).
Título "GioSer" centrado dentro de la superficie de la chacana, sin
subtítulo. Se inclina junto con la chacana vía CSS perspective +
rotateX/rotateY desde u-tilt-x/y inyectadas cada frame por WASM.
Botones (4 tips):
- Reposicionados a `arm_extent * 1.32` (entre punta y aro grueso).
- Bigger: min-width 168px, glyph 54px, label Cinzel 0.95rem.
- Doble anillo en hover (::before con border + glow).
- Cuando un drawer se abre, fade-out de tips + canvas + brand.
Drawers MD (uno por elemento):
- `<aside class="drawer drawer-{element}">` con transform-origin desde
CSS vars (--origin-x/y) seteadas por WASM al click — crece desde la
posición exacta del botón hasta fullscreen en 700ms con cubic-bezier.
- Ambience por elemento: AIRE (radial drift), FUEGO (flicker keyframe),
AGUA (tide vertical), TIERRA (warm earth gradient).
- Cerrado con botón X, Escape o data-close-drawer.
- Carga MD desde ./md/{element}.md via spawn_local + Reader::open_url.
Pluma (visor MD agnóstico, dos crates nuevos):
- `crates/modules/pluma/pluma-md` — wrapper sobre pulldown-cmark 0.12.
API: to_html(), to_themed_html(md, theme) con sanitización del theme,
events() para AST stream. GFM completo. No deps web. 5 tests.
- `crates/modules/pluma/pluma-reader-web` — toma HtmlElement, expone
open_url async (fetch via wasm-bindgen-futures), render_md sync,
show_loading/show_error. NO inyecta CSS — el host estiliza
`.pluma-doc[data-pluma-theme="..."]` con sus colores.
CSS pluma-doc completo: h1/h2/h3, code/pre con border-left accent,
blockquote, tables, lists, hr gradient. Loader spinner + error state.
Placeholders en md/{aire,fuego,tierra,agua}.md con texto seed.
Workspace verde + 18 tests (6 geom + 4 palette + 3 physics + 5 pluma-md).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- axis.rs: paint_axes extraído a función pública reusable entre
crates de visualización. LapalomaChartElement::paint_axes ahora
es un thin wrapper.
- OhlcBuffer: stride 6 f32 por bar (t, o, h, l, c, v). Bar struct
con is_bull/is_bear. price_range y time_range. 5 tests.
- aggregate_time_bucketed (sección 3.2 del ARCHITECTURE.md):
buckets por TIEMPO (no índice) — open=first, close=last,
high=max, low=min, volume=sum. Preserva volatilidad (los wicks
sobreviven al downsample, a diferencia de LTTB). Fallback a
copy 1:1 si el span temporal es cero. 4 tests cubren bucket
count, preservation of volatility, fallback, empty input.
- paint_candlesticks: render agnóstico contra el trait Canvas.
Wick = stroke_line vertical (high → low). Body = fill_rect
open ↔ close con color bull/bear/neutral. body_width derivado
del spacing entre bars (con body_min_width floor).
- LapalomaCandlestickElement: Element GPUI que reusa paint_axes
+ paint_candlesticks. Sin pan-blit cache en v0.1 (≤500 bars
on-screen no lo necesita).
- crates/apps/lapaloma-financial-demo: random walk determinístico
(xorshift32 inline + seed fijo) de 120 bars, pan + zoom + reset
igual que el cartesian demo. Paleta nórdica para bull (#a3be8c)
y bear (#bf616a).
60 tests verdes (28 cartesian + 20 core + 9 financial + 3 render).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- lapaloma-phosphor: feature `gpui` (default). LapalomaPhosphorElement
divide el RingBuffer en N segmentos (default 16, configurable) y
pinta cada uno como una stroke_polyline con alpha = (k+1)/N. El
segmento más nuevo va con alpha 1.0, el más viejo casi
transparente — efecto fósforo persistente.
- Cada segmento incluye el primer punto del siguiente para evitar
gaps visibles entre tramos.
- Wraparound se parte en dos sub-polilíneas (no concatenadas) para
no introducir la línea horizontal "del slot cap-1 al slot 0".
- Glow opcional: pasada adicional con width × glow_width_mult y
alpha × glow_alpha — efecto halo CRT.
- crates/apps/lapaloma-phosphor-demo: misma señal sintética que
stream-demo, paleta verde Tektronix (#9bff8c sobre #050805),
trail 24 segs + glow 4× α 0.18.
Limitación v0.1: el doc canónico usa triangle strip con per-vertex
color (sección 4.3); GPUI 0.2 no expone esa API directa. La impl
actual es funcionalmente equivalente con N draw calls en lugar de 1.
Cuando wgpu directo esté disponible, swap inmediato sin tocar las
API públicas.
46 tests verdes (sin cambios; phosphor se valida via demo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- lapaloma-stream: feature `gpui` (default). LapalomaStreamElement
pinta un RingBuffer en modo sweep — dos polilíneas split-at-head
(segmento [head..cap) viejo + [0..head) nuevo) para evitar la
línea horizontal del wraparound. Pre-fill (count < cap) sólo
pinta [0, head) para evitar el flat-line del 1.0.2 fix.
- y_range configurable, background opcional, padding.
- crates/apps/lapaloma-stream-demo: osciloscopio sintético con
RingBuffer cap=512. Timer en cx.background_executor que hace
push(synthesize(t)) + cx.notify() cada 16ms (60 Hz). Señal =
suma de dos sinusoides desfasadas + jitter determinístico.
Header muestra cap / head / filled% / t / revision en vivo.
- Workspace: registrada la app lapaloma-stream-demo.
Showcase del P2 zero-alloc: push(v) son 2 writes + 2 increments,
zero allocations per frame, ningún Vec se reasigna.
46 tests verdes (sin cambios; el stream se valida en runtime via demo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cadena end-to-end DataBuffer → LineSeries → Canvas → gpui::Window
funcionando. cargo run -p lapaloma-demo abre una ventana con sin(x)
sobre 1024 muestras y una sola paint_path por frame.
- lapaloma-render: feature `gpui` opcional. WindowCanvas adapter
traduce el trait Canvas a paint_quad/paint_path de gpui 0.2.
Conversión RGB→HSL para integrar con el sistema de colores Hsla
del resto del codebase yahweh. 3 tests de conversión.
- lapaloma-cartesian: feature `gpui` (default). element::LapalomaChartElement
con impl Element + IntoElement. Arma WindowCanvas en paint() y
delega a LineSeries — un solo paint_path por chart.
- crates/apps/lapaloma-demo registrado en workspace.
Limitaciones conocidas v0.1: clip stack, triangle strips y draw_text
no implementados (los necesitan phosphor / Sankey / axes; se
agregan en sus fases).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Daemon escribe append-only a $XDG_STATE_HOME/shipote/audit.log además
del tracing. Single-line: ts=<ms> uid=<peer> action=<verb> <detail>.
Rotación simple a .log.1 al pasar 1 MiB.
- shipote-gateway: TCP listener 127.0.0.1:7378 default. POST /rpc traduce
JSON ↔ postcard contra daemon socket. GET / health text. HTTP parser
ad-hoc (~70 LOC), sin dep de hyper/axum. Sin auth — bind a localhost
+ SHIPOTE_TRUST_ANYONE=1 en prod.
E2E: curl --noproxy '*' POST /rpc → "Pong", Health JSON, Capabilities
JSON. Audit log persiste mutaciones con uid del peer.
85 tests pasan (features nuevos son binarios, no library mods).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Iter 22. Cierra el set de hoy: future-me (o cualquier nuevo collab)
levanta el escenario completo con un comando.
crates/apps/brahman-demo/ con 3 binarios:
- brahman-demo-broker: Server::bind standalone con Broker. Reemplaza
a ente-zero para demos (ente-zero es PID 1 con kernel surface,
child subreaper, bus, brain, audit — overkill).
- brahman-demo-producer: Card con flow.output[demo-stream:json].
- brahman-demo-consumer: Card con flow.input[demo-feed:json] —
mismo type → matchea con producer.
Env vars en los 3: BRAHMAN_INIT_SOCKET, BRAHMAN_BROKER_CONTEXT,
BRAHMAN_DEMO_LABEL/FLOW/TYPE, RUST_LOG.
scripts/bootstrap-demo.sh:
- Modes: all (default) / broker / only.
- Cleanup-safe: trap mata todos los PIDs spawneados (SIGTERM grace
+ SIGKILL fallback) y borra el socket.
- Espera al socket antes de spawnear (evita ENOENT en handshake).
- Logs separados por proceso bajo $BRAHMAN_DEMO_LOG_DIR.
Smoke end-to-end (sin DISPLAY): consumer recibe MatchEvent
{ Available, demo-feed ← demo-stream, via: Exact, pinned: false }
automáticamente cuando entra el producer. Match fluye por el push
channel del broker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 21. Cierra el loop iniciado en iter 20: ahora se ven sesiones
+ matches actuales + cómo cambian a través del tiempo.
brahman-handshake/messages:
- Frame::ListMatches → Frame::MatchList(Vec<brahman_broker::Match>).
brahman-handshake/server:
- run_post_handshake pasa Option<&SharedBroker> a handle_inbound_frame.
- Sin broker configurado → MatchList vacía (no error).
brahman-handshake/client + brahman-sidecar:
- Client::list_matches() análogo a list_sessions, drena MatchEvents.
- list_matches / list_matches_blocking, mismo patrón.
brahman-broker-explorer:
- Poll-tick agrega list_matches_blocking además de list_sessions.
- last_match_keys: HashSet<MatchKey> para diff entre ticks.
- timeline: VecDeque<TimelineEntry> cap 50.
- diff_matches (free fn): Available para keys nuevas, Lost para
desaparecidas. Primer tick marca todo Available (boot UX).
- Render: stat_card "Timeline" con HH:MM:SS {+/-} formato compacto.
5 tests broker-explorer (3 nuevos del diff). Stack verde.
Decisión: timeline polled cada POLL_INTERVAL=5s, no push. MatchEvents
del broker son consumer-céntricos (cada session ve sólo SUS matches);
"system-wide timeline" requeriría broker subscribe-all (mucho scope).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Iter 15. El patrón "tarjeta de dashboard con border-l accent +
label + value grande + descripción + listing opcional" tenía 2
consumers (minga-explorer + brahman-broker-explorer). Ahora vale
extraer al stack yahweh.
crates/modules/ui_engine/widgets/stat-card/:
- pub fn stat_card(cx, label, value: impl Into<SharedString>,
description, accent, text, text_dim, recent_items: &[String])
-> impl IntoElement.
- Compone yahweh-widget-card::card_themed; sin theme directo
(caller pasa text/text_dim ya resueltos).
- 3 tests #[gpui::test] con TestAppContext + theme: smoke con/sin
items, type-check value.
minga-explorer:
- Borra fn stat_card local (~60 líneas).
- Borra dep yahweh-widget-card.
- 3 callsites pasan value.to_string() (widget acepta
Into<SharedString>).
brahman-broker-explorer:
- fn state_card refactorizada como wrap del stat_card compartido,
preservando la traducción ProbeState→(accent,value,description)
como helper local app-specific.
- Borra dep yahweh-widget-card.
Sub-header del listing: pasa de "recent (N de TOTAL):" a
"recent (N):" — el widget no conoce TOTAL; el caller lo pone en
label si quiere ("Nodos AST (5 de 247)"). Trade-off aceptable
por reusabilidad genérica.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 14. Cierra otro frente: visibilidad del broker handshake. La
app probe cada 5s vía await_provider_blocking y reporta 4 estados
claros (Pending / Down / UpNoProvider / UpWithProvider).
crates/apps/brahman-broker-explorer/:
- Deps: brahman-handshake + brahman-sidecar + stack yahweh themed.
- ProbeState enum con 4 variants.
- Polling cx.spawn cada 5s; el probe blocking se ejecuta en
cx.background_executor().spawn para no congelar el main thread.
- Configuración via env: BRAHMAN_INIT_SOCKET, BRAHMAN_BROKER_PROBE_FLOW
(default broker-health), BRAHMAN_BROKER_PROBE_TYPE (default ping).
- UI: header probe info + theme switcher; banner permanente
Error/Warning/Success según estado; stat card con accent color.
- 2 tests sanity.
Smoke run verificado: bootstrap OK, panic esperado sin display.
Apps GUI themed: 5 (nakui-ui + 3 explorers + ahora broker-explorer).
Limitaciones: observer Card se registra/desregistra en cada probe;
no muestra lista global de Cards (handshake no expone API). Para
timeline real de MatchEvents hace falta mantener el Client vivo
entre probes — scope futuro.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>