Commit Graph

274 Commits

Author SHA1 Message Date
sergio ab03a61db4 feat(lapaloma-phosphor): trail CRT con alpha decay + glow
- 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>
2026-05-13 03:05:16 +00:00
sergio 4796f2652d feat(lapaloma-stream): osciloscopio CRT con RingBuffer en sweep mode
- 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>
2026-05-13 02:58:25 +00:00
sergio 66d18ab47c feat(lapaloma-cartesian): multi-series en LapalomaChartElement
- Element ahora mantiene Vec<ChartSeriesItem> con DataBuffer +
  StrokeStyle + nombre opcional por serie. Builder add_series y
  add_series_named.
- En paint(), una pasada por cada serie reusando el mismo scratch.
  N series = N paint_path (no N × por punto). Cumple P3 del
  ARCHITECTURE.md por serie.
- `lapaloma_chart(data, vp, stroke)` queda como helper retrocompat
  para el caso una-serie.
- Demo: 3 series simultáneas (sin, cos, mix) con colores nórdicos
  + leyenda textual en el header.

46 tests verdes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 02:55:15 +00:00
sergio 138307c2ba feat(lapaloma-demo): pan + zoom + double-click reset interactivos
- viewport.rs: `pan_fraction(fx, fy)` — pan en fracción del viewport
  independiente del plot_rect. Útil cuando el handler GPUI trabaja
  en coords de window y no conoce el rect interno del chart.
- lapaloma-demo: state machine de drag (DragAnchor con snapshot del
  viewport al click) + handlers on_mouse_down/move/up para pan,
  on_scroll_wheel con sensitivity exponencial 0.0015 para zoom
  anchor-preserving al cursor, on_click con click_count >= 2 para
  reset al viewport inicial. El header muestra estado dragging.
- Maneja ScrollDelta::Pixels (trackpad) y ::Lines (mouse wheel
  tradicional) unificando con line-height 16px.

46 tests verdes en lapaloma-{core,cartesian,render}.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 02:52:51 +00:00
sergio fd25369715 feat(lapaloma-cartesian): ejes con ticks y labels decimadas
- axis.rs: ticks_nice (Wilkinson sobre lapaloma_core::scale::nice_step),
  decimate_labels con min_spacing_px, format_tick con decimales según
  step, AxisStyle config. 8 tests.
- gpui_backend::draw_text: shape_line via window.text_system() + iterate
  glyphs con paint_glyph. Sin dep en App context (sólo &mut Window).
- LapalomaChartElement.paint_axes: línea base + tick marks + labels
  centrados (X) / right-aligned (Y) con decimación. Margins por defecto
  reservan 32px izq + 24px abajo.

45 tests verdes en lapaloma-{core,cartesian,render}.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 02:43:01 +00:00
sergio 97c09bc96a feat(lapaloma): backend GPUI + LapalomaChartElement + app demo
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>
2026-05-13 02:36:41 +00:00
sergio 791ca18d81 feat(lapaloma-cartesian): viewport + coord_system + LineSeries con LTTB
- ChartViewport: pan/zoom anchor-preserving en coords de dominio.
- CoordinateSystem: proyección dominio→pixel + project_buffer zero-alloc.
- trait Series + LineSeries que emite una sola stroke_polyline por frame
  (valida P3 del ARCHITECTURE.md). LTTB se dispara cuando data.len()
  excede 3× el ancho del plot.
- hit_test sobre coords sorted-by-X con binary search + threshold 8px.
- 14 tests cubren pan, zoom, projection, downsample y hit-test.

Element GPUI queda para la siguiente fase (requiere pionear paint custom
sobre PaintContext — el monorepo no tiene precedente todavía).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 02:24:30 +00:00
sergio 88051d220a creation 2026-05-13 02:17:40 +00:00
sergio 52acaabcf4 gioser 2026-05-12 18:55:29 +00:00
sergio 6596c81271 feat(shipote): audit log persistente + HTTP gateway (fase S)
- 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>
2026-05-11 17:16:11 +00:00
sergio d962fe4601 docs(shipote): README + 4 docs en docs/ (ARCHITECTURE, CLI, RECIPES, DEVELOPMENT)
- README.md en crates/modules/shipote/ como entry point.
- docs/ARCHITECTURE.md — 11 crates, capas, decisiones (O_CLOEXEC,
  dirty AtomicBool, pipeline restart entero, etc.) + snapshot versioning.
- docs/CLI.md — referencia comando por comando, flags, env vars.
- docs/RECIPES.md — specs TOML para workspaces y pipelines típicos.
- docs/DEVELOPMENT.md — compilar, correr daemon/shell/CLI, tests,
  smoke E2E manual, debugging FDs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 17:10:44 +00:00
sergio a9124449f9 feat(shipote): health endpoint + audit log + token-bucket real (fase R)
- Request::Health → Response::Health { version, uptime_ms, alive_*,
  active_flows, dirty }. CLI: shipote health.
- handle_client lee peer_uid una vez al accept. audit_request emite
  info!(target: "audit", uid, action, detail) por mutación (create/stop/
  run/pipeline.*/flow.drop). Reads omitidos. Filtrable con SHIPOTE_LOG=
  warn,audit=info.
- TokenBucket real reemplaza rate_limit_sleep: refill por wall time,
  capacity = 1s de rate, debt negativo dispara sleep proporcional.
  Permite burst real, no chunk-by-chunk uniforme.

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:58:10 +00:00
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 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 7fb2ad3b1e fix(nakui-core): schema_bundle_hash usa bytes reales del schema, no del bundle
Iter 17. Regresión surfaceada por verify_log_rejects_seed_after_schema_kcl_changes.

Bug: compute_schema_bundle_hash operaba sobre los bytes del bundle
compilado, que es `(import "/abs/path") & ...`. Esos bytes no cambian
cuando se edita el archivo apuntado; sólo cambian si se mueve el
módulo o se agregan/quitan schemas. El hash quedaba pegado y un seed
firmado con schema vN se verificaba ok contra schema vN+1.

Fix: nueva fn read_schema_files_concat que lee cada schema declarado
y los concatena con framing `\0NCL:<name>\0`. Esos bytes alimentan
los dos hashers (schema_bundle_hash y morphism_schema_hash). El bundle
compilado sigue siendo imports-style (Nickel necesita los paths para
resolver), sólo la fuente del hash cambia.

Impacto: logs versados con el binario anterior fallan SchemaMismatch
al verificarse — comportamiento correcto (re-seed).

Test renombrado: verify_log_rejects_seed_after_schema_kcl_changes →
_after_schema_changes (residuo de la migración KCL→Nickel).

10/10 schema_versioning verde.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 13:10:48 +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 ce424eb6af feat(yahweh-theme): persistencia de la preferencia de theme entre runs
Iter 13. El switcher ya cambiaba el chrome en runtime, pero al
cerrar/reabrir el theme volvía a Nebula default. Ahora se persiste
en $XDG_CONFIG_HOME/yahweh/theme (default ~/.config/yahweh/theme)
y se restaura al boot.

yahweh-theme:
- pub fn config_path() -> Option<PathBuf>: resuelve XDG; None en
  sandbox/CI sin HOME ni XDG_CONFIG_HOME.
- pub fn load_persisted() / persist(theme): API pública.
  Variantes load_from_path / persist_to_path con path explícito
  para tests y apps custom.
- Theme::install_default(cx) ahora load_persisted o cae a Nebula.
- Theme::set(cx, theme) ahora persist + set_global. Best-effort:
  io errors ignorados (no rebota la UX por un secundario).
- 5 tests nuevos: round-trip, missing file, unknown name, create
  parent dir, XDG_CONFIG_HOME respetado.

API pública de install_default/set sin cambio de shape — todos los
downstream compilan sin tocar nada. Smoke run verificado.

Beneficio: 4 apps yahweh-themed comparten preferencia persistente.
Usuario puede preset via `echo Aurora > ~/.config/yahweh/theme`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:56:48 +00:00
Sergio ea074d7d57 feat(yahweh): caret blinking + slots ornament en theme + MetaApp full themed
Iters 8-9 combinadas. Tres mejoras pequeñas que cierran la
integración del theme:

1) text-input caret blinking: caret_visible bool toggea cada 500ms
   via cx.spawn loop. _blink_task se mantiene en self para que
   drop cancele. render dibuja | sólo si focused && caret_visible.

2) yahweh-theme: 5 slots ornament secundario como methods (no
   fields) derivados de is_dark via ornament_slots() helper:
   bg_input/bg_button/bg_button_hover/accent_destructive/
   bg_destructive_hover. No requiere modificar los 6 presets.

3) MetaApp ornament cleanup: 11 rgb(0x...) hardcoded → slots del
   theme. Sidebar menu items, list row separator/buttons, icon ✕
   delete y su hover, EntityRef selector hovers, form submit
   button + fallback input bg, confirm modal hint y hovers.

Pattern: let X = theme.slot() antes de las closures + move |d|
d.bg(X) en hover/when para tomar ownership.

Antes MetaApp tenía la paleta principal themed (iter 5) pero el
ornament secundario seguía hardcoded. Ahora el theme switcher
cambia absolutamente todo el chrome.

Tests: 117 verdes. Downstream compila. Smoke nakui-ui: bootstrap
OK.

Limitación: nouser-explorer todavía hardcoded — próxima iter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 11:05:39 +00:00
Sergio fc4da751ca feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus
Iter 7 (mini-iter — el text-input ya estaba themed, faltaba el
polish de focus visibility). Antes el border era siempre
accent_strong y el caret | siempre presente — imposible distinguir
cuál input está activo en un form con varios fields.

- Border focus-aware: focused → accent_strong; blur → border (slot
  tenue del theme).
- Caret | sólo on focus; sin focus se muestra texto plano (reduce
  ruido visual).
- render usa window.is_focused(focus_handle) para chequear.

Sin cambios en API pública. Tests downstream verdes.

Limitación: caret estático (no parpadea). Iter futura si emerge
la necesidad de animation timer via cx.spawn.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:51:00 +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 5885457628 feat(yahweh-widget-meta-form): paleta del chrome migrada a Theme::global(cx)
Iter 5 de integración. MetaApp::render tenía 7 vars con colors
hardcoded (bg/panel/border/text/text_dim/accent/accent_active);
ahora salen de Theme::global(cx) que el shell instala al boot.
confirm_delete_banner usa themed_colors(Warning/Error) para sus
colors base.

render:
- 7 let X = rgb(0x...) → theme.bg_app/bg_panel/border/fg_text/
  fg_muted/accent/accent_strong.
- toast_div / error_banner: banner() → banner_themed(cx).

Firmas internas:
- render_sidebar/main/list/entity_ref_selector/form cambian
  Rgba → Hsla (Background donde aplica para panel). gpui::Div
  acepta ambos via Into, uso interno no cambia.

confirm_delete_banner:
- 6 colors hardcoded → themed_colors(Warning) para banner base,
  themed_colors(Error) para Confirm, theme.bg_panel_alt+fg_text
  para Cancel.

NO migra esta iter (ornament hardcoded para una pasada futura):
row hovers, bordes sutiles entre filas, bg de inputs custom,
bg de botones del EntityRef selector, color del icon ✕ de delete.

Smoke test del binario verificado: bootstrap completo OK, panic
esperado en open_window sin display. 115 tests verdes (sin
cambio: los tests del widget no acceden al render).

Beneficio: theme switcher (cuando llegue) cambia toda la paleta
con 1 sola llamada Theme::set(cx, ...).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 10:20:08 +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 3e4278d766 feat(yahweh): MockBackend público + tests E2E del widget con TestAppContext
Cierra el ciclo de testabilidad del widget metainterfaz. Hasta
ahora los tests del MetaBackend trait vivían como impl privada en
backend.rs; el widget no podía testear handlers sin levantar
NakuiBackend (que depende de event log + Rhai).

yahweh-meta-runtime:
- Nuevo `pub mod testing` con MockBackend (renombre del MemBackend
  privado, ahora público). Constructores: new(), with_records(iter),
  with_morphism(name, handler) builder. Métodos de inspección
  total_records / records_for. Bajo `pub mod testing` (no cfg test)
  para que crates downstream lo usen en sus dev tests.
- Tests del trait en backend.rs simplificados: usan MockBackend en
  vez del MemBackend duplicado. 8 backend.rs + 9 nuevos del mock.

yahweh-widget-meta-form:
- Dev-dep nueva: gpui con feature "test-support" (TestAppContext).
- MetaApp::apply_action ahora pub (era privado). Necesario para
  invocar handlers desde tests E2E.
- Nuevo tests/widget_with_mock_backend.rs con 4 tests #[gpui::test]:
  meta_app_constructs, open_view_action_does_not_panic,
  backend_state_visible_from_widget_perspective,
  morphism_handler_can_be_registered_and_called_via_widget.

Tests: 47→56 yahweh-meta-runtime, 3→7 yahweh-widget-meta-form.
Total stack 109 verdes.

Limitación: render() no se invoca (requiere window context más
rico). Tests verifican state machine, no pixels. Snapshot tests
serían scope futuro.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 09:49:20 +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 b05de24c24 refactor(nakui-core): KCL → Nickel — kcl_wrapper reemplazado por evaluación in-process
Cierra el plan original. El motor de validación de entities deja
de shellear el binario externo `kcl` y pasa a evaluar Nickel
contracts in-process via la dep nickel-lang (la misma que usa el
brazo de cards). Los 3 schemas de sales/inventory/treasury migran
de .k a .ncl.

nakui-core:
- Nueva dep nickel-lang = "2.0.0".
- Borrado kcl_wrapper.rs.
- Nuevo nickel_validator.rs con vet(schema_path, state, entity)
  que evalúa `let bundle = (import "<schema>") in
  (std.deserialize 'Json m%%"<json>"%%) | bundle.<entity>`.
- executor.rs: KclError → NickelError, KclPre/Post/PostCreate →
  SchemaPre/Post/PostCreate, kcl_check → validate_entity.
  build_schema_bundle ahora emite `(import "X") & (import "Y") & ...`
  en lugar de concatenar bytes (cada .ncl es expresión completa).
- manifest.rs: default schema "schema.ncl", extract_schema_names
  reescrito para sintaxis Nickel record (CapitalCase keys con
  2-space indent).

Schemas migrados:
- sales/schema.ncl: Venta con std.contract.Sequence [record,
  from_predicate] para combinar shape + invariante cross-field
  (total == cantidad * precio_unitario). El patrón directo
  `record | from_predicate` rebota con "missing definition" porque
  el predicate evalúa antes de que el value populate el record;
  documentado en cada schema.
- inventory/schema.ncl, treasury/schema.ncl: idem.
- 3 schema.k viejos borrados; sales/nsmc.json paths actualizados.

Tests: refs Kcl* renombradas; paths .k → .ncl; tests inline que
escribían schema.k cambian a schema.ncl con sintaxis Nickel.
84 tests verdes en nakui-core.

Doc-only borrados:
- crates/core/ente-card/schema/card.k (REFERENCE ONLY).
- crates/core/ente-brain/schema/rule.k (REFERENCE ONLY).

Beneficios: sin dep externa al binario `kcl` (build CI limpio),
errores Nickel en línea con caret pointing al field, mismo motor
que cards (una dep para todo el repo), sin tempfile JSON
intermedio.

Cierra el plan original yahweh + KCL + card.k. Pendientes salen
de nuevo trabajo.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 02:59:48 +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 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 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 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 6be50c5b73 feat(minga-core): alpha-hashing per-language para Python, TS, JS, Go
Cierra el ultimo pendiente fundamentado del CHANGELOG. Cada lenguaje
soportado por minga tiene ahora su propio profile alpha-equivalente
— refactorings tipo "rename variable" no inflan el storage del repo
en ningun dialecto.

Refactor de alpha.rs (639 LOC) a modulo alpha/:
- alpha/common.rs: primitives compartidos (TAG_*, write_kind_and_field,
  emit_*, push_identifier_name). Garantiza wire bit-equivalente.
- alpha/rust.rs: logica Rust movida sin cambios funcionales.
- alpha/python.rs, alpha/ecmascript.rs, alpha/go.rs: nuevos.
- alpha/mod.rs: re-exporta hash_node_alpha (Rust legacy) + expone
  hash_alpha_with(dialect, node) que despacha al profile correcto.

Cobertura per-language:

Python: function_definition, lambda, for_statement, list/set/dict
comprehensions, generator_expression (con scope incremental:
binders del for_in_clause viven en clauses siguientes + body),
with_statement (recursando en as_pattern_target).

ECMAScript (TS+JS): function_declaration, function_expression,
method_definition, generator_function_*, arrow_function (paren y
shorthand), statement_block (con lexical_declaration y
variable_declaration introduciendo binders al resto), for_in_statement
(cubre for-of/for-in), for_statement (initializer C-style),
catch_clause, TS typed/optional parameters.

Go: function_declaration, method_declaration, func_literal (closure),
parameter_declaration con multi-name agrupados, block (con
short_var_declaration), for_statement con range_clause y for_clause,
if_statement con initializer.

Tests: 26 nuevos en alpha_polyglot.rs cubriendo rename invariants +
sanity negatives (function name matters, type matters, operation
matters) por cada lenguaje + cross-language sanity (mismo source en
distintos lenguajes -> hashes distintos).

141 tests verdes en minga-core (115 antes; +26 polyglot). 36 alpha
tests Rust intactos (sin regresion).

Pendientes Minga: minga-vfs (FUSE, proyecto independiente).
Cobertura adicional por-lenguaje (Python class, JS destructuring,
Go type_switch) queda como nice-to-have.
2026-05-09 19:06:48 +00:00
Sergio d1888e0901 feat(minga-core): cierre del α-hashing de Rust — if let, while let, let-else, or-pattern, let-chains
Cierra los 5 pendientes documentados en alpha.rs. El hash
alpha-equivalente ahora es estable bajo renombre de TODOS los binders
de Rust, no solo los del MVP (params, let, for, match arms).

Pendientes cerrados:
- if let X = expr { ... }: if_expression detecta let_condition en
  condition, recolecta binders del pattern, los propaga al
  consequence. Alternative (else) no los ve.
- while let X = expr { ... }: simetrico al if-let, propaga al body.
- let-else: ya funcionaba por construccion (alternative procesado en
  scope antes que feed_block extienda con los binders).
- or_pattern: ambos lados introducen los mismos binders (Rust
  enforcement). Emit recorre todos, collect solo el primero para no
  duplicar.
- let-chains (if let X = a && let Y = b): collect_let_condition_binders
  recursa en el arbol del condition capturando todos los let_condition
  vivan donde vivan (binary_expression u otros).

Helper nuevo: feed_let_condition para que el pattern del let_condition
pase por feed_pattern (que distingue binders de constructors). Sin
esto, los identifiers del pattern se hasheaban como variables libres
y Some(x) != Some(y) aun teniendo el mismo significado.

Tests: 6 nuevos en alpha_invariants:
- alpha_if_let_binder_rename_invariant
- alpha_if_let_else_does_not_see_binder
- alpha_while_let_binder_rename_invariant
- alpha_let_else_binder_rename_invariant
- alpha_or_pattern_binder_rename_invariant
- alpha_let_chain_binders_propagate_to_consequence
- alpha_if_let_does_not_collide_with_unrelated_program (negativo)

36 tests alpha verdes. 115 totales en minga-core.

Refactorings del tipo "rename variable" no inflan el storage del
repo. Pendiente futuro: alpha-hashing per-language (Python, TS, JS,
Go) — cada uno requiere conocimiento profundo de su gramatica.
2026-05-09 17:21:25 +00:00
Sergio 4db168253c feat(minga): multi-lenguaje en parser — Python, TypeScript, JavaScript, Go
Minga deja de ser Rust-only. Cualquiera de los cinco dialectos
(Rust + 4 nuevos) se ingresa al CAS por su AST normalizado, hashea
estructuralmente, sincroniza por DHT como cualquier nodo. La
auto-deteccion por extension hace que minga ingest archivo.{py,ts,js,go}
"simplemente funcione".

API nueva en minga_core::parse:
- Funciones por dialecto: python, typescript, javascript, go (~6 LOC
  c/u sobre el parse_with comun). Mas la rust existente.
- Enum Dialect con parse(source) y name() para logging.
- detect_by_extension(ext) -> Option<Dialect>: rs/py/pyi/ts/js/mjs/
  cjs/go (case-insensitive). None para extensiones desconocidas.

Wire en minga-cli:
- cmd_ingest deja de hardcodear parse::rust — usa
  detect_dialect(file)?.parse(...).
- initial_scan + cmd_watch cambian is_rs_file -> is_supported_source.
- CliError::UnsupportedLanguage { path, extension } nuevo, lista las
  extensiones reconocidas en el mensaje.

Notas sobre hashing:
- Hashing estructural (cas::hash_node) funciona para todos. NO es
  alpha-equivalente.
- Hashing alpha-equivalente (alpha::hash_node_alpha) sigue siendo
  Rust-only — cada lenguaje tiene reglas distintas para binder vs
  constructor; implementacion per-language queda como work futuro
  (requiere conocimiento profundo de cada gramatica).
- Sanity test structural_hash_distinguishes_languages verifica que
  "x = 1" parseado como Python != JS — las gramaticas no comparten
  kinds, hashes salen distintos. Importante para evitar colisiones.

Deps nuevas (workspace + minga-core):
- tree-sitter-python 0.23, tree-sitter-typescript 0.23 (modo
  LANGUAGE_TYPESCRIPT, no TSX), tree-sitter-javascript 0.23,
  tree-sitter-go 0.23.

Tests: 9 nuevos en parse::tests (parse basico para 5 dialectos +
detect_by_extension canonical/case-insensitive + name() +
structural_hash_distinguishes_languages). 108 verdes en minga-core,
10 en minga-cli, sin regresion.

Pendientes: alpha-hashing per-language; alpha-Rust documentados en
alpha.rs (if let, while let, let-else, let-chains, or_pattern con
bindings).
2026-05-09 16:06:31 +00:00
Sergio ad0d475a2e feat(brahman-net): capa P2P compartida — Fase 0 (extracción del swarm)
Primer paso del plan "el encuentro entre Entes no se restringe a local".
El swarm libp2p que vivía dentro de minga-p2p::network (282 LOC) sale
a una crate compartida brahman-net para que cualquier protocolo de la
familia (handshake brahman remoto en Fase 1, sync minga, futuros) reuse
una sola malla TCP+Noise+Yamux+Kad+Identify+Stream.

BrahmanNet expone:
- new() / with_keypair() para identidad efimera o persistente
- API de comandos uniforme: dial, listen, add_dht_peer,
  find_closest_peers, start_providing, find_providers
- Publica peer_id (libp2p) y control (stream::Control) — cada
  protocolo registra su StreamProtocol sin acoplarse al swarm
- Re-exporta Stream y StreamProtocol para evitar dep directa a libp2p

minga-p2p::network reduce de 282 LOC a 22: re-export del nuevo
BrahmanNet bajo el alias historico LibP2pNode (zero churn en
MingaPeer) y la const SYNC_PROTOCOL = "/minga/sync/1.0.0" especifica
del sub-protocolo de sync Minga.

Aclaracion semantica anclada por el usuario: Arje es el init (PID 1),
Brahman es el encuentro entre Entes. El nombre brahman-net refleja
que la malla pertenece al encuentro, no al runtime — Minga es un
cliente de la malla, no su dueño.

Tests: minga-p2p completo verde (58 tests, sin regresion). Behavior
identico — solo se movio codigo, ningun cambio funcional.
2026-05-09 12:29:16 +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