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.
This commit is contained in:
Sergio
2026-05-09 20:41:37 +00:00
parent 170d1f890a
commit 932e7464d7
5 changed files with 601 additions and 8 deletions
+82
View File
@@ -6,6 +6,88 @@ ratio/diff ver `git show <sha>`.
## 2026-05-09
### feat(nakui-ui): Action::Morphism wired al pipeline real (compute → log → apply)
Cierra el último 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 de Nakui:
compute (con dry-run + KCL post-checks) → log append → store apply.
Schema `nakui-ui-schema` extendido:
- **`Module.nakui_module_dir: Option<String>`** nuevo. Path
(relativo al directorio del `module.json` o absoluto) a un módulo
nakui-core. Sin esto, las Action::Morphism del módulo quedan
no-op con toast informativo. Las Action::SeedEntity siguen
funcionando sin manifest (alta administrativa).
- **`Action::Morphism`** ganó dos campos opcionales:
- `inputs: BTreeMap<String, String>` — mapeo `role → field_name`.
Por cada input declarado en el `MorphismSpec.inputs`, indica
qué field del form contiene el UUID del record. El runtime
parsea como `Uuid` y lo pasa al `execute_and_log`.
- `params: Vec<String>` — lista de fields cuyos values van al
`params` JSON. Si vacío, 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`
por cada módulo UI que declare la entry. Errores de carga van al
banner; el módulo sigue cargado para SeedEntity, sólo Morphism
queda no-op.
- **`commit_morphism(mod_idx, name, inputs_map, params_fields)`** nuevo.
Resuelve inputs (parsea cada field como Uuid), arma params (Value
object con tipos inferidos via `infer_param_value` — int/float/
bool/string), llama `execute_and_log_with_recovery`. Toast con
cantidad de ops aplicadas o el error tipado.
- **`infer_param_value`** nuevo helper: heurística simple para
pasar values del form al morphism con tipo inferido (i64 → f64 →
bool → string).
Tests: 2 nuevos:
- `infer_param_value_int_then_float_then_bool_then_string`
cobertura de la heurística.
- **E2E `morphism_pipeline_executes_real_sales_vender`** —
carga el módulo real `crates/modules/nakui/modules/sales`,
arma store + log, ejecuta el morphism `vender` con inputs
Stock+Caja y params (cantidad=5, precio_unitario=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacío).
- stock.cantidad bajó 100 → 95.
- caja.saldo subió 1_000_000 → 1_001_000.
12 tests verdes en nakui-ui (+1 vs commit anterior). Schema
extension no rompió nada (6 unit + 5 integration siguen verdes).
Demo nuevo: **`examples/nakui-modules/sales_engine/module.json`**
- Apunta a `crates/modules/nakui/modules/sales` vía `nakui_module_dir`.
- 6 vistas: list + form para cada Stock, Caja, Venta + form
"Vender" con `Action::Morphism { name: "vender", inputs: {stock,
caja}, params: [venta_id, cantidad, precio_unitario, timestamp] }`.
- El user crea Stocks + Cajas con seed_entity, copia los UUIDs
cortos a los inputs de "Vender", y ejecuta el morphism real:
stock baja, caja sube, Venta se persiste, todo loggeado.
- Validaciones KCL fallan limpio (toast con error) si el morphism
rebota — p. ej. cantidad > stock disponible.
Activación full:
```sh
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \
NAKUI_MODULES_DIR=examples/nakui-modules \
cargo run -p nakui-ui
# Sidebar gana "Ventas (con morphism)" — los 6 menús aparecen y
# el form "Vender" dispara el pipeline nakui-core completo.
```
Trade-offs documentados:
- **Inputs UUID a mano**: el form pide que el user copie el UUID de
un Stock/Caja existente. Para UX seria habría que agregar
`FieldKind::EntityRef { entity }` que renderiza un dropdown — no
hecho por scope, queda como nice-to-have.
- **Inferencia de tipo en params**: `infer_param_value` adivina
por shape del string. Para casos sutiles (ej. "true" como string
literal vs bool), el módulo nakui-core puede explicitar tipos
via `kind` en el FieldSpec — el form lo respeta para validación
pre-submit; la inferencia final sigue siendo heurística.
### feat(nakui-ui): edit + delete de records (ciclo CRUD completo)
Cierra "no hay UI para editar/borrar records existentes" del commit
anterior. Cada fila de la lista gana dos botones (✎ edit, ✕ delete);