Commit Graph

102 Commits

Author SHA1 Message Date
sergio 18c0344a52 feat(shipote): throughput card + rate-limit + snapshot incremental (fase Q)
- shipote-shell Flow channels card extiende con bytes_total + bytes/s
  por socket. Lookup helper evita borrows en closures.
- DiscernPolicy.max_bytes_per_sec: splitter task hace sleep proporcional
  al tamaño de chunk tras cada broadcast. Token-bucket simple v1.
- WorkspaceManager.dirty: AtomicBool. mark_dirty() en mutaciones que
  afectan al snapshot. save_snapshot skip si clean y path existe.
  restore_snapshot resetea dirty=false (hidratación no es mutation).

85 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 26, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 16:20:50 +00:00
sergio 3486949d24 feat(shipote): throughput + stats persistente + auth peer (fase P)
- FlowMeter (atomic u64 + rolling window 32 samples) en cada FlowChannel.
  flow_throughput() → (socket, bytes_total, bytes_per_sec). CLI:
  shipote flow throughput. Idle threshold 5s = rate 0.0.
- Snapshot v4 con stats_history persistente por workspace (cap 16).
  PersistedStats separado para evitar Instant. Restore hidrata el VecDeque
  con source="persisted".
- Auth SO_PEERCRED: daemon rechaza peers con uid distinto al propio.
  SHIPOTE_TRUST_ANYONE=1 = escape hatch documentado.

84 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 25, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 13:58:41 +00:00
sergio 1cce50b290 feat(shipote): collision detection + stats history server-side (fase O)
- Flow socket names usan pipeline_id full (ULID 26 chars) + edge_idx.
  Cero colisiones entre pipelines (ULID es único global). Fallback con
  suffix -N si el path existe (cap 1000 retries).
- WorkspaceState.stats_history (VecDeque cap 64) — workspace_stats
  appendea cada call. API workspace_stats_history(id, tail). Protocol
  WorkspaceStatsHistory. Shell pide history al primer probe → sparkline
  hidratada al boot, sobrevive restart del shell.

84 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 25, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:55:21 +00:00
sergio a823c40fe1 feat(shipote): drain shutdown + persist live pipelines + batched query (fase N)
- Daemon SIGTERM/SIGINT: snapshot ANTES, stop_with_grace(1s) de todos
  los workspaces DESPUÉS. Grace permite app-level cleanup.
- Snapshot v3 con live_pipelines: pipeline_supervisors se persisten;
  daemon relanza al restore con sus recursos (Incarnator+DiscernPipeline).
  RestoreOutcome separado para que core no necesite incarnator.
  Forward-compat v1/v2 via #[serde(default)].
- WorkspaceFullSummary: stats+quota+commands+flow_sockets en 1 roundtrip.
  Shell reduce N×4 requests/probe a N×1 + 4 globales.

83 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 24, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:48:11 +00:00
sergio c3f9c9e36a feat(shipote): pipeline backoff + quota card + logs follow (fase M)
- PipelineSpec.restart_backoff_ms + restart_max_backoff_ms + restart_max:
  backoff exponencial entre relaunches (anti-thrash). take_pending_restarts
  aplica restart_max (0 = infinito); excedido = supervisor descartado con
  warning. Daemon hace tokio::sleep(backoff) antes del relaunch y escala
  current_backoff x2 hasta el cap.
- shipote-shell card "Quota breaches": probe extiende con WorkspaceQuota
  por workspace. Color rojo si hay breaches, verde si no.
- shipote logs --follow: poll cada 200ms al daemon, imprime suffix nuevo
  hasta que el comando termine. Sin cambios al protocolo. Best-effort:
  si el ring rota más rápido que el poll, se pierden bytes.

83 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 24, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:34:27 +00:00
sergio 4c9d1b4c1d feat(shipote): quota enforce + cgroup memory.max + pipeline restart (fase L)
- WorkspaceSpec.quota_enforce: QuotaAction (None|Log|Kill) por recurso
  (mem, nproc). reap_dead aplica policy; Kill usa stop_with_grace(ZERO).
- ente_incarnate::cgroup::apply_rlimits_to_cgroup escribe memory.max y
  pids.max. WorkspaceManager::create_with_id lo invoca si soma.cgroup.path
  y delegation. Kernel hace OOM kill al exceder; falla silenciosa si no
  hay delegation.
- PipelineSpec.restart_on_failure: bool. register_pipeline_supervisor
  retiene spec; reap_dead detecta all-dead + any-failed → push a queue;
  daemon reaper drena y relanza pipeline ENTERO (los pipes intermedios
  no permiten restart parcial).

82 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 24, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 10:22:46 +00:00
sergio 324a0c2d5d feat(shipote): multi-core CPU% + quota report + restart-on-failure (fase K)
- WorkspaceStats.cpu_cores via sysconf cacheado. CLI muestra
  `cpu_pct: 98.7 % (24.7% total / 4 cores)`.
- workspace_quota compara SomaSpec.rlimits contra accounting actual.
  Reporta breaches humanos. NO enforcement automático en v1.
- run_with_options(.., restart_on_failure): si exit != 0, reaper
  relaunch con backoff exponencial 200ms → 30s cap. Inner.restart_specs
  persiste el spec entre intentos.

81 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 22, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 01:32:39 +00:00
sergio d8727a3038 feat(shipote): CPU% + pipeline live-tail + replay por bytes (fase J)
- CPU% derivado server-side entre samples (WorkspaceState.last_cpu_sample).
  100% = 1 core saturado. Primer sample devuelve None (sin baseline).
- shipote pipeline run --tail: tras lanzar, suscribe al primer flow_socket
  y vuelca bytes hasta EOF. Auto-implica --tap.
- DiscernPolicy.replay_bytes: cap adicional por bytes para el replay
  buffer del FlowChannel. evict_for_incoming considera el chunk entrante
  para que post-push el buffer NUNCA exceda los caps.
- shipote-shell: stats history extiende sparkline con %CPU.

80 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 21, shipote-discern 5, yahweh-provider-fs 3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:57:04 +00:00
sergio 36dac00c8d feat(shipote): data plane + DAG fan-in/out + stats + lifecycle (fases F-I)
Pipeline runtime:
- Fan-out 1→N (splitter task replica al N consumers) y fan-in N→1 (merger
  task con mpsc + reader-per-input). DAGs no lineales soportados.
- Flow channels: Unix socket + tokio broadcast con replay buffer
  configurable por pipeline (DiscernPolicy.replay_chunks). Subscribers
  externos vía `shipote flow tail <socket>`.
- Templating en specs con `${KEY}` (CLI `--var KEY=VALUE`). Walk
  recursivo sobre serde_json::Value, soporta todos los strings del schema.
- Pipelines guardados (`pipeline save/saved-list/drop/run-saved`)
  persisten con el snapshot.

Lifecycle de comandos:
- Log capture per-stream (stdout/stderr separados) via pipe O_CLOEXEC +
  AsyncFd. CLI `shipote logs <ws> <cmd> --stream {stdout,stderr,both}`.
- Stop graceful con tiempo configurable: SIGTERM → grace → SIGKILL.
  Tanto a nivel workspace como pipeline individual.
- TTL auto-stop ya existente (Fase C) sigue funcionando.

ente-incarnate:
- ChildStdio declarativo (Fase C) + ChildPreExec declarativo nuevo:
  NoNewPrivs, ParentDeathSig, Dumpable, NewSession, Chdir, Umask.
- Aplicación pre-execve async-signal-safe en ambos paths (plain via
  Command::pre_exec, namespaced via callback del clone(2)).

Observabilidad:
- WorkspaceStats: RSS + RSS peak (VmHWM o memory.peak cgroup) + CPU usec
  + uptime. Fuente per-proc o cgroup según delegation.
- shipote-shell con sparkline ASCII por workspace (history cap 24),
  card de flow channels activos, vista de comandos + saved pipelines.
- Tap → broker: cada edge enriquecido con TypeRef se anuncia como Card
  efímera vía SidecarPool (graceful si broker no corre).

Discern:
- Integrado en yahweh-provider-fs (mime_type en EntityNode).
- Integrado en nouser-core::cluster::pick_lens como fallback cuando la
  extensión cae a Lens::Grid.

79 tests pasan: ente-incarnate (16), nouser-core (27), shipote-card (8),
shipote-core (20), shipote-discern (5), yahweh-provider-fs (3).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 00:29:46 +00:00
sergio c22d2480b9 shell 2026-05-10 21:58:16 +00:00
Sergio 3d55f189c0 feat(brahman-demo): bootstrap script reproducible — broker + producer + consumer + 4 explorers
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>
2026-05-10 16:06:11 +00:00
Sergio a97f6b98f3 feat(brahman-handshake): ListMatches endpoint + timeline en broker-explorer
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>
2026-05-10 15:53:38 +00:00
Sergio 99cd685dc1 feat(brahman-handshake): ListSessions endpoint + cliente + UI broker-explorer
Iter 20. Nuevo flujo end-to-end para observabilidad: cualquier módulo
conectado puede pedir al broker la lista de sesiones activas y mostrar
labels + flows in/out por cada una.

brahman-handshake/messages:
- Frame::ListSessions(ListSessions{session}) → Frame::SessionList(SessionList{entries}).
- SessionEntry: session, label, schema_version, outputs, inputs, conscious.

brahman-handshake/server:
- run_post_handshake pasa SessionRegistry a handle_inbound_frame.
- build_session_list helper proyecta el snapshot bajo lock.
- Validación session_id mismatched → Unauthorized.

brahman-handshake/client:
- Client::list_sessions() async, drena MatchEvents intermedios al
  pending_events buffer, mismo patrón que ping().

brahman-sidecar/discovery:
- list_sessions / list_sessions_blocking arman Card observer mínima,
  piden, Farewell.

brahman-broker-explorer:
- Poll-tick agrega list_sessions_blocking cuando broker está UP*.
- stat_card "Sesiones activas" con count + items ordenados por Ulid:
  label · in:[flows] out:[flows]  (wit)?.

Test list_sessions_returns_currently_registered: 3 clientes
conectados, observer pide list, verifica labels + schema_version
+ conscious=false. 24 handshake tests + sidecar + broker-explorer
verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:40:33 +00:00
Sergio 37e40073ef 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>
2026-05-10 15:19:17 +00:00
Sergio be3a0e78fc feat(yahweh-widget-app-header): promover header standard de explorers
Iter 16. Patrón con 4 consumers idénticos: nakui-explorer,
nouser-explorer, minga-explorer, brahman-broker-explorer todos
declaraban un header flex_row + flex_grow(label) + theme_switcher
+ bg(panel) + border-bottom + text_size(14) + padding(16/12).
Ahora es 1 línea.

crates/modules/ui_engine/widgets/app-header/:
- pub fn app_header(cx, label: impl Into<SharedString>) -> impl IntoElement.
- pub fn app_header_with(cx, label_child: impl IntoElement) — variante
  para left side no-text.
- 3 tests #[gpui::test].

Migración 4 consumers:
- Cada uno: bloque de ~13 líneas → 1 línea app_header(cx, text).
- Borra dep yahweh-widget-theme-switcher (incluida vía app-header).
- Reemplaza import.

Ahorro ~50 líneas UI hardcoded. Cambios visuales del header (padding,
border, text_size) en un solo lugar.

Decisión: sidebar header del MetaApp NO se migra — es de sidebar,
no de app top, styling distinto. Diferente patrón → diferente widget.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 12:40:56 +00:00
Sergio af7417ce08 feat(yahweh-widget-stat-card): promover patrón stat card como widget
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>
2026-05-10 12:33:02 +00:00
Sergio 1493d616fd feat(brahman-broker-explorer): nueva app probe del broker brahman
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>
2026-05-10 12:23:16 +00:00
Sergio 99838b849b feat(minga-explorer): listings de items recientes en cada stat card
Iter 12. Hasta ahora minga-explorer mostraba sólo counts. Ahora
cada stat card muestra un sample de los 5 primeros items: hashes
truncados de nodos AST (con kind), atestaciones (content_hash ←
did_short) y claves MST.

- RepoSnapshot agrega 3 Vec<String/(String,String)> con recent
  items, cap RECENT_LIMIT=5.
- load_snapshot itera los stores con filter_map(Result::ok) +
  take(5). Errores per-item silenciados — dashboard tolerante a
  corrupción puntual.
- short_hash(&str) local: trunca a 12 chars (48 bits).
- stat_card extendido con recent_items: &[String]. Si no vacío,
  agrega "recent (N de TOTAL):" + una linea por item.

Tests: 2 → 4 (sanity defaults + short_hash).

Beneficio: tras `minga ingest`, el explorer muestra los hashes
de los nodos creados sin necesitar queries SQL.

Limitación: los "recent" no son cronológicos (sled ordena lex
por hash). Timeline real requiere timestamp al schema.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:37:31 +00:00
Sergio 2790b6dc8a feat(minga-explorer): nueva app dashboard del repo Minga sobre stack yahweh
Iter 11. Cierra integración del módulo Minga (VCS semántico P2P)
al ecosistema GUI. Antes Minga sólo tenía CLI; ahora hay un
dashboard GPUI con counts del repo en vivo.

crates/apps/minga-explorer/:
- Deps: minga-store + yahweh-theme + 3 widgets compartidos.
  Sin minga-cli (sin passphrase prompts) ni minga-core.
- PersistentRepo abierto directo (counts son lectura pública,
  sin passphrase). El DID sigue requiriendo `minga status` CLI.
- Refresh polling 2s (mismo pattern que nakui/nouser explorer).
- 3 stat cards: Nodos AST, Atestaciones, Claves MST. Cada una
  con border-l accent + label + número grande + descripción.
- Helper stat_card() factoriza la card.
- Header con título dinámico + theme switcher.
- error_banner themed.
- 2 tests sanity (missing dir errors, RepoSnapshot default).

Smoke run verificado: bootstrap completo OK, panic esperado en
open_window sin display.

Apps GUI themed: 4 (nakui-ui, nakui-explorer, nouser-explorer,
minga-explorer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:27:59 +00:00
Sergio 2f426b0171 feat(nouser-explorer): integración al stack yahweh themed
Iter 10. nouser-explorer (paralela a nakui-explorer pero para ver
Mónadas via daemon nouser) tenía colors hardcoded idénticos al
patrón previo. Aplico el mismo refactor: theme global instalado,
chrome a slots, widgets compartidos.

- Nuevas deps: yahweh-theme + 3 widgets (banner, card,
  theme-switcher).
- main() instala Theme::install_default.
- render: 4 vars rgb() chrome → theme slots.
- Header: flex_row + theme switcher a la derecha (mismo pattern
  que nakui-explorer).
- error_banner: div hardcoded → banner_themed(Error).
- 2 cards (Engine/Monad): div().flex_col().p().bg().rounded()...
  → card_themed(cx).border_l_4().border_color(accent).
- Accents semánticos (engine cyan, data purple) quedan locales
  como señales de dominio.

Las dos apps explorer del repo ahora comparten paleta themed +
switcher común.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:09:33 +00:00
Sergio 8fdef818cc feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime
Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed +
widgets que la consumen, faltaba el control UI para rotar entre
presets en vivo. Ahora hay un botón yahweh que muestra el theme
actual y al click avanza al siguiente.

crates/modules/ui_engine/widgets/theme-switcher/:
- pub fn theme_switcher(cx: &mut App) -> impl IntoElement: botón
  clickable con bg=panel_alt, hover=row_hover, label "Tema: <name> ▸".
  Al click hace Theme::set(cx, Theme::next_after(current.name)).
- 2 tests #[gpui::test]: smoke + verificación de cambio de global.
- Dev-dep gpui con test-support.

Migración:
- nakui-explorer: header pasa a flex_row con label flex_grow + switcher
  alineado derecha.
- yahweh-widget-meta-form (sidebar): mismo pattern en el header
  "Nakui" del sidebar.

Tests stack: 115 → 117.

Beneficio: click cambia toda la paleta en vivo. 6 presets disponibles
(Nebula, Aurora, Sunset, Flat Dark, Solarized Light, High Contrast)
ciclables circularmente.

Limitación: TextInput entities tienen colors hardcoded; migrar
text_input al theme es iter futura.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:37:49 +00:00
Sergio 9632d8c4a7 feat(yahweh): theme integration en banner + card + nakui-explorer consume themed
Iter 4 de integración nakui↔yahweh. Los widgets banner y card
ofrecen variants _themed(cx, ...) que leen Theme::global(cx).
Versiones sin theme preservadas para apps sin theme global.

yahweh-widget-card:
- Nueva dep yahweh-theme.
- pub fn card_themed(cx: &App) -> Div: card() + bg(theme.bg_panel).

yahweh-widget-banner:
- Nueva dep yahweh-theme.
- pub fn banner_themed(cx, kind, msg) -> Div: deriva (bg, fg) según
  kind + theme.is_dark. Info usa accent del theme; Success/Warning/
  Error usan hue fijo (verde/amber/rojo) + lightness flippeada.
- pub fn themed_colors(kind, theme) -> (Background, Hsla) helper.
- 3 tests nuevos del derivation.

nakui-explorer:
- Nueva dep yahweh-theme.
- main() instala Theme::install_default antes de open_window.
- render: 5 vars rgb() locales → theme slots (bg_app/fg_text/etc).
- card() → card_themed(cx). banner() → banner_themed(cx).
- Accents semánticos del log (seed azul, morphism verde) quedan
  locales: son señales del dominio, no chrome.

Tests: 112 → 115 (+3). Stack intacto.

Beneficio: cambiar de Theme refleja en nakui-explorer automático.
Próximo candidato: migrar MetaApp (paleta hardcoded de 6 colors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:11:29 +00:00
Sergio 1be7bf9b1e feat(yahweh-widget-card): container card-shape compartido para timeline entries
Iter 3 de integración nakui↔yahweh. El card visual pattern (padding
consistente + rounded + flex_col + gap) que vivía duplicado en cada
timeline entry de nakui-explorer ahora es un widget yahweh reusable.

crates/modules/ui_engine/widgets/card/:
- pub fn card() -> Div: flex_col + px(12) + py(8) + mb(4) +
  rounded(4) + gap(2). Sin colores (caller decide via builder).
- 1 test smoke.

nakui-explorer:
- Los 2 timeline entry patterns (Seed/Morphism) pasan de ~7
  calls a ~3, intención "card with accent" emerge del nombre.

Tests stack: 111 → 112. App-agnostic — el widget no impone paleta,
permite themes diversos.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:02:23 +00:00
Sergio 715cf6be03 feat(yahweh-widget-banner): widget compartido para toasts/errores cross-app
Patrón visual común a yahweh-widget-meta-form (toast success +
error banner) y nakui-explorer (error banner): div con bg + text
colored según severidad. Antes duplicado con colores hardcoded en
cada consumer.

Crate nuevo crates/modules/ui_engine/widgets/banner:
- pub enum Banner { Info, Success, Warning, Error } con bg()/fg()
  hardcoded por variant.
- pub fn banner(kind, message) -> Div: padding/text_size defaults;
  caller compone con .child()/.px()/.on_click()/etc.
- 2 tests sanity (no color collisions).

Migración:
- yahweh-widget-meta-form: 12 líneas hardcoded → 2 llamadas a
  banner().
- nakui-explorer: error banner usa banner() + override de
  padding custom del header.

Tests stack: 109 → 111 (+2). Cada crate compila individualmente.

Próximo natural: confirm_delete_banner (Warning + botones) puede
extraerse como modal-banner cuando emerja segundo consumer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 09:57:32 +00:00
Sergio d5ef7144b5 feat(yahweh-meta-runtime): promover short_hash y preview_value desde nakui-explorer
Continúa la integración de apps nakui al stack yahweh. Los
helpers visuales que nakui-explorer tenía locales y son reusables
suben a yahweh-meta-runtime/format.

yahweh-meta-runtime:
- short_hash(h: &[u8; 32]) -> String: hex de los primeros 4 bytes.
- preview_value(v: &Value, max: usize) -> String: JSON one-liner
  truncado con "..." (edge case max < 3 sin panic).
- 5 tests nuevos.

nakui-explorer:
- Nueva dep yahweh-meta-runtime.
- Borrado helpers locales (short_uuid + short_hash + preview_value)
  + 4 tests duplicados.
- Imports desde yahweh-meta-runtime.

Tests: 42→47 yahweh-meta-runtime, 7→3 nakui-explorer (los 3 que
quedan son específicos del explorer: load_log, breakdown,
missing_file). Resto intacto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 09:38:50 +00:00
Sergio b3a99f38dc refactor(yahweh): Fase 2c — widget render extraído a yahweh-widget-meta-form
Cierra el refactor de UI. El widget render (forms, lists, modal de
delete, EntityRef selector, sidebar, key handlers) deja de vivir en
nakui-ui y pasa a un crate yahweh nuevo, genérico sobre MetaBackend.

crates/modules/ui_engine/widgets/meta-form/ (yahweh-widget-meta-form):
- pub MetaApp<B: MetaBackend> con todo el state UI + impl Render
  + handlers + render_*. El bound `B: MetaBackend` se propaga.
- pub fn new(modules, backend, initial_toast, initial_error, cx):
  constructor sin bootstrap. Caller pre-construye todo.
- Helpers locales del widget: lookup_field, append_compact_msg,
  format_seed_toast.
- Cero deps a nakui o brahman-cards. Reusable por cualquier app.
- 3 tests funcionales puros (lookup_field, append_compact_msg,
  format_seed_toast).

nakui-ui (shell):
- main.rs: 1959 → 424 líneas (78% reducción). Sólo bootstrap:
  load_ui_modules + load executors + NakuiBackend::open +
  cx.open_window con MetaApp::<NakuiBackend>::new como root view.
- Dep nueva yahweh-widget-meta-form.
- Tests E2E quedan: event_log_replay, morphism_pipeline real
  sales, load_ui_modules x3 (4 shell). NakuiBackend tests siguen
  en backend.rs (8). Widget tests en su crate.

Distribución total tests refactor yahweh:
- yahweh-meta-schema: 8
- yahweh-meta-runtime: 42
- yahweh-widget-meta-form: 3
- brahman-cards: 26
- nakui-ui: 12 (4 shell + 8 backend)
Total: 91 tests cubriendo el área.

Cada crate compila individualmente. Fase 3 (shell mínimo) era
implícita en F2c — el shell ya quedó en 424 líneas.

Pendientes restantes: KCL → Nickel, eliminar card.k.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 02:00:34 +00:00
Sergio 2462aca444 refactor(yahweh): Fase 2b — MetaBackend trait + NakuiBackend + MetaUi consume el backend
3 steps en un commit:

A) yahweh-meta-runtime/backend.rs: trait MetaBackend con 6 métodos
   (list_records, load_record, seed, update, delete, morphism) +
   WriteOutcome { id, changed, post_status }. 9 tests con MemBackend.

B) nakui-ui/backend.rs: NakuiBackend struct con store/log/executors/
   compaction. NakuiBackend::open() compone log+snapshot+replay+tick;
   impl MetaBackend mapea cada método al pipeline nakui-core.
   snapshot_path_for / maybe_compact_log se mueven acá. 7 tests del
   impl.

C) MetaUi consume el backend:
   - 6 fields colapsan en `backend: NakuiBackend`.
   - MetaUi::new pasa de ~150 líneas a ~10 (delega a NakuiBackend::open).
   - commit_seed / commit_morphism / commit_delete delegan al trait;
     CommitOutcome enum eliminado, reemplazado por WriteOutcome.
   - tick_runtime_compact eliminado (interno al backend; el msg sale
     por WriteOutcome.post_status).
   - validate_entity_refs callsite usa cierre sobre backend.load_record.
   - Imports nakui_core::delta y event_log salen de main.rs (sólo
     quedan en tests E2E).

Tests: 33→42 yahweh-meta-runtime (+9 trait), 14→21 nakui-ui (+7
backend impl). 97 totales en el área. Cada crate compila individualmente.

Pendiente Fase 2c: extraer widget render (form/list/modal/EntityRef)
al crate yahweh — ahora trivial porque el render solo consume
&self.modules + self.backend (via trait).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 01:48:49 +00:00
Sergio 6104484498 refactor(yahweh): Fase 2 — extraer helpers puros a yahweh-meta-runtime
Sigue de la Fase 1 (lift del schema). Ahora los helpers puros que
cualquier widget renderer o backend ejecutor consume sobre el schema
viven en yahweh-meta-runtime. Sin GPUI, sin nakui — usa cierres en
lugar de traits para decoupling máximo.

Crate nuevo crates/modules/ui_engine/libs/meta-runtime:
- parse.rs: parse_field_value, infer_param_value, resolve_param_value.
- delta.rs: compute_field_delta, compute_clear_fields.
- refs.rs: validate_entity_refs(load: F, refs) con cierre
  Fn(&str, Uuid) -> Option<Value> en vez de trait Store.
- format.rs: human_label_for_record, render_value, value_to_input_text,
  short_uuid.
- 33 tests propios.

nakui-ui:
- Nueva dep yahweh-meta-runtime.
- Borrado código local equivalente (~200 líneas) + 34 tests
  duplicados.
- validate_entity_refs callsite usa cierre:
  validate_entity_refs(|e, id| store.load(e, id), &refs).
- 14 tests runtime-específicos quedan (compact/snapshot/event-log/
  morphism pipeline/load_ui_modules).

Distribución tests: 48 → 14 nakui-ui; +33 yahweh-meta-runtime.
Cada crate afectado builds + tests limpio individualmente. Workspace
build full no completó esta corrida por OOM al compilar
surrealdb-core (ambiental, no relacionado).

Fase 2b pendiente: extraer render widgets (form/list/modal/
EntityRef selector) a yahweh — requiere diseñar MetaBackend trait.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 01:17:17 +00:00
Sergio f5987d9cfc refactor(yahweh): Fase 1 — nakui-ui-schema → yahweh-meta-schema
Primer paso del refactor yahweh. El schema de UI declarativa no
tiene acoplamiento real con Nakui (sólo dep en serde/thiserror) —
movemos a yahweh para que cualquier app metadata-driven lo use sin
pasar por nakui.

Mecánico:
- git mv crates/modules/nakui/ui-schema → crates/modules/ui_engine/libs/meta-schema.
- Crate name: nakui-ui-schema → yahweh-meta-schema.
- Workspace members[] actualizado (sección yahweh, no nakui).
- Consumers actualizados: brahman-cards (Cargo.toml + lib.rs +
  readers.rs), nakui-ui (Cargo.toml + main.rs).
- Self-test (example_modules.rs): import + path rebase (5 niveles
  arriba ahora).

Documental:
- Doc del crate ahora dice "metainterfaz (yahweh meta-schema)" +
  "backend-agnostic" en filosofía.
- Module.nakui_module_dir documentado como "path opaco al backend";
  se conserva el nombre por compat con módulos ya escritos +
  serde alias "backend_module_dir" para futuro rename suave.

Tests: 13 yahweh-meta-schema + 26 brahman-cards + 48 nakui-ui
verdes. Workspace build verde.

NO hace Fase 1: mover widgets a yahweh (Fase 2), trait MetaBackend
(Fase 3), renombrar nakui_module_dir.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:38:25 +00:00
Sergio f6361bbdca feat(nakui-ui): migrar consumer al brazo brahman_cards::load_cards_from_dir
Primera consumer migration del brazo. nakui-ui ya no llama a
nakui_ui_schema::load_modules_from_dir directamente; pasa por
brahman_cards::load_cards_from_dir y extrae UiModule del CardBody.

Beneficios concretos:
- Soporta .ncl además de .json (templates Nickel + merge funcionan
  en cualquier subdir de modules).
- Cards de otros body kinds (Ente/Monad) se skipean limpio con
  toast informativo, no rompen la carga.

Cambios en brahman-cards:
- Nuevo load_cards_from_dir(dir) + variante con readers/filenames
  custom. DEFAULT_CARD_FILENAMES = [card.ncl, card.json, module.ncl,
  module.json] (orden de prioridad).
- 4 tests nuevos del helper.

Cambios en nakui-ui:
- Nueva dep brahman-cards.
- Helper load_ui_modules(dir) -> (Vec<Module>, Vec<String>) envuelve
  el brazo, filtra a UiModule, aplica Module::validate(), detecta
  duplicate ids.
- MetaUi::new usa el helper, emite toast con cards skipped si las hay.
- 3 tests e2e nuevos.

26/26 brahman-cards verdes (+4). 48/48 nakui-ui verdes (+3).
Workspace build verde.

nakui_ui_schema::load_modules_from_dir queda intacto (sus tests lo
usan + otros consumers futuros pueden preferirlo). Migración opt-in.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:32:47 +00:00
Sergio ffdfa6f8d7 feat(nakui-ui): validación cross-field del EntityRef (existence en store)
parse_field_value(EntityRef) sólo validaba forma (UUID parseable).
Un UUID válido pero inexistente pasaba al log/store, dejando
dangling references. Ahora validamos también existencia contra la
entity declarada en FieldSpec.ref_entity.

- Nuevo helper validate_entity_refs<S: Store>(store, refs):
  fail-fast loop sobre (label, target_entity, uuid) tuples; primer
  record ausente → error con label legible + UUID corto.
- commit_seed: durante el parse loop encolamos cada EntityRef +
  ref_entity + UUID parseado. Después del loop, una sola toma del
  store lock valida todos. Falla early: ningún log entry.
- Cobertura: SEED + EDIT. Morphism inputs ya cubierto por
  Executor::compute (load + EntityMissing) — documentado en el doc
  del helper.

5 tests nuevos del helper: happy path, fail-fast con detalles,
label en msg, lista vacía, distingue target entity.

45 tests verdes en nakui-ui.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:23:20 +00:00
Sergio f0c0a71860 feat(nakui-core,nakui-ui): FieldOp::Clear — borrar values vía form vacío
Nueva variante semántica del kernel: Clear { path } remueve la key
del record, distinta de Set { value: Null } (que deja la key con
valor literal null). Habilita "limpiar" un field optional vaciando
el input en la UI.

nakui-core:
- delta::FieldOp::Clear + simulate_on + capability_token (mismo
  shape que Set: "entity.field").
- MemoryStore::apply_dry_run y apply: Set/Clear comparten
  pre-condition (record existe + es objeto). Clear de field
  ausente = no-op silencioso.
- SurrealStore: equivalente con `UPDATE ... UNSET <field>`.
- Executor capability check: Set/Clear comparten match.
- Conservation rules NO consideran Clear (sólo Set) — documentado
  como morphism-author responsibility.

nakui-ui:
- commit_seed acumula `to_clear: Vec<String>` con optional empties
  en lugar de `continue` silencioso.
- EDIT branch: nuevo helper compute_clear_fields filtra a sólo los
  fields con current value non-null. Combina Set + Clear ops.
  NoChange ahora requiere ambos vacíos. Log entry incluye
  `cleared: [...]` sólo si non-empty. Updated.changed cuenta
  sets+clears.

Tests: +7 en nakui-core (4 store + 3 delta), +3 en nakui-ui.
Suites: 34/34 nakui-core, 40/40 nakui-ui. Workspace build verde.
E2E del morphism real intacto.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:15:42 +00:00
Sergio 613f4f299e feat(nakui-ui): snapshot/compaction durante runtime cada N writes
El compact ya no es sólo en boot: en runtime, después de cada write
efectivo (commit_seed Created/Updated, commit_morphism, commit_delete),
incrementamos un contador en memoria y disparamos maybe_compact_log
cuando alcanza el threshold (mismo NAKUI_SNAPSHOT_THRESHOLD del
startup). El log no crece sin tope en sesiones largas.

- Nuevos fields en MetaUi: snap_path, snapshot_threshold,
  writes_since_compact (los dos primeros cacheados en new()).
- Nuevo método tick_runtime_compact: increment + check + maybe
  compact + reset. Si compact falla, counter NO se resetea (próximo
  write reintenta). Threshold 0 desactiva.
- Helper append_compact_msg concatena el msg de compact al toast
  del op original con "; " separator.
- Wireado en los 3 callsites de write. NoChange (edit sin cambios)
  no cuenta — preserva "1 write = 1 log entry = 1 tick".

2 tests nuevos: format del helper, ciclo de 7 writes con
threshold=3 verifica 2 compacts + counter residual + log final con
1 anchor + 1 write.

37 tests verdes (+2). Workspace build verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 22:02:00 +00:00
Sergio 9951307fd9 feat(nakui-ui): atajo Esc para cancelar el modal de delete
`capture_key_down` en el root div: si event.keystroke.key=="escape" y
hay pending_delete, lo limpia y emite toast "delete cancelado (X)
[esc]". Capture phase (no bubble) intercepta el Esc antes de que
cualquier TextInput descendiente lo consuma. Sin pending el handler
es no-op, el evento sigue su flujo.

Hint visual en el banner: subtítulo amber tenue
"Esc para cancelar · click [Confirmar] para borrar" para que el
usuario descubra el atajo sin RTFM.

35 tests verdes. El handler son 8 líneas no-testeables sin GPUI cx;
type-check garantiza wireup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:58:15 +00:00
Sergio bcccf3b953 feat(nakui-ui): EntityRef validation en parse_field_value (UUID al submit)
Antes: parse_field_value(EntityRef, raw) devolvía Ok(json!(raw))
blindly. Garbage entraba al log/store si el field se usaba como
seed o param (sólo el path de morphism inputs validaba UUID).

Ahora: Uuid::parse_str(raw.trim()) → error claro si falla, value
trimmed si pasa. El selector clickable garantiza happy path; este
check es defensivo contra paste manual o garbage tipeado.

5 tests nuevos: happy path, trim de whitespace, rechazo de garbage,
rechazo de empty, propagación de error a resolve_param_value con
label del FieldSpec en el mensaje.

35 tests verdes. E2E del morphism real intacto (sus inputs van por
path dedicado, no por parse_field_value).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:39:31 +00:00
Sergio 7e2be96a57 feat(nakui-ui): snapshot/compaction automático del event log al startup
Wirea el snapshot machinery existente en nakui-core (Snapshot,
replay_with_snapshot_into, EventLog::compact_through) al runtime de
la UI. Reduce el costo de boot de O(events) a O(events_post_snapshot).

- Path: sibling del log, extensión `.snap.json`.
- Threshold via `NAKUI_SNAPSHOT_THRESHOLD` env (default 50; 0 = off).
- Helper `maybe_compact_log` captura snapshot + compacta dejando la
  última entry como anchor del cursor (sin anchor, EventLog::open
  vería log vacío y perdería next_seq).
- WAL order: write snap antes de compactar; un crash entre los dos
  da el mismo outcome al próximo boot (replay skipea entries
  cubiertas por snap).
- Fail-soft: snap load/compact errors no son fatales, sólo banner.

5 tests nuevos: derivación del path, dos no-ops bajo threshold, E2E
60 entries → snapshot+compact → reopen+replay → 60 records intactos.

31 tests verdes en nakui-ui. Workspace build verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:37:04 +00:00
Sergio 70f8c66548 feat(nakui-ui): edit delta-only — sólo campos modificados al log/store
Antes: editar un record emitía Set por *todos* los fields del form,
incluso los no tocados. Bloataba el log y oscurecía el intent. Ahora:

- Nuevo helper `compute_field_delta(current, proposed)` — devuelve
  sólo las entries que difieren (PartialEq de Value).
- Nuevo enum `CommitOutcome { Created, Updated{changed}, NoChange }`
  para que el toast sea preciso ("actualizado X (2 campo(s))" vs
  "sin cambios — no log entry").
- `commit_seed` en path EDIT carga current del store, calcula delta,
  return early si vacío (no log entry, no apply). Si no vacío emite
  `Morphism{ ui.edit_record, params.fields=delta }` con sólo los
  campos modificados.

5 tests nuevos del helper: delta vacío, sólo campo cambiado, current
Null = todo nuevo, int vs string, ignora fields ausentes del proposed.

27 tests verdes. SEED path inalterado, E2E del morphism real verde.

Limitación: edit no puede *clearear* un value vaciando el input
(empty optional fields ya hacían `continue` antes del delta). Para
soportar eso haría falta `FieldOp::Clear`, no necesario hoy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:24:27 +00:00
Sergio fdb9bbe058 feat(nakui-ui): confirmación de delete vía banner modal antes de borrar
El click en `✕` ya no borra inmediatamente: marca el record como
pendiente y abre un banner amber al tope con [Cancelar] / [Confirmar].
Sólo [Confirmar] llama `commit_delete` (la lógica del store/log no
cambia).

Cambios:
- Nuevo `MetaUi.pending_delete: Option<(String, Uuid)>`.
- Click en ✕ → set pending_delete + clear toast.
- Banner renderea como sibling del row sidebar+main en `flex_col`
  raíz; None cuando no hay pending. Texto: "¿Borrar {Entity} {id}?".
- [Cancelar] → toast informativo, sin tocar el store.
- [Confirmar] → limpia pending primero (evita banner colgado en
  caso de error) y llama `commit_delete`.
- `select_view` también limpia pending (record podría no ser visible
  en la nueva view).

22 tests verdes — la lógica del store/log no cambió, sólo el state
machine de UI. La compilación garantiza el wireup de las closures.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 21:20:16 +00:00
Sergio 46185951d0 feat(nakui-ui): validación estricta de params del morphism vía FieldKind
Reemplaza la heurística `infer_param_value` por parseo estricto basado
en el `FieldKind` declarado en el `FieldSpec` correspondiente. Un Boolean
con value "abc" ahora rebota en la UI con mensaje claro en lugar de
fallar opacamente dentro del morphism Rhai.

Cambios:
- Nuevo helper `resolve_param_value(field_name, raw, spec)`:
  - Required + empty → error con label legible.
  - Optional + empty → Value::Null.
  - Spec presente → parse_field_value(spec.kind, raw) estricto.
  - Spec ausente (módulo mal-formado) → fallback a infer_param_value.
- `commit_morphism` simplificado: el loop de params ahora delega al
  helper, que es testable sin GPUI.
- 6 tests nuevos cubriendo: número estricto, boolean rechaza "abc",
  required vacío, optional vacío → null, fallback a infer sin spec.

22 tests verdes (+6 nuevos). E2E del morphism real
`morphism_pipeline_executes_real_sales_vender` sigue verde — la
validación estricta no afecta el path correcto, sólo agrega rebotes
tempranos a values mal-tipados.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 20:59:11 +00:00
Sergio fc72726666 feat(nakui-ui): FieldKind::EntityRef — selector clickable de records existentes
Cierra el principal trade-off documentado del commit anterior:
"Inputs UUID a mano (no dropdown)". Los formularios pueden declarar
un campo entity_ref que apunta a una entity y el runtime renderea
una lista clickable de records existentes; click selecciona, el UUID
queda guardado para el submit.

Schema:
- Nueva variante FieldKind::EntityRef (serializa como "entity_ref").
- FieldSpec.ref_entity: Option<String> nuevo. validate() chequea que
  cualquier field con kind=entity_ref tenga ref_entity set.
- Nuevo SchemaError::EntityRefMissingTarget.

Runtime:
- render_entity_ref_selector helper: lista clickable debajo del input,
  cada item con etiqueta humana (heuristica: name > label > title >
  sku > sku_id > UUID corto) y click handler via cx.listener que
  setea el TextInput con el UUID completo. Highlight en accent color
  para el seleccionado.
- parse_field_value(EntityRef) devuelve string raw — validacion como
  Uuid es responsabilidad de commit_morphism downstream.
- Mensaje "(sin {entity}: crea uno antes...)" cuando lista vacia —
  el user sabe que hacer.

Demo actualizado sales_engine: vender_form.stock_id_input y
caja_id_input cambian a kind=entity_ref. Flujo nuevo: click en Stock
listado bajo input, click en Caja, escribir venta_id/cantidad/precio/
timestamp, submit. Sin copiar UUIDs.

Tests: 2 nuevos schema (validate detecta EntityRef sin ref_entity y
acepta con ref_entity) + 4 nuevos runtime (parse, human_label cubre
todos los key fallbacks). 29 tests totales (16 + 8 + 5).

Pendientes: confirmacion de delete, snapshot/compaction del log,
edit delta-only, validacion estricta de params del morphism via
FieldKind del FieldSpec en lugar de infer_param_value.
2026-05-09 20:48:59 +00:00
Sergio 932e7464d7 feat(nakui-ui): Action::Morphism wired al pipeline real (compute -> log -> apply)
Cierra el ultimo gran TODO de la metainterfaz Nakui: las acciones
Action::Morphism ya no son un toast informativo; despachan al
Executor cargado del manifest nakui-core (nsmc.json + schemas KCL +
scripts Rhai), pasando por el pipeline completo: compute (con
dry-run + KCL post-checks) -> log append -> store apply.

Schema nakui-ui-schema extendido:
- Module.nakui_module_dir: Option<String> nuevo. Path al modulo
  nakui-core. Sin esto, Action::Morphism quedan no-op con toast.
  SeedEntity sigue funcionando (alta administrativa sin manifest).
- Action::Morphism gano dos campos opcionales:
  - inputs: BTreeMap<String, String> — mapeo role -> field_name.
  - params: Vec<String> — fields cuyos values van al params JSON.
    Si vacio, todos los fields no-input van a params.

Runtime nakui-ui:
- MetaUi.executors: BTreeMap<String, Arc<Executor>> nuevo. Carga
  Executor::load_module(nakui_module_dir) en MetaUi::new.
- commit_morphism: resuelve inputs (parsea UUIDs), arma params
  (Value object con tipos inferidos), llama
  execute_and_log_with_recovery. Toast con count de ops o error.
- infer_param_value: heuristica i64 -> f64 -> bool -> string.

Tests: 2 nuevos. E2E morphism_pipeline_executes_real_sales_vender
carga el modulo real crates/modules/nakui/modules/sales, ejecuta
"vender" con inputs Stock+Caja y params (cantidad=5, precio=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacio).
- stock.cantidad: 100 -> 95.
- caja.saldo: 1_000_000 -> 1_001_000.

12 tests verdes en nakui-ui (+1). Schema extension no rompio nada
(6 unit + 5 integration siguen verdes).

Demo nuevo: examples/nakui-modules/sales_engine/module.json apunta
al sales real via nakui_module_dir. 6 vistas (list+form para Stock/
Caja/Venta + "Vender" con Action::Morphism). El user crea Stocks +
Cajas con seed_entity, copia los UUIDs a los inputs de "Vender", y
ejecuta el morphism real con KCL post-checks.

Activacion:
  NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
  NAKUI_MODULES_DIR=examples/nakui-modules \\
  cargo run -p nakui-ui

Trade-offs:
- Inputs UUID a mano (no dropdown). Nice-to-have: FieldKind::EntityRef
  que renderee selector.
- Inferencia de tipo en params es heuristica.
2026-05-09 20:41:37 +00:00
Sergio 170d1f890a feat(nakui-ui): edit + delete de records (ciclo CRUD completo)
Cierra "no hay UI para editar/borrar" del commit anterior. Cada fila
de la lista gana dos botones (edit, delete); el form view se reusa
para alta y para edit; el delete es inline. Las mutaciones pasan por
LogEntry::Morphism con sus ops, asi el replay restaura el estado
correcto.

Cambios:
- MetaUi.editing: Option<(String, Uuid)> nuevo. Set al click ✎,
  cleared al cambiar de view o tras submit.
- open_edit(mod_idx, entity, id, cx): setea editing, busca primer
  Form view del modulo cuya entity matchee, navega ahi.
- select_view extendido: cuando carga un Form, si editing matchea y
  el record existe, pre-llena cada input con value_to_input_text del
  record (inverso de parse_field_value).
- commit_seed ramifica:
  - Edit path: emite LogEntry::Morphism { name: "ui.edit_record",
    ops: [Set per field] }. Aplica via store.apply.
  - Seed path: alta nueva (comportamiento previo).
- commit_delete(entity, id): emite LogEntry::Morphism { name:
  "ui.delete_record", ops: [Delete] } + apply.
- Render del form: titulo y submit label cambian segun editing
  ("Editar customer abc..." / "Guardar cambios").
- Render de la lista: dos columnas nuevas — id, acciones. Cada fila
  con ✎ (edit, accent) y ✕ (delete, rojo) + hover states.

Coherencia con el modelo: todo cambio post-seed pasa por ops dentro
de Morphism. nakui-explorer muestra estos morphisms con sus ops en
la timeline.

Trade-offs:
- schema_hash: None sigue (legacy path) hasta Action::Morphism
  wireé Manifest.
- Delete sin confirmacion (1 click).
- Edit sobreescribe todos los campos del form (no delta-only).

Tests: 3 nuevos. 10 totales:
- value_to_input_text_inverse_of_parse + round_trip — la propiedad
  del pre-llenado.
- event_log_replay_handles_full_crud_cycle — E2E: seed + edit +
  delete via log, replay desde cero deja store vacio. Replay parcial
  deja valores editados.

Activacion:
  NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
  NAKUI_MODULES_DIR=examples/nakui-modules \\
  cargo run -p nakui-ui
2026-05-09 20:32:06 +00:00
Sergio d60ee5eab2 feat(nakui-ui): persistencia con event log + replay al startup
Cierra "sin persistencia entre runs" del commit anterior. Cada
SeedEntity se appendea al nakui_core::event_log::EventLog con WAL
semantics (log antes que store) y al re-abrir el binario el replay
reconstruye el MemoryStore desde cero. Cerrar y volver a abrir ya
no borra el data.

Cambios:
- MetaUi.event_log: Option<Arc<Mutex<EventLog>>> nuevo. Compartido
  bajo Mutex para que commit_seed pueda mutar.
- Apertura + replay al startup: path por env NAKUI_EVENT_LOG, default
  ./nakui-ui-state.jsonl. EventLog::open + replay_into reconstruyen
  el store. Toast: "log nuevo" o "log X cargado: N evento(s)
  replayed".
- WAL en commit_seed: log.append(LogEntry::Seed { ..., schema_hash:
  None }) antes de store.seed. Si append falla, cancela operacion.
- schema_hash: None es el path "legacy / pre-versioning" documentado
  para seeds que no pasan por Manifest+Executor. Correcto para alta
  via metainterfaz hasta que Action::Morphism wire el Manifest.
- Degradacion gracil: si abrir log falla -> toast error + sigue
  in-memory.

Tests: 1 nuevo E2E event_log_replay_restores_memory_store que escribe
2 seeds via EventLog::append, re-abre + replay_into store fresh,
verifica records con values correctos. 7 tests verdes en nakui-ui.

Activacion con persistencia:
  NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
  NAKUI_MODULES_DIR=examples/nakui-modules \\
  cargo run -p nakui-ui

Pendientes:
- Action::Morphism (cargar Manifest junto a Module).
- Snapshot/compaction para logs grandes.
- UI para editar/borrar records existentes (hoy solo alta).
- Widget input simple sin selection/IME/clipboard.
2026-05-09 20:20:48 +00:00
Sergio 5d584ff815 feat(nakui-ui): inputs reales + click handlers funcionales
Cierra dos limitaciones documentadas del commit anterior: los
formularios ahora aceptan teclado real, y los clicks en menus +
botones mutan estado correctamente.

Cambios:
- Cada FieldSpec del Form materializa un Entity<TextInput> de
  yahweh-widget-text-input al entrar a la vista. Los entities se
  reemplazan al cambiar (drop limpio). Soporta: escribir caracteres,
  Backspace, Enter (Confirmed event no usado todavia), Escape.
  Cursor renderea como "|" al final.
- Click handlers wired via cx.listener: menus invocan select_view,
  botones invocan apply_action. Tienen acceso real al
  Context<MetaUi> y mutan el modelo + cx.notify.
- commit_seed reemplaza el buffer ad-hoc por
  input.read(cx).text() por cada field. El value parseado va al
  MemoryStore con tipo correcto.
- Reset de inputs tras submit (set_text("")) si no hay next_view —
  flujo de alta consecutiva sin re-tipear.
- Hover states en sidebar y botones.
- Theme::install_default(cx) al inicio (requerido por text_input).

Wire: deps nuevas yahweh-widget-text-input + yahweh-theme.

Limitaciones que siguen:
- Action::Morphism: requiere cargar Manifest de nakui-core.
- Sin persistencia entre runs (wire con EventLog cuando daemon Nakui
  exista).
- Widget input es simple (sin cursor positioning, selection, IME,
  multilinea, copy/paste).
- Enter no envia (TextInputEvent::Confirmed no suscrito; submit va
  por click). Trivial de wirear si se necesita.

Tests: 6 unit verdes. Visual requiere cargo run + manual.

Activacion:
  NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui
2026-05-09 20:11:33 +00:00
Sergio 06c4fb9130 feat(nakui): metainterfaz declarativa + 6 modulos ERP estandar
Salto cualitativo: Nakui pasa de "library + demos + read-only viewer
del event log" a plataforma ERP con UI dirigida por datos. Cada
modulo de negocio se declara como un module.json (sin codigo Rust
nuevo) y el runtime GPUI lo carga dinamicamente: sidebar de menus,
listas con columnas configurables, formularios de alta.

3 entregables:

1. Crate nakui-ui-schema (datos puros): Module, View::List/Form,
   FieldSpec con FieldKind {Text|Multiline|Number|Boolean|Date},
   Action {OpenView|SeedEntity|Morphism}. Module::from_path,
   Module::validate, load_modules_from_dir(dir). 6 tests unit + 4
   integration.

2. Crate nakui-ui (binario GPUI): carga modulos desde
   NAKUI_MODULES_DIR. Sidebar + main panel. List view con tabla
   weighted; form view con campos labeled + submit que ejecuta
   SeedEntity contra MemoryStore in-process compartido. Toast +
   error banner. 6 tests unit.

3. 6 modulos demo en examples/nakui-modules/:
   - customers (nombre, email, telefono, credito, notas)
   - products (SKU, nombre, categoria, precio, stock)
   - suppliers (razon social, ID fiscal, contacto, terminos pago)
   - inventory_movements (fecha, tipo, SKU, cantidad, costo, motivo)
   - sales_orders (numero, cliente, fechas, estado, totales)
   - invoices (numero, cliente, fechas, totales, pagado, moneda)

Filosofia: UI como datos. Persistencia universal (MemoryStore hoy,
SurrealStore manana, sin tocar module.json). Schema primero, semantica
despues.

Activacion:
  NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui

Limitaciones conocidas (proximos iters):
- Inputs sin teclado (GPUI no lo trae nativo; integrar
  yahweh-widget-text-input).
- Click handlers no propagan mutacion al estado (refactor con
  cx.listener pendiente).
- Action::Morphism queda como TODO hasta cargar Manifest junto al
  Module.
- Sin persistencia entre runs (wire con EventLog/SurrealStore para
  cuando el daemon Nakui exista).

Tests: 16 totales nuevos. Lo que esto desbloquea: cualquiera puede
escribir un module.json para su dominio (pacientes, alumnos,
reservaciones) y aparece en la UI sin recompilar.
2026-05-09 19:54:21 +00:00
Sergio 5b8f71e0de feat(nakui-explorer): nuevo binario GPUI — Nakui visible en la interfaz
Cierra "nakui no tiene UI propia" del audit. Nuevo binario standalone
nakui-explorer (paralelo a nouser-explorer) que renderea el event log
de un repo Nakui: timeline scrollable de seeds + morphisms con sus
parametros, breakdown por entity type, polling cada 2s para detectar
nuevos eventos appended sin restart.

Diseno: lee directamente el archivo .jsonl del nakui_core::event_log::
EventLog. Path por env NAKUI_EVENT_LOG, default "nakui.jsonl" en pwd.
Sin discovery via broker brahman porque nakui hoy es CLI/library/demos,
no daemon. Cuando se daemonice, sustituir el lector de archivo por
un sidecar consumer (mismo patron que nouser-explorer).

UI:
- Header: path, count total + breakdown seeds/morphisms, reload time.
- Breakdown line: top 5 buckets por frecuencia (entities + morphisms).
- Timeline: tarjetas color-coded por kind (azul=seed, verde=morphism)
  con #seq, kind, entity/morphism, id corto, preview de data/params,
  schema hash o "(legacy)". Mas-recientes-primero, hasta 200 visibles.
- Error banner si lectura falla; el explorer no crashea, sigue
  intentando cada 2s.

Wire: nuevo crates/apps/nakui-explorer/ agregado al workspace. Deps
minimas: nakui-core, gpui, serde_json, uuid (feature serde). Sin
deps de brahman (Nakui standalone).

Tests: 7 unitarios cubriendo load_log (in-order, missing-file),
breakdown (counts + buckets), preview_value (truncate + intact),
short_uuid + short_hash.

Activacion:
  NAKUI_EVENT_LOG=/tmp/nakui_inv_xxx.jsonl cargo run -p nakui-explorer

Estado del CHANGELOG global tras este commit: cero pendientes
fundamentados activos. Lo unico que queda es minga-vfs (FUSE,
explicitamente diferido) y mejoras nice-to-have (cobertura adicional
per-lenguaje, daemon-izacion de nakui para sidecar discovery).
2026-05-09 19:33:50 +00:00
Sergio 6f993f4268 refactor(explorer+card): independencia jerarquica enforced
Cierra el unico debt estructural detectado en el audit de
independencia: nouser-explorer ya no arrastra nouser-core (que
aportaba notify/walkdir/sled/blake3 al grafo de compilacion de una
UI que solo habla JSON contra un socket).

- Cliente movido: engine_socket::client::list_monads (~60 LOC, std +
  serde_json puros) emigra de nouser_core::engine_socket a
  nouser_card::query::client. Vive donde viven los wire types,
  consistente con el principio "un consumer importa el contrato, no
  el runtime del productor".
- Drop dep: nouser-explorer deja de depender de nouser-core.
  Verificado con cargo tree: notify, sled, blake3 desaparecen del
  grafo del binario.
- Fallback "falla hacia la simplicidad": nueva resolve_socket() en
  el explorer intenta primero broker discovery; si el broker no
  responde / no hay init vivo, fallback directo al
  default_socket_path. El explorer queda funcional contra un daemon
  huerfano (standalone sin init) — completa "consciente cuando hay
  ecosistema, soberano cuando esta solo".
- socket_source gana tercer estado "default-path" para visibilidad.

Audit estructural confirmo que el resto del ecosistema ya respeta
el principio. Brahman es pegamento opcional, no chasis obligatorio
— y ahora el grafo de Cargo lo enforcea, no solo la convencion.

Tests: 4 + 10 + 27 verdes. Cliente movido ejercitado end-to-end
por los 3 tests integracion de engine_socket.
2026-05-09 03:32:11 +00:00
Sergio 2ae888bc8f feat(explorer+daemon): discovery dinamico via broker + query socket
Cierra el "explorer encuentra al daemon de forma totalmente dinamica"
del meta-plan. La UI deja de hardcodear el socket admin: descubre al
daemon nouser via MatchEvent::Available del broker y le consulta sus
Monadas directo.

Pipeline end-to-end:
- Daemon publica engine Card con service_socket = $XDG_RUNTIME_DIR/
  nouser-engine.sock y flow.output = monad-list:json.
- Daemon binda Unix socket en ese path con listener blocking que
  sirve nouser_card::query::QueryRequest::ListMonads, responde
  ListMonadsResponse { engine, monads: Vec<MonadView> }.
- Explorer construye consumer Card con flow.input matched,
  brahman_sidecar::await_provider_blocking le devuelve el socket,
  y nouser_core::engine_socket::client::list_monads lo consulta.
- Cachea el socket; cualquier fallo de query lo invalida y la
  proxima iteracion re-descubre.

Wire types nuevos en nouser_card::query:
- QueryRequest::ListMonads
- ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }
- MonadView: proyeccion slim de MonadManifest SIN centroid ni
  members (KB que no tienen por que viajar cada poll).
- transport::default_socket_path() con env override.

Listener en nouser_core::engine_socket: spawn_listener + client
blocking con QueryError tipado. 3 tests integracion verdes.

Refactor explorer:
- Drop dep brahman-admin, add brahman-sidecar/nouser-card/nouser-core.
- State: socket cache + snapshot + socket_source informativo.
- TickOutcome enum desacopla la I/O del UI.

Trade-offs: polling 2s (no streaming — broker no empuja Data cards
hoy), re-discovery full en error (discovery es barato).

Tests: 10 (nouser-card +3 query) + 27 (nouser-core +3 engine_socket)
+ 4 (sidecar) verdes. Explorer compila clean.
2026-05-09 03:08:20 +00:00
Sergio 5c41ef920d feat(nouser): yahweh widget — nouser-explorer panel GPUI
Bin GPUI standalone que consulta brahman-admin cada 2s y renderea
todas las sesiones del Init como cards. Cierra el círculo visual
del ecosistema brahman.

- Crate nuevo crates/apps/nouser-explorer (deps: brahman-admin,
  brahman-card, gpui).
- Ventana 900x640 con header del estado del Init, banner de error
  cuando no conecta, y lista de cards (una por sesión).
- Cada card muestra: kind + label + lifecycle, ULID corto, summary
  (si data), keywords, lens hint, service_socket si está, y refs
  (RelationshipKind → target_label). El borde izquierdo coloreado
  diferencia ente (azul) de data (lavanda).
- cx.spawn(async move |this, cx| { ... }) corre el loop de refresh
  en el GPUI executor; query_blocking porque GPUI no tiene runtime
  tokio.
- Helper nuevo en brahman-admin: client::query_blocking(path) —
  versión sync de query(), para callers con su propio executor.

Uso:
  $ ente-zero & nouser daemon crates/core &
  $ cargo run -p nouser-explorer
  # ventana 900x640, ~6 cards en vivo, refrescando cada 2s.

cargo check --workspace: 0 errores, 0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 19:54:49 +00:00
Sergio 70a7a0d46d feat: segundo módulo (nakui) + admin API + brahman-status
Dos cosas en una sesión, en el orden discutido:

(1) Segundo módulo brahman vivo: nakui-core
  - crates/modules/nakui/core/Cargo.toml: deps brahman-card,
    brahman-sidecar, ulid.
  - crates/modules/nakui/core/src/bin/nakui.rs: brahman_card_for_nakui()
    construye una Card como Lifecycle::Daemon, Supervision::Restart,
    flow.input "command" (json) + flow.output "report" (json). El
    cmd_run llama brahman_sidecar::spawn antes de levantar el server
    de nakui.

(2) crates/shared/brahman-sidecar (estrena crates/shared/)
  Boilerplate del sidecar extraído (DRY): el thread con tokio current
  thread runtime, conexión vía Client::connect, ping loop. Yahweh y
  nakui ahora consumen este crate. API:
  - spawn(card)                    fire-and-forget
  - spawn_with_handle(config)      con JoinHandle
  Example "presence" útil para demos: módulo dummy con label tomado
  del primer arg que se queda vivo hasta SIGTERM.

(3) crates/core/brahman-admin: observabilidad del broker
  Socket Unix paralelo en \$BRAHMAN_ADMIN_SOCKET (default
  \$XDG_RUNTIME_DIR/brahman-admin.sock). Cada conexión recibe un
  StatusSnapshot JSON line-delimited y se cierra. Compatible con nc/socat.
  - StatusSnapshot { server, protocol, init_attached, sessions, matches }
  - server::AdminServer
  - client::query(path)
  - example "brahman-status" CLI

(4) Wiring de ente-zero
  En primordial_loop, junto al handshake server, ahora también levanta
  AdminServer con misma política de degradación grácil.

(5) brahman-broker: BrokeredCard ahora incluye lifecycle. Endpoint y
  Match derivan Serialize/Deserialize. Nuevo método cards() expone
  iterador de BrokeredCard para que el admin pueda construir snapshots.

(6) brahman-card: re-export pub use ulid::* para que módulos no
  necesiten depender de ulid directamente.

(7) yahweh-shell migrado al sidecar compartido. Su brahman_client.rs
  pasa de 96 a 53 líneas: sólo declara la Card, delega el spawn.

Demo end-to-end:
  $ ente-zero &
  $ presence demo.producer &
  $ presence demo.consumer &
  $ brahman-status

  Init: server=0.1.0 protocol=0.1.0 attached=true
  Sessions (2):
    01KR42TY1J... demo.producer  lifecycle=Daemon  priority=Normal
    01KR42TY1K... demo.consumer  lifecycle=Daemon  priority=Normal
  Matches (2):
    demo.producer.in  ←  demo.consumer.out  via Exact
    demo.consumer.in  ←  demo.producer.out  via Exact

El broker matchea bidireccional por tipo. El admin lo expone.

Tests: 27/27. cargo check --workspace: 0 errores.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 15:21:49 +00:00