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
This commit is contained in:
Sergio
2026-05-09 20:32:06 +00:00
parent d60ee5eab2
commit 170d1f890a
2 changed files with 491 additions and 37 deletions
+80
View File
@@ -6,6 +6,86 @@ ratio/diff ver `git show <sha>`.
## 2026-05-09
### 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);
el form view se reusa para alta y para edit; el delete es inline.
Las mutaciones pasan por `LogEntry::Morphism` con sus ops, así el
replay restaura el estado correcto.
Cambios:
- **`MetaUi.editing: Option<(String, Uuid)>`** nuevo. Set al click
en ✎; cleared al cambiar de view o tras submit exitoso.
- **`open_edit(mod_idx, entity, id, cx)`**: setea `editing`, busca la
primera Form view del módulo cuya `entity` matchee, navega ahí. Si
el módulo no tiene Form para esa entity → toast con error
("no hay form view para entity X").
- **`select_view`** extendido: cuando carga un Form, si `editing`
matchea esa entity y el record existe en el store, pre-llena cada
input con el valor del record (vía nuevo helper
`value_to_input_text` — inverso de `parse_field_value`).
- **`commit_seed`** ramifica:
- **Edit path** (cuando `editing.is_some()` y entity matchea):
emite `LogEntry::Morphism { name: "ui.edit_record", ops:
[Set { path, value } for each field], params: { entity, id,
fields } }`. Aplica al store via `apply(&ops)`.
- **Seed path** (alta nueva): comportamiento previo.
- **`commit_delete(entity, id)`**: emite `LogEntry::Morphism {
name: "ui.delete_record", ops: [Delete { entity, id }] }` + apply.
- **Render del form**: título cambia a "Editar customer abc12345"
cuando `editing` matchea; submit label cambia a "Guardar cambios
en customer".
- **Render de la lista**: dos columnas nuevas — "id" y "acciones".
Cada fila tiene ✎ (accent color, click → open_edit) y ✕ (rojo,
click → commit_delete). Hover states.
Ramificación visible en el event log:
```
{"kind":"seed","seq":0,"entity":"customer","id":"abc...","data":{"name":"Acme"}}
{"kind":"morphism","seq":1,"morphism":"ui.edit_record","ops":[
{"op":"set","path":{"entity":"customer","id":"abc...","field":"name"},
"value":"Acme S.A."}
]}
{"kind":"morphism","seq":2,"morphism":"ui.delete_record","ops":[
{"op":"delete","entity":"customer","id":"abc..."}
]}
```
Coherente con el modelo de Nakui — todo cambio post-seed pasa por
ops dentro de Morphism. `nakui-explorer` muestra estos morphisms
con sus ops claros en su timeline.
Trade-offs documentados:
- **`schema_hash: None`** sigue para los morphism de la UI (legacy/
pre-versioning path) hasta que `Action::Morphism` cargue Manifest
schemas.
- **Delete sin confirmación**: 1 click, sin modal. Para MVP es OK
(los records son recuperables vía replay parcial), pero un futuro
iter agregaría confirmación.
- **Edit sobreescribe TODOS los campos del form**, no sólo los
cambiados — emite N ops Set, una por field. Adecuado para forms
chicos; para forms con muchos campos optimizar a delta-only.
Tests: 3 nuevos (10 totales en nakui-ui):
- `value_to_input_text_inverse_of_parse` y
`value_to_input_then_parse_round_trip` — la propiedad fundamental
del pre-llenado: text → parse devuelve el Value original.
- `event_log_replay_handles_full_crud_cycle` — E2E del log: escribe
Seed + Morphism(Set ops) + Morphism(Delete op), replay desde cero,
verifica que el store termina vacío (delete fue el último). Verifica
además que un replay parcial (sin el delete) deja los valores
editados.
Activación:
```sh
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \
NAKUI_MODULES_DIR=examples/nakui-modules \
cargo run -p nakui-ui
# Crear un customer, click ✎ en su fila, modificar campos,
# "Guardar cambios". Click ✕ en otra fila para borrar.
# Cerrar y reabrir: el state persiste con todos los cambios.
```
### 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