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>
This commit is contained in:
@@ -6,6 +6,64 @@ ratio/diff ver `git show <sha>`.
|
||||
|
||||
## 2026-05-09
|
||||
|
||||
### feat(nakui-ui): edit delta-only — sólo campos modificados al log/store
|
||||
Antes de este cambio, editar un record emitía un `FieldOp::Set` por
|
||||
**cada field del form**, incluso los no tocados. Eso bloata el log
|
||||
(replay tenía que aplicar N ops cuando 1 alcanzaba) y oscurece el
|
||||
intent en una auditoría posterior. Con delta-only, el edit emite
|
||||
sólo los Sets cuyo value nuevo difiere del actual; un edit que no
|
||||
cambia nada deja el log intacto.
|
||||
|
||||
Cambios:
|
||||
- **Nuevo helper `compute_field_delta(current, proposed)`** — toma
|
||||
el record actual del store (un `Value`, posible `Null` si el
|
||||
record no existe) y el `Map` propuesto desde el form, y devuelve
|
||||
sólo las entries que difieren. Comparación: `PartialEq` estructural
|
||||
de `serde_json::Value` (un `Null` en current = todos los proposed
|
||||
son nuevos).
|
||||
- **Nuevo enum `CommitOutcome`**:
|
||||
- `Created(Uuid)` — alta nueva.
|
||||
- `Updated { id, changed }` — edit con N campos modificados.
|
||||
- `NoChange(Uuid)` — edit sin diferencias (el toast lo refleja
|
||||
como "X sin cambios — no log entry").
|
||||
- **`commit_seed` en path EDIT**:
|
||||
- Carga current via `store.load(entity, id)` con fallback a
|
||||
`Value::Null`.
|
||||
- Calcula delta. Si vacío → return early sin tocar log ni store.
|
||||
- Si no vacío → emite `Morphism { ui.edit_record, ops: [Set...] }`
|
||||
con `params.fields` reflejando el delta (no todo el form),
|
||||
haciendo la auditoría grep-able por field cambiado.
|
||||
- **Toast del callsite**:
|
||||
- `creado X uuid` (Created)
|
||||
- `actualizado X uuid (N campo(s))` (Updated)
|
||||
- `X uuid sin cambios — no log entry` (NoChange)
|
||||
- **`editing` se limpia incluso en NoChange** — el modo edit cierra,
|
||||
el form vuelve al state limpio.
|
||||
|
||||
5 tests nuevos del helper:
|
||||
- delta vacío cuando todo coincide.
|
||||
- delta sólo con el field cambiado.
|
||||
- delta full cuando current = Null (record no existe).
|
||||
- distingue int 100 de string "100".
|
||||
- ignora fields del current que no están en proposed.
|
||||
|
||||
27 tests verdes (+5). El path SEED no cambió; el E2E del morphism
|
||||
real sigue verde.
|
||||
|
||||
Limitación conocida (consistente con pre-delta): el form no puede
|
||||
**borrar** un value vaciando el input — empty optional fields hacen
|
||||
`continue` antes de llegar al delta. Para clearear un value hay que
|
||||
declarar el field como required, o esperar a un `FieldOp::Clear`
|
||||
futuro (no necesario hoy: ningún demo lo requiere).
|
||||
|
||||
Pendientes restantes:
|
||||
- **Snapshot/compaction** del log (replay full cada startup escala
|
||||
mal con repos grandes).
|
||||
- **EntityRef validation post-submit** — validar UUID parseable al
|
||||
submit en lugar de al execute del morphism.
|
||||
- **Atajo Esc para Cancelar** del modal de delete.
|
||||
- **`FieldOp::Clear`** — para soportar borrar un value vía form.
|
||||
|
||||
### feat(nakui-ui): confirmación de delete vía banner modal antes de borrar
|
||||
Cierra el primer pending del último round: borrar un record pedía un
|
||||
solo click en `✕` y se ejecutaba inmediatamente (irreversible —
|
||||
|
||||
Reference in New Issue
Block a user