tawasuyu

tawasuyu/brahman

Rust 492TOML 194Markdown 53JSON 176939 Definiciones

⎇ bb21c28eb158d264c7b03f48fba731a75c1648f0 docs/changelog/nahual.md Markdown · 1054 líneas · 49.2 KB Historia crudo

feat(mirada): mirada-greeter — greeter de login del escritorio carmen bb21c28eb · sergio ·

1# Changelog — nahual
2
3Motor GPUI: libs + widgets. Renombrado de `yahweh` el 2026-05-19.
4
5### feat(nahual-widget-text-input): modo enmascarado para contraseñas
6
7`TextInput::with_mask()` dibuja el contenido como puntos (`•`, uno por
8carácter Unicode) en vez del texto real; `text()` sigue devolviendo el
9contenido crudo. Lo usa el campo de contraseña de `mirada-greeter`.
10Lógica de enmascarado en la función pura `display_text`, con tests.
11
12### feat(nahual-theme): exportación del tema a GTK (módulo toolkit)
13
14Módulo nuevo `nahual-theme/src/toolkit.rs`: traduce el `Theme` activo a
15`~/.config/gtk-3.0/gtk.css` y `gtk-4.0/gtk.css` con overrides
16`@define-color`. Las apps GTK adoptan el acento exacto del tema + un
17neutro claro/oscuro coherente. Los fondos en gradiente de nahual no se
18pueden reproducir en ventanas GTK sólidas, así que el neutro se sintetiza
19con un ramp de luminancia tintado por el matiz del borde del tema.
20
21- `gtk4_css` / `gtk3_css` — generadores puros (nombres de color de
22  libadwaita y de Adwaita 3 respectivamente).
23- `export_toolkit_configs` + `export_toolkit_configs_to(base)` —
24  escritura; el segundo con directorio base explícito para tests.
25- **Guarda de no-pisar**: si un `gtk.css` ya existe sin la marca de
26  nahual, es del usuario y se respeta (`ExportReport.skipped`).
27- `Theme::set` y `Theme::install_default` exportan best-effort: cambiar
28  de tema en cualquier app GPUI actualiza GTK al instante.
29- `config_path` refactorizado sobre un nuevo `config_home()`.
30- Ejemplo `dump-toolkit-css` para inspeccionar el CSS generado.
31- 8 tests nuevos en `toolkit`.
32
33### feat(yahweh-launcher): F3 — extracción del shell standard de explorers
34Iter 19. Patrón con 4 consumers idénticos (nakui-explorer,
35nouser-explorer, minga-explorer, brahman-broker-explorer) declaraban
36~20 líneas de boot:
37`Application::new + Theme::install_default + cx.open_window
38+ WindowOptions{ window_bounds, titlebar } + cx.activate(true)`.
39Todo idéntico salvo título, tamaño, y root entity factory.
40
41Crate nuevo `crates/modules/ui_engine/libs/launcher/`
42(`yahweh-launcher`):
43- **Deps**: `gpui`, `yahweh-theme`. Sin más.
44- **`pub fn launch_app(title, size, root_factory)`**: 1-line boot.
45- **`pub fn launch_app_with(config, root_factory)`**: variante con
46  `AppLaunchConfig` armado afuera, para casos donde título/tamaño se
47  computan condicionalmente (env var, etc).
48- **`AppLaunchConfig::new(title, size)`**: builder normalizador.
49- 2 tests `#[test]` cubren la normalización de config (no se testea
50  `launch_app` per se porque bloquea el thread main hasta que la
51  ventana se cierra).
52
53Migración 4 consumers:
54- `main()` pasa de 20 líneas a 1: `launch_app("Title", (W, H), Explorer::new)`.
55- Imports de gpui se podan: ya no necesitan `App, Application,
56  Bounds, WindowBounds, WindowOptions` ni `SharedString` ni `prelude::*`.
57- Cada consumer agrega dep `yahweh-launcher` (path local).
58
59Naming: el slot natural era `yahweh-shell`, pero ya existe un crate
60`yahweh-shell` en `crates/apps/` (bootstrap heavyweight con file
61explorer + DB explorer + viewers). El helper es liviano y específico
62al patrón de launch, así que `yahweh-launcher` evita confusión.
63
64Total ahorro: ~75 líneas hardcoded de boilerplate UI a 4 líneas en
65total. Cambios de boot (window opts, theme, etc) ahora viven en un
66solo lugar.
67
68Tests stack: 2 nuevos en launcher; suites de los 4 consumers
69intactas. Todo verde.
70
71### feat(yahweh-widget-app-header): promover el header standard de explorers
72Iter 16. Patrón con 4 consumers idénticos: `nakui-explorer`,
73`nouser-explorer`, `minga-explorer`, `brahman-broker-explorer`
74todos declaraban un header `flex_row + flex_grow(label) +
75theme_switcher + bg(panel) + border-bottom + text_size(14) +
76padding(16/12)`. Ahora es 1 línea.
77
78Crate nuevo `crates/modules/ui_engine/widgets/app-header/`
79(`yahweh-widget-app-header`):
80- **Deps**: `gpui`, `yahweh-theme`, `yahweh-widget-theme-switcher`.
81  El switcher se incluye automáticamente.
82- **`pub fn app_header(cx: &mut App, label: impl Into<SharedString>)
83  -> impl IntoElement`**: caso simple con texto plano.
84- **`pub fn app_header_with(cx, label_child: impl IntoElement)`**:
85  variante para cuando el lado izquierdo no es texto plano (icon
86  + text, múltiples spans, etc.).
87- 3 tests `#[gpui::test]`: smoke con string label, con custom
88  child IntoElement, type-check de label con literal/owned/format!.
89
90Migración de los 4 consumers:
91- Cada uno reemplaza un bloque `let header = div().flex().flex_row()...
92  .child(theme_switcher(cx))` (~13 líneas) por
93  `let header = app_header(cx, header_text)` (~1 línea).
94- Cada uno borra dep `yahweh-widget-theme-switcher` (ya no la
95  necesita directo — el `app_header` la incluye internamente).
96- Cada uno reemplaza `use yahweh_widget_theme_switcher::theme_switcher`
97  por `use yahweh_widget_app_header::app_header`.
98
99Total ahorro: ~50 líneas de código UI hardcoded en consumers.
100Cambios visuales en el header (padding, border, text_size) ahora
101viven en un solo lugar.
102
103Tests stack: 3 nuevos en app-header; suites de los 4 consumers
104intactas. Todo verde.
105
106Decisión: el sidebar header del `MetaApp` (que también incluye
107theme_switcher) NO se migra — es un header de sidebar, no de app
108top, y tiene styling distinto (px(12/10/13), sin bg/border-bottom
109porque ya está dentro del panel). Diferente patrón → diferente
110widget si emerge segundo consumer.
111
112### feat(yahweh-widget-stat-card): promover el patrón stat card como widget
113Iter 15. El patrón "tarjeta de dashboard con border-l accent +
114label + valor grande + descripción + listing opcional" tenía 2
115consumers (`minga-explorer` y `brahman-broker-explorer`); ahora vale
116extraer al stack yahweh para reusabilidad y mantenimiento single-place.
117
118Crate nuevo `crates/modules/ui_engine/widgets/stat-card/`
119(`yahweh-widget-stat-card`):
120- **Deps**: `gpui` + `yahweh-widget-card` (compone `card_themed`).
121  Sin theme directo — el caller pasa `text` y `text_dim` ya
122  resueltos del theme.
123- **`pub fn stat_card(cx, label, value, description, accent,
124  text, text_dim, recent_items)`**:
125  - `cx: &App` (acepta `&Context<T>` por deref auto-coerce).
126  - `value: impl Into<SharedString>` — sirve para counts (`"3"`),
127    status text (`"UP"`), o cualquier label corto.
128  - `recent_items: &[String]` — si no vacío, agrega sub-header
129    `"recent (N):"` + una linea por item.
130- 3 tests `#[gpui::test]` con TestAppContext: smoke con/sin
131  recent_items, type-check de `value` con literal/format/owned.
132- Dev-deps: gpui con `test-support` + yahweh-theme para construir
133  el cx con un theme global.
134
135Cambios consumer:
136- **`minga-explorer`**: sustituye su `fn stat_card` local
137  (~60 líneas) por `use yahweh_widget_stat_card::stat_card`.
138  Borra dep `yahweh-widget-card` (ya no se usa directo). Adapta
139  los 3 callsites para pasar `value.to_string()` (el widget
140  acepta `Into<SharedString>`).
141- **`brahman-broker-explorer`**: refactoriza su `fn state_card`
142  para que sea un wrap de `stat_card` con la traducción
143  `ProbeState → (accent, value, description)`. La función queda
144  como helper local porque la mapping del enum es app-specific,
145  pero el rendering pasa por el widget compartido. Borra dep
146  `yahweh-widget-card`.
147
148Tests stack: nuevos 3 del widget. Suites de los 2 consumers
149intactas (4 minga-explorer, 2 broker-explorer). Stack total ~120
150verdes (varía por compilation cache).
151
152Beneficio operativo:
153- Cualquier app nueva que necesite cards de dashboard usa
154  `stat_card(...)` directo; no re-implementa el pattern.
155- Cambios visuales (text sizes, padding, sub-header format)
156  ahora viven en un solo lugar.
157- `value: impl Into<SharedString>` es más expressive que el
158  `usize` rígido del original local.
159
160Pequeña simplificación documentada: el sub-header del listing
161pasa de `"recent (N de TOTAL):"` a `"recent (N):"`. El "TOTAL"
162ya no se calcula porque el widget no lo conoce — el caller que
163quiera mostrarlo lo formatea en el label/value (ej. label `"Nodos
164AST (5 de 247)"`). Acceptable trade-off por la reusabilidad
165genérica.
166
167### feat(yahweh-theme): persistencia de la preferencia de theme entre runs
168Iter 13. El theme switcher ya cambiaba el chrome en runtime, pero
169al cerrar y reabrir la app el theme volvía a Nebula default. Ahora
170el name del theme se persiste en `$XDG_CONFIG_HOME/yahweh/theme`
171(default `~/.config/yahweh/theme`) y se restaura al boot.
172
173Cambios en `yahweh-theme`:
174- **`pub fn config_path() -> Option<PathBuf>`**: resuelve el path
175  XDG. Devuelve `None` si ni `XDG_CONFIG_HOME` ni `HOME` están
176  set (sandbox/CI).
177- **`pub fn load_persisted() -> Option<Theme>`**: lee el archivo,
178  trim, busca el theme por name vía `Theme::by_name`. `None` si
179  el file no existe, lectura falla, o el name no matchea ningún
180  preset (e.g. preset renombrado entre versiones).
181- **`pub fn persist(theme: &Theme) -> io::Result<()>`**: escribe
182  el name al config file. Crea el dir parent si no existe.
183- **`pub fn load_from_path` y `pub fn persist_to_path`**: variantes
184  con path explícito — útiles para tests con tempfile y para apps
185  que quieren un path custom (multi-user, staging, etc.).
186- **`Theme::install_default(cx)` cambia**: antes hardcoded
187  `nebula()`. Ahora intenta `load_persisted()`, fallback a Nebula.
188- **`Theme::set(cx, theme)` cambia**: antes sólo `cx.set_global`.
189  Ahora también `persist(&theme)` antes (best-effort: ignora io
190  errors). El `theme_switcher` widget ya consume `Theme::set`, así
191  que sin cambios en su código el switching ahora persiste.
192
1935 tests nuevos (`persistence_tests`):
194- `persist_then_load_round_trip` — escribir + leer Aurora.
195- `load_from_missing_file_returns_none` — no rebota.
196- `load_from_unknown_name_returns_none` — name desconocido →
197  `None` (degrada a default cuando se usa).
198- `persist_creates_parent_dir_if_missing` — crea
199  `~/.config/yahweh/` si no existe.
200- `config_path_uses_xdg_config_home_when_set` — respeta el env.
201
202Tests stack: ~5 nuevos en yahweh-theme. Todos los downstream
203(nakui-ui, *-explorer) compilan sin tocar nada — la API pública
204de `Theme::install_default` y `Theme::set` no cambió shape.
205Smoke run del binario verificado: bootstrap OK, panic esperado
206sin display.
207
208Beneficio operativo:
209- Usuario abre `nakui-ui`, cicla a Aurora con el switcher, cierra
210  app. Próxima apertura: Aurora cargado del disco. Todas las
211  apps yahweh-themed (4 del repo) comparten la misma preferencia.
212- Failure mode benigno: sin home dir o sin permisos de write,
213  el theme cambia in-memory pero no se persiste — el switcher
214  sigue usable, sólo no sobrevive al close.
215- Path canónico documentado: usuarios que quieran preset el
216  theme antes de abrir la app pueden hacer
217  `echo Aurora > ~/.config/yahweh/theme`.
218
219### feat(yahweh): caret blinking + slots ornament en theme + MetaApp full themed
220Iters 8-9 combinadas. Tres mejoras pequeñas que cierran la
221integración del theme:
222
223**1. Caret blinking en text-input** (`yahweh-widget-text-input`):
224- Nuevo field `caret_visible: bool` que toggea cada 500ms.
225- Nuevo field `_blink_task: Task<()>` mantiene el loop de blink
226  vivo y lo cancela al drop del widget.
227- En `new()`, `cx.spawn(...)` arranca el loop: `timer.timer(500ms)`
228  + `this.update(...)` que toggea + `cx.notify()`. Si el update
229  falla (entity drop), break.
230- En `render()`, caret `|` se dibuja sólo si
231  `is_focused && self.caret_visible`. Familiar feel del SO.
232
233**2. Slots ornament en yahweh-theme** (5 nuevos):
234- `bg_input() -> Hsla` — bg sutil para fields editables.
235- `bg_button() -> Hsla` + `bg_button_hover() -> Hsla` — controls
236  clickable secundarios.
237- `accent_destructive() -> Hsla` — rojo para acciones peligrosas.
238- `bg_destructive_hover() -> Hsla` — bg de hover sobre destructive.
239- Implementados como **methods** del `Theme` (no fields del
240  struct), derivados via `ornament_slots(self.is_dark)`. Esto
241  evita modificar los 6 presets — el slot vive donde uno lo
242  invoca.
243
244**3. MetaApp ornament cleanup** (`yahweh-widget-meta-form`):
245- 11 colores hardcoded `gpui::rgb(0x...)` migrados a slots del
246  theme:
247  - Sidebar menu items (selected/hover) → `bg_row_active` /
248    `bg_row_hover`.
249  - List row separator + button bgs → `bg_row_active` /
250    `bg_button()` / `bg_button_hover()`.
251  - Icon ✕ delete + hover → `accent_destructive()` /
252    `bg_destructive_hover()`.
253  - EntityRef selector hover/selected → `bg_row_active` /
254    `bg_row_hover`.
255  - EntityRef selector border → `theme.border` (slot existente).
256  - Form fallback input bg + submit button → `bg_input()` /
257    `bg_button()` / `bg_button_hover()`.
258  - Confirm modal hint subtitle + hovers de Cancel/Confirm →
259    `theme.fg_muted` / `bg_button_hover()` / `bg_destructive_hover()`.
260- Pattern: `let X = theme.slot()` antes de las closures + `move |d|
261  d.bg(X)` en hover/when para que el cierre tome ownership.
262
263Antes de este commit MetaApp tenía la **paleta principal** themed
264(iter 5) pero el ornament secundario (hovers, separators, botones
265inline) seguía hardcoded. Ahora el theme switcher cambia
266**absolutamente todo** el chrome del MetaApp en runtime.
267
268Tests: 117 verdes (sin cambios numéricos, pero downstream sigue
269compilando). Smoke run de nakui-ui: bootstrap completo OK.
270
271Limitación restante: `nouser-explorer` todavía no migra al stack
272yahweh themed — patrón idéntico a `nakui-explorer` aplicado pero
273más nuevo. Próxima iter.
274
275### feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus
276Iter 7 (mini-iter — el text-input ya estaba themed, faltaba sólo
277el polish de focus visibility). Antes el border era siempre
278`accent_strong` y el caret `|` siempre estaba presente — imposible
279distinguir cuál input está activo en un form con varios fields.
280
281Cambios en `yahweh-widget-text-input`:
282- **Border focus-aware**: cuando el input está focused, border =
283  `theme.accent_strong` (color vivo). Cuando no, border =
284  `theme.border` (color tenue del chrome). Se obtiene via
285  `self.focus_handle.is_focused(window)`.
286- **Caret `|` sólo on focus**: cuando el input no tiene focus, se
287  muestra el texto plano sin caret. Reduce el "ruido visual" en
288  forms con muchos fields.
289- `render` ahora usa el `Window` arg (antes `_w`) para chequear
290  focus.
291
292Sin cambios en API pública — todo es interno al `render`. El
293binario no requiere migración.
294
295Tests: sin cambios (los tests del crate son struct constructors,
296no rendering). Tests downstream del widget (`yahweh-widget-meta-form`,
297`nakui-ui`) siguen verdes — el cambio es backward compatible.
298
299Beneficio operativo:
300- Forms con 5+ fields ahora son navegables: el usuario ve cuál
301  input recibe sus teclas via el border highlighted.
302- Cambio de theme afecta también a inputs (ya estaban themed; ahora
303  además respetan el `accent_strong` específico del preset
304  cuando focused, vs el `border` cuando no).
305
306Limitación pendiente: el caret `|` literal no parpadea (no hay
307animation timer). Cuando emerja la necesidad, agregar via
308`cx.spawn` con un loop de toggle. Por ahora el caret estático on
309focus es suficiente signal.
310
311### feat(yahweh-widget-theme-switcher): control para ciclar themes en runtime
312Iter 6. Cierra el ciclo del theme: ya teníamos paleta themed +
313widgets que la consumen, faltaba el control UI para rotar entre
314presets en vivo. Ahora hay un botón yahweh que muestra el theme
315actual y al click avanza al siguiente. `nakui-ui` y `nakui-explorer`
316lo incrustan en sus headers — un click cambia toda la paleta.
317
318Crate nuevo: `crates/modules/ui_engine/widgets/theme-switcher/`
319(`yahweh-widget-theme-switcher`):
320- **Deps**: `gpui` + `yahweh-theme`. Sin nada más.
321- **`pub fn theme_switcher(cx: &mut App) -> impl IntoElement`**:
322  botón clickable con `id`, padding consistente (`px(8/4)`),
323  bg = `theme.bg_panel_alt`, hover = `bg_row_hover`. Muestra
324  `"Tema: <name> ▸"` y al click hace
325  `Theme::set(cx, Theme::next_after(current.name))`.
326- 2 tests `#[gpui::test]`:
327  - `switcher_constructs_with_theme_installed` — smoke: el
328    constructor lee el global y devuelve un IntoElement sin panic.
329  - `theme_set_changes_global` — verifica que `Theme::set` reemplaza
330    el global y que el siguiente `Theme::global` devuelve el nuevo.
331- Dev-dep `gpui` con `test-support` para habilitar TestAppContext.
332
333Migración de consumers:
334- **`nakui-explorer`**: nueva dep `yahweh-widget-theme-switcher`.
335  El header pasa de `div().px().py()...child(text)` a
336  `div().flex_row().child(div().flex_grow().child(text)).child(theme_switcher(cx))`.
337  El switcher queda alineado a la derecha vía `flex_grow` del label.
338- **`yahweh-widget-meta-form`**: nueva dep. El sidebar header
339  ("Nakui" + 12px padding) gana el switcher con el mismo patrón
340  flex_row + flex_grow.
341
342Tests stack: 115 → **117** (+2 del switcher). Cada crate compila
343individualmente.
344
345Beneficio operativo:
346- Click en el switcher cambia toda la paleta en vivo: bg del app,
347  panels, banners (los que usan `_themed`), confirm modal, todo.
348- 6 presets disponibles via `Theme::all()` (Nebula, Aurora,
349  Sunset, Flat Dark, Solarized Light, High Contrast). El switcher
350  cicla circularmente.
351- Apps adoptantes del `Theme` heredan el switch sin esfuerzo.
352
353Decisión técnica: el handler usa `Theme::set(cx, ...)` que
354invalida el global. GPUI marca todos los views como dirty y
355re-renderea — los widgets que leen `Theme::global` en su `render`
356ven el nuevo automáticamente. No requiere `cx.observe_global`
357explícito en cada widget consumidor.
358
359Limitación: TextInput entities ya creadas no se actualizan visualmente
360si el theme cambia los colors del input bg/border (esos colors
361están hardcoded en `yahweh-widget-text-input`). Migrar text_input
362al theme es una iter futura — bajo scope porque actualmente vive
363suficientemente bien con sus defaults dark.
364
365### feat(yahweh-widget-meta-form): paleta del chrome migrada a `Theme::global(cx)`
366Iter 5 de integración. El `MetaApp::render` tenía 7 vars locales
367con colors hardcoded (`bg/panel/border/text/text_dim/accent/
368accent_active`) que se pasaban a las funciones internas
369(`render_sidebar`/`render_main`/`render_list`/`render_form`/
370`render_entity_ref_selector`). Ahora salen del `Theme::global(cx)`
371que el binario shell instala al boot. El `confirm_delete_banner`
372también usa `themed_colors(Banner::Warning)` / `themed_colors(Banner::Error)`
373para sus colors base.
374
375Cambios en `MetaApp::render`:
376- 7 `let X = gpui::rgb(0x...)` → derive del theme:
377  - `bg` ← `theme.bg_app` (Background, soporta gradientes).
378  - `panel` ← `theme.bg_panel`.
379  - `border` ← `theme.border` (Hsla).
380  - `text` ← `theme.fg_text`.
381  - `text_dim` ← `theme.fg_muted`.
382  - `accent` ← `theme.accent`.
383  - `accent_active` ← `theme.accent_strong`.
384- `toast_div` y `error_banner`: `banner(...)` → `banner_themed(cx, ...)`.
385
386Cambios de firma (internas, no API público):
387- `render_sidebar` / `render_main` / `render_list` /
388  `render_entity_ref_selector` / `render_form` cambian Rgba →
389  Hsla en sus parámetros de color (Background donde aplica para
390  `panel`). Los métodos `bg/text_color/border_color` de gpui::Div
391  aceptan ambos via `Into`, así que el uso interno no cambia.
392
393Cambios en `render_confirm_delete_banner`:
394- 6 colors hardcoded amber/red/gray → `themed_colors(Warning)` para
395  banner base, `themed_colors(Error)` para botón Confirm,
396  `theme.bg_panel_alt + fg_text` para botón Cancel.
397- Cambiar de Theme ahora cambia toda la paleta del modal.
398
399Lo que **NO** migra esta iter (queda como ornament hardcoded; iter
400futura si emerge la necesidad):
401- Row hovers misceláneos en `render_list` (px 0x232a36 / 0x1f2630
402  para selected/hover de filas).
403- Borders sutiles entre filas (px 0x232a36).
404- Bg de inputs custom (px 0x171a20).
405- Bg de botones en `render_entity_ref_selector` (px 0x2c3540).
406- Color rojo del icon `✕` de delete (px 0xd07070) y su hover
407  (px 0x4a2020).
408
409Estos son detalles ornamentales que un theme switcher real
410querría integrar; los aislo para una pasada futura cuando esté
411claro qué slots semánticos del theme conviene agregar (ej.
412`bg_row_selected` distinto de `bg_row_hover`, `accent_destructive`,
413etc.).
414
415`nakui-ui` shell ya instalaba `Theme::install_default(cx)` desde la
416iter pasada — sigue siendo el contract entre el shell y el widget.
417Smoke test del binario verificado: bootstrap completo OK, panic
418esperado en open_window sin display.
419
420Tests stack: 115 verdes (sin cambio — los tests del widget no
421acceden al render).
422
423Beneficio operativo:
424- El theme switcher (cuando llegue) cambia toda la paleta principal
425  de `MetaApp` con 1 sola llamada `Theme::set(cx, ...)`.
426- `MetaApp` y `nakui-explorer` comparten el mismo theme global en
427  un mismo proceso (si llegan a vivir juntos).
428- Los `confirm_delete_banner` y los toasts del MetaApp respetan
429  is_dark: el contrast ajusta automatic.
430
431### feat(yahweh): theme integration en `banner` + `card` + `nakui-explorer` consume themed
432Iter 4 de la integración. Los widgets `banner` y `card` ahora
433ofrecen variants `_themed(cx, ...)` que leen `Theme::global(cx)`.
434Las versiones sin theme se preservan para apps sin theme global.
435`nakui-explorer` migra a versiones themed + `Theme::install_default`
436al boot — el chrome hardcoded del explorer (5 variables `let bg =
437rgb(...)`) sale del theme.
438
439Cambios en `yahweh-widget-card`:
440- **Nueva dep**: `yahweh-theme`.
441- **`pub fn card_themed(cx: &App) -> Div`**: devuelve [`card`]
442  pre-aplicado con `bg(theme.bg_panel)`. El caller sigue componiendo
443  con borders, accents, children.
444
445Cambios en `yahweh-widget-banner`:
446- **Nueva dep**: `yahweh-theme`.
447- **`pub fn banner_themed(cx: &App, kind, message) -> Div`**:
448  deriva `(bg, fg)` según `kind` + `theme.is_dark`:
449  - `Info`: `theme.bg_panel_alt` + `theme.accent`.
450  - `Success` / `Warning` / `Error`: hue fijo (verde/amber/rojo)
451    + lightness flippeada según `is_dark` (dark = bg low, fg high;
452    light = invertido).
453- **`pub fn themed_colors(kind, theme) -> (Background, Hsla)`**:
454  helper público para callers que quieren computar el par sin
455  construir el div.
456- 3 tests nuevos del derivation: dark/light lightness contrast,
457  kinds distinguidos por hue.
458
459Migración de `nakui-explorer`:
460- Nueva dep `yahweh-theme`.
461- `main()` llama `Theme::install_default(cx)` antes de open_window
462  (el theme default es Nebula).
463- `render`:
464  - 5 `let bg/text/text_dim/card_bg/border = rgb(...)` colors
465    locales → `theme.bg_app/fg_text/fg_muted/bg_panel/border`.
466  - `card().bg(card_bg)` → `card_themed(cx)` (borra los locales).
467  - `banner(Banner::Error, ...)` → `banner_themed(cx, Banner::Error, ...)`.
468  - Los accents `accent_seed` / `accent_morphism` se preservan
469    locales: son **señales semánticas del log** (azul=seed,
470    verde=morphism), no chrome del app.
471
472Distribución de tests: 112 → **115** (+3 del banner derivation).
473Workspace stack pasó por la migración sin errores.
474
475Beneficio operativo:
476- Cambiar de Theme (Nebula → Aurora → Solarized Light, etc.) ahora
477  refleja en `nakui-explorer` automáticamente. Antes había que
478  buscar y reemplazar los hex codes uno a uno.
479- Apps que adopten el patrón `_themed` heredan el switcher de
480  theme cuando emerja.
481
482Decisiones:
483- **Hue fijo por kind**: Success siempre verde, Error siempre rojo,
484  etc. La lightness se ajusta al theme; el hue se mantiene como
485  invariante semántico cross-theme.
486- **API dual**: `banner` (defaults) + `banner_themed` (theme).
487  Apps sin theme global pueden seguir con la versión simple.
488- **Acentos semánticos del explorer (seed/morphism) NO migran**:
489  pertenecen al dominio del log, no al chrome.
490
491Próximas integraciones pendientes:
492- `MetaApp` (en `yahweh-widget-meta-form`) tiene su propia paleta
493  hardcoded de 6 colors que podría migrarse al theme. Scope mayor
494  que esta iter; queda como candidato.
495- Theme switcher widget (botón/menú en chrome para ciclar themes).
496  Cuando emerja la necesidad real.
497
498### feat(yahweh-widget-card): container card-shape compartido para timeline entries
499Iteración 3 de la integración nakui ↔ yahweh. El "card visual"
500pattern (padding consistente + rounded + flex_col + gap) que vivía
501duplicado en cada timeline entry de `nakui-explorer` ahora es un
502widget yahweh reusable. Sin acoplamiento a colores: el caller
503decide bg/border/accent.
504
505Crate nuevo: `crates/modules/ui_engine/widgets/card/`
506(`yahweh-widget-card`):
507- **Dep**: solo `gpui`. App-agnostic.
508- **`pub fn card() -> Div`**: container con `flex_col` + `px(12)`
509  + `py(8)` + `mb(4)` + `rounded(4)` + `gap(2)`. Sin colores
510  aplicados.
511- El return es `Div` GPUI — el caller compone con `.bg(...)`,
512  `.border_l_4()`, `.border_color(...)`, `.child(...)`, hover,
513  on_click, etc., según necesite.
514- 1 test smoke (constructor no panicea).
515
516Migración de `nakui-explorer`:
517- Nueva dep `yahweh-widget-card`.
518- Los 2 patterns de timeline entry (Seed y Morphism) pasan de:
519  ```rust
520  div().flex().flex_col().px(12).py(8).mb(4).bg(card_bg)
521       .rounded(4).border_l_4().border_color(accent).gap(2)...
522  ```
523  a:
524  ```rust
525  card().bg(card_bg).border_l_4().border_color(accent)...
526  ```
527- Reducción ~7 calls → ~3 por entry; legibilidad mejor (la
528  intención "card with accent" emerge del nombre `card()`).
529
530Tests stack: 111 → **112 verdes** (+1 del crate card). Cada crate
531afectado compila y testea individualmente.
532
533Beneficio operativo:
534- Si `MetaApp` o cualquier futura app necesita un container
535  card-shape (ej. info card, expanded list row), `card()` está
536  ya disponible.
537- Cambiar el padding/rounded/gap canónico = un cambio en un solo
538  lugar.
539- El widget no impone colores → no fuerza una paleta y permite
540  themes diversos por app/contexto.
541
542### feat(yahweh-widget-banner): widget compartido para toasts/errores cross-app
543Patrón visual común a `yahweh-widget-meta-form` (toast success +
544error_banner) y `nakui-explorer` (error_banner): un `div` con bg
545+ text colored según severidad. Antes vivía duplicado con colores
546hardcoded en cada consumer; ahora hay un widget yahweh con presets
547consistentes.
548
549Crate nuevo: `crates/modules/ui_engine/widgets/banner/`
550(`yahweh-widget-banner`):
551- **Dep**: solo `gpui` (sin nakui, sin runtime). Reusable por
552  cualquier app GPUI que necesite tiras de status.
553- **`pub enum Banner`** con 4 variants:
554  - `Info` (azul tenue, mensajes neutros).
555  - `Success` (verde, confirmaciones).
556  - `Warning` (amber, llamadas de atención).
557  - `Error` (rojo, errores fatales).
558- **Métodos `Banner::bg()` y `Banner::fg()`**: paleta hardcoded por
559  variant (sin tema dinámico todavía — cuando emerja, se
560  inyecta vía `yahweh-theme`).
561- **`pub fn banner(kind, message) -> Div`**: constructor que
562  devuelve el div ya con padding/text_size defaults; el caller
563  puede agregar children, override pads/sizes, attach handlers.
564- 2 tests sanity: ningún kind comparte bg, ningún kind comparte fg.
565
566Migración de consumers:
567- **`yahweh-widget-meta-form`**: nueva dep `yahweh-widget-banner`.
568  El `toast_div` (Success) y `error_banner` (Error) en
569  `MetaApp::render` pasan de 2x6 líneas hardcoded a una llamada
570  a `banner(...)` cada uno (~12 líneas → 2).
571- **`nakui-explorer`**: nueva dep. El error banner local pasa a
572  `banner(Banner::Error, e).px(16).py(8).text_size(12)` —
573  preserva el padding/size custom del header del explorer via
574  override builder.
575
576Tests stack: 109 → **111 verdes** (+2 del crate banner).
577
578Beneficio operativo:
579- Si emerge un tercer consumer, importa la dep + 1 llamada.
580- Cambiar la paleta de un kind = un cambio en un solo lugar
581  (ej. ajustar tono del Error o el contraste del Warning).
582- Composición preservada: el `banner()` devuelve un `Div` directo,
583  el caller modifica con builder calls (`.child()`, `.px()`,
584  `.on_click()`, etc.) sin rewrap.
585
586Próximo candidato natural: el `confirm_delete_banner` de MetaApp
587es Banner::Warning + 2 botones embedded. Cuando emerja un segundo
588consumer de modal-style banners, extraer un widget compositivo
589arriba del `Banner` base.
590
591### feat(yahweh): `MockBackend` público + tests E2E del widget con `gpui::TestAppContext`
592Cierra el ciclo de testabilidad del widget metainterfaz. Hasta
593ahora los tests del trait `MetaBackend` vivían como impl privada
594en `backend.rs`; el widget no tenía forma de testear handlers
595reales sin levantar `NakuiBackend` (que depende de event log +
596Rhai + nakui-core). Ahora el mock es público y los tests del widget
597lo consumen con `TestAppContext`.
598
599Cambios en `yahweh-meta-runtime`:
600- **Nuevo módulo `pub mod testing`** con
601  `pub struct MockBackend`. Exporta:
602  - `MockBackend::new()` — vacío.
603  - `MockBackend::with_records(iter)` — pre-poblado con
604    `(entity, uuid, value)` tuples.
605  - `MockBackend::with_morphism(name, |inputs, params| -> Result<usize>)` —
606    builder para registrar handlers callable de morphism (sin
607    handler, `morphism()` rebota con error claro).
608  - Métodos de inspección `total_records()` / `records_for(entity)`
609    (último devuelve `Vec<(Uuid, &Value)>` sin clones).
610  - `impl MetaBackend` completo: seed/load/list/update/delete con
611    semantica documentada.
612- **Tests del trait en `backend.rs` simplificados**: el `MemBackend`
613  duplicado se borra; los tests pasan a usar `MockBackend::new()`
614  importado de `crate::testing`. 8 tests del backend.rs intactos +
615  9 tests propios del mock en `testing.rs`.
616- Bajo `pub mod testing` (no `#[cfg(test)]`) deliberadamente: los
617  crates downstream pueden importarlo en sus dev/integ tests
618  vía `yahweh_meta_runtime::testing::MockBackend`.
619
620Cambios en `yahweh-widget-meta-form`:
621- **Dev-dep nueva**: `gpui = { workspace = true, features = ["test-support"] }`.
622  Habilita `TestAppContext` para tests sin abrir window real.
623- **`MetaApp::apply_action` ahora `pub`** (era privado). Necesario
624  para que los tests E2E lo invoquen desde fuera. La function ya
625  era el entry point de los click handlers internos; exponerla no
626  cambia el contract.
627- **Nuevo archivo `tests/widget_with_mock_backend.rs`** con 4 tests
628  `#[gpui::test]`:
629  - `meta_app_constructs_with_mock_backend_and_initial_state`:
630    instancia `MetaApp<MockBackend>` con records pre-poblados +
631    toast inicial; valida que la window construye sin panic.
632  - `open_view_action_does_not_panic`: invoca
633    `apply_action(OpenView)` real a través de
634    `window.update(cx, |meta, _, cx| ...)` → state machine corre
635    sin crash.
636  - `backend_state_visible_from_widget_perspective`: demuestra el
637    patrón "backend pre-poblado para fixtures" (typical para
638    screenshots / demos).
639  - `morphism_handler_can_be_registered_and_called_via_widget`:
640    `MockBackend::with_morphism` registra un counter callback;
641    `apply_action(Morphism)` lo dispara via `commit_morphism`
642    sin tocar nakui-core / Rhai.
643
644Helpers de tests:
645- `customers_module()`: fixture local de un `Module` con entity
646  Customer + view list + view form. Reusable cross-test.
647
648Distribución de tests:
649- `yahweh-meta-runtime`: 47 → **56** (+9 del nuevo testing
650  module).
651- `yahweh-widget-meta-form`: 3 → **7** (+4 E2E reales).
652- Total stack: **109 tests verdes** (56 runtime + 31 cards + 12
653  nakui-ui + 3 explorer + 7 widget).
654
655Beneficio operativo:
656- El widget tiene cobertura runtime real, no sólo type-check.
657- Cualquier app que tome `B: MetaBackend` puede testarse con
658  `MockBackend` en sus dev-deps sin re-implementar el mock.
659- Fixtures pre-pobladas habilitan demos/screenshots/CI con state
660  conocido.
661
662Limitaciones:
663- `render()` no se invoca en los tests (requiere window context
664  más rico). Los tests verifican state machine, no pixels. Pixel
665  comparison (snapshot tests) es scope futuro si emerge la
666  necesidad.
667- `apply_action(Morphism)` con un module que no declara
668  `nakui_module_dir` rebota antes de llamar al mock handler. El
669  4to test acepta ambos outcomes (counter 0 o 1) — si en el futuro
670  agregamos un módulo de fixture con nakui_module_dir poblado, el
671  test puede aserta exactamente.
672
673### feat(yahweh-meta-runtime): promover `short_hash` y `preview_value` desde nakui-explorer
674Continúa la integración de las apps nakui al stack yahweh. Los
675helpers visuales que `nakui-explorer` tenía locales y son reusables
676suben a `yahweh-meta-runtime/format` para que cualquier app pueda
677consumirlos sin duplicar.
678
679Cambios en `yahweh-meta-runtime`:
680- **`pub fn short_hash(h: &[u8; 32]) -> String`**: hex de los
681  primeros 4 bytes (8 chars). Útil para mostrar bundle/schema
682  hashes en UI sin quemar pantalla.
683- **`pub fn preview_value(v: &Value, max: usize) -> String`**:
684  JSON one-liner truncado con `...` al final si excede `max`
685  chars. Edge case: `max < 3` devuelve los primeros `max` chars
686  sin sufijo.
687- Re-exports en lib.
688- 5 tests nuevos: 4 tests + 1 sanity para el caso `max < ellipsis`.
689
690Migración de `nakui-explorer`:
691- Nueva dep `yahweh-meta-runtime` en Cargo.toml.
692- Borrado helpers locales `short_uuid`, `short_hash`,
693  `preview_value` (~30 líneas).
694- `use yahweh_meta_runtime::{preview_value, short_hash, short_uuid}`.
695- Borrados 4 tests duplicados (los runtime los testea).
696
697Tests:
698- `yahweh-meta-runtime`: 42 → **47** (+5 helpers nuevos).
699- `nakui-explorer`: 7 → **3** (–4 duplicados; quedan los 3
700  específicos: load_log, breakdown, missing_file).
701- Resto del workspace intacto.
702
703Beneficio operativo: 3 helpers visuales centralizados. Cualquier
704app nueva que muestre UUIDs/hashes/JSON-previews los importa sin
705re-implementar la heurística de truncamiento.
706
707Pendiente arquitectural: el render del card timeline en
708`nakui-explorer` (border-l-4 colored + flex_col + texto en niveles)
709es un pattern reusable que también aparece en `yahweh-widget-meta-form`
710(render_list filas). Cuando aparezca un tercer consumer de ese
711pattern se extrae a un widget yahweh.
712
713### refactor(yahweh): Fase 2c — extracción del widget al crate `yahweh-widget-meta-form`
714Cierra el refactor de UI: el widget render (forms, lists, modal de
715delete, EntityRef selector, sidebar, key handlers) deja de vivir en
716el binario nakui-ui y pasa a un crate yahweh nuevo, genérico sobre
717`MetaBackend`. nakui-ui queda como un shell de bootstrap de 424
718líneas.
719
720Crate nuevo: `crates/modules/ui_engine/widgets/meta-form/`
721(`yahweh-widget-meta-form`):
722- **Deps**: gpui, yahweh-meta-schema, yahweh-meta-runtime, yahweh-theme,
723  yahweh-widget-text-input, serde_json, uuid. **Cero deps a nakui** o
724  brahman-cards — reusable por cualquier app.
725- **`MetaApp<B: MetaBackend>`** público: estructura genérica con
726  `modules`, `backend: B`, `active`, `form_inputs`, `editing`,
727  `pending_delete`, `toast`, `load_error`. El bound `B: MetaBackend`
728  se propaga a todos los `impl MetaApp<B>` y al `impl Render for
729  MetaApp<B>`.
730- **`MetaApp::new(modules, backend, initial_toast, initial_error, cx)`**:
731  constructor sin lógica de bootstrap. El caller pre-construye
732  modules + backend + cualquier mensaje inicial. La active view
733  default es la primera entry del menú del primer módulo.
734- **Methods preservados** del original (rename simbólico): select_view,
735  open_edit, commit_seed, commit_morphism, commit_delete, apply_action,
736  list_rows, render_*, tick interno via WriteOutcome.post_status.
737- **Helpers locales del widget**: `lookup_field` (path walker JSON
738  por la lista renderer), `append_compact_msg` (concatenador del
739  toast), `format_seed_toast` (decide "creado/actualizado/sin cambios"
740  según `WriteOutcome`).
741- **3 tests funcionales puros**: `lookup_field`, `append_compact_msg`,
742  `format_seed_toast`. Tests con GPUI cx no son posibles sin un
743  TestAppContext setup; quedan implícitos vía type-check del trait
744  bound.
745
746Cambios en `nakui-ui` (shell):
747- **main.rs**: 1959 → **424** líneas (78% reducción). Ahora sólo:
748  1. Carga modules via `brahman_cards::load_cards_from_dir` +
749     `load_ui_modules` (filtra UiModule body, valida, dedup).
750  2. Carga executors para módulos con `nakui_module_dir`.
751  3. `NakuiBackend::open(...)` para inicializar el backend.
752  4. `cx.open_window(...)` con `MetaApp::<NakuiBackend>::new(...)`
753     como root view.
754- **`use yahweh_widget_meta_form::MetaApp`** + dep nueva en
755  Cargo.toml. Los imports de yahweh-meta-runtime/schema desaparecen
756  de main (los consume el widget internamente).
757- **Tests del shell**: 4 tests E2E que tocan nakui-core directamente
758  (event_log_replay, morphism_pipeline_real_sales_vender,
759  load_ui_modules x3). Los tests del NakuiBackend impl quedan en
760  `backend.rs` (8 tests). Los tests del widget viven en su propio
761  crate.
762- **`backend.rs`**: sin cambios (NakuiBackend ya estaba aislado en
763  Fase 2b).
764
765Distribución final del refactor yahweh:
766- `yahweh-meta-schema`: 8 tests (data puro).
767- `yahweh-meta-runtime`: 42 tests (helpers + trait MetaBackend).
768- `yahweh-widget-meta-form`: 3 tests (widget genérico).
769- `brahman-cards`: 26 tests (loader unificado).
770- `nakui-ui`: 12 tests (4 shell + 8 backend impl).
771- **Total: 91 tests** cubriendo el área.
772
773Cada crate compila individualmente. El widget consume el trait sin
774saber qué backend hay debajo; `nakui-ui` provee el trait wireado a
775nakui-core; cualquier futuro shell (mock para tests, otro stack de
776storage) puede reusar el widget sin cambio.
777
778Lo que NO hace Fase 2c:
779- No mueve `format_seed_toast`/`append_compact_msg`/`lookup_field`
780  a `yahweh-meta-runtime`. Son lo bastante widget-flavored
781  (`SharedString` de gpui, decisiones de UX del toast, etc.) que
782  preferí dejarlos al lado del render.
783- No introduce un `MetaApp::with_status` builder pattern. La
784  signature de `new` con 5 args es manejable; si crece, se refactor
785  después.
786- No expone configuración del widget (theme override, layout
787  custom, etc.). Cuando emerja una segunda app que use el widget
788  con preferencias distintas, se agregan opts.
789
790**Pendientes**:
7911. **KCL → Nickel**: kcl_wrapper en nakui-core reemplazado por
792   evaluación de Nickel contracts. Migrar los 3 schemas .k de
793   sales/inventory/treasury a .ncl.
7942. **`card.k` eliminado** (REFERENCE ONLY documentado en su header).
795
796### refactor(yahweh): Fase 2b — `MetaBackend` trait + `NakuiBackend` + MetaUi consume el backend
797Materialización del trait que diseñamos en charla. Tres pasos
798combinados en un solo commit:
799
800**Step A** — trait + WriteOutcome en `yahweh-meta-runtime`:
801- Nuevo módulo `backend.rs` con:
802  - `pub trait MetaBackend: 'static` con 6 métodos:
803    `list_records`, `load_record`, `seed`, `update`, `delete`,
804    `morphism`. Convención de ids como `Uuid` canónico (los
805    backends que internamente usan otros tipos mapean), `set+clear`
806    pre-computados por el caller (no double-roundtrip al store),
807    threshold `'static` sin Send/Sync (suficiente para handlers
808    GPUI single-threaded).
809  - `pub struct WriteOutcome { id, changed, post_status }` con
810    constructor `no_change(id)`. La UI usa `changed = 0` para
811    "sin cambios", `post_status` para concatenar mensajes
812    auto-emitidos por el backend (compact, etc.).
813- 9 tests con un `MemBackend` mínimo (HashMap por
814  `(entity, uuid)`): seed/load round-trip, list/filter/order,
815  update set/clear/no-op, delete/missing, object-safety check.
816
817**Step B** — `NakuiBackend` en `nakui-ui/src/backend.rs`:
818- Estructura que ownea `Arc<Mutex<MemoryStore>>`,
819  `Option<Arc<Mutex<EventLog>>>`, `BTreeMap<id, Arc<Executor>>`,
820  `snap_path`, `snapshot_threshold`, `writes_since_compact`.
821- `NakuiBackend::open(log_path, threshold, executors) -> (Self, OpenStatus)`:
822  abre log, carga snapshot, replay, auto-compact si threshold
823  cruzado; devuelve `OpenStatus { init_toast, load_error }` para
824  que el caller agregue al banner.
825- `tick_compact()` privado que cada write public method invoca
826  tras éxito; devuelve `Option<String>` que se mete en
827  `WriteOutcome.post_status`.
828- `impl MetaBackend for NakuiBackend`:
829  - `seed`: WAL order (log first, store after), `tick_compact`,
830    devuelve `WriteOutcome { id: Some(uuid), changed: 1, post_status }`.
831  - `update`: si `set+clear` vacíos devuelve `WriteOutcome::no_change`;
832    si no construye `FieldOp::Set`+`FieldOp::Clear`, log Morphism
833    `ui.edit_record` con `params.fields/cleared`, store.apply, tick.
834  - `delete`: `FieldOp::Delete`, log Morphism `ui.delete_record`,
835    store.apply, tick.
836  - `morphism`: locks log + store, `execute_and_log_with_recovery`,
837    tick. `WriteOutcome { id: None, changed: ops.len(), post_status }`.
838- Funciones `snapshot_path_for` y `maybe_compact_log` movidas acá
839  desde main.rs (ahora son detalle del backend).
840- 7 tests del impl: round-trip via trait, set+clear, no-op edit
841  no escribe, delete/load, list_records, morphism sin executor da
842  error claro, threshold dispara snapshot.
843
844**Step C** — `MetaUi` consume el backend:
845- Reemplaza fields `store` / `event_log` / `executors` /
846  `snap_path` / `snapshot_threshold` / `writes_since_compact`
847  por un único `backend: NakuiBackend`.
848- `MetaUi::new` colapsa el wiring de persistencia en
849  `NakuiBackend::open(...)` — pasó de ~150 líneas a ~10 líneas.
850- `commit_seed` ya no construye `LogEntry`/`FieldOp` directos:
851  - SEED → `self.backend.seed(entity, obj)`.
852  - EDIT → `self.backend.load_record + compute_field_delta +
853    compute_clear_fields → self.backend.update(set, clear)`.
854  - Devuelve `WriteOutcome` (reemplaza el viejo enum `CommitOutcome`).
855- `commit_morphism` parsea inputs/params del form y delega a
856  `self.backend.morphism(...)`.
857- `commit_delete` es one-liner: `self.backend.delete(entity, id)`.
858- `tick_runtime_compact` eliminado (ahora interno al backend; el
859  msg viaja en `WriteOutcome.post_status`).
860- `list_rows` queda como proxy `self.backend.list_records(entity)`.
861- `validate_entity_refs` callsite usa cierre sobre
862  `backend.load_record` (en vez de `&Store`).
863- Nuevo helper `format_seed_toast(entity, was_editing, &outcome)`
864  reemplaza el match sobre `CommitOutcome`.
865- Imports limpiados: no más `nakui_core::delta::FieldOp`/`FieldPath`,
866  no más `nakui_core::event_log::*` en main.rs (sólo en tests E2E).
867  No más `Arc/Mutex` (vive en backend).
868
869Distribución de tests post-refactor:
870- `yahweh-meta-runtime`: 33 → **42** (+9 trait tests con MemBackend).
871- `nakui-ui`: 14 → **21** (+7 tests del NakuiBackend impl).
872- `yahweh-meta-schema`: 8 (sin cambio).
873- `brahman-cards`: 26 (sin cambio).
874- Total: **97**.
875
876Build: cada crate compila individualmente.
877
878Nota sobre Fase 2b/c estado:
879- ✅ Backend trait + impl + MetaUi usa backend.
880- ⏭ Falta extraer los **widgets render** (form/list/modal/EntityRef
881  selector) de nakui-ui a un crate yahweh nuevo
882  (sugerencia: `yahweh-widget-meta-form`). Esa extracción ahora es
883  trivial: el render code ya consume sólo `&self.modules` +
884  `self.backend` (vía trait). Lo dejo para próximo commit.
885
886**Pendientes**:
8871. **Fase 2c**: extraer widget render al crate yahweh
888   (`yahweh-widget-meta-form` o similar) — `MetaApp<B: MetaBackend>`
889   genérico, `nakui-ui` queda como ~50 líneas de shell con
890   `MetaApp::<NakuiBackend>::new(...)`.
8912. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de
892   Nickel contracts.
8933. **`card.k` eliminado** (REFERENCE ONLY).
894
895### refactor(yahweh): Fase 2 — extraer helpers puros a `yahweh-meta-runtime`
896Sigue de la Fase 1 (lift del schema a yahweh). Ahora extraemos los
897**helpers puros** que cualquier widget renderer o backend ejecutor
898necesita sobre el schema: parse, delta, validation, format. Sin
899GPUI, sin acoplamiento a un backend específico.
900
901Crate nuevo: `crates/modules/ui_engine/libs/meta-runtime/`
902(`yahweh-meta-runtime`):
903- **Deps**: `serde_json`, `thiserror`, `uuid`, `yahweh-meta-schema`.
904  NO GPUI, NO nakui.
905- **Módulos**:
906  - `parse.rs` — `parse_field_value(kind, raw)`,
907    `infer_param_value(raw)`, `resolve_param_value(name, raw, spec)`.
908  - `delta.rs` — `compute_field_delta(current, proposed)`,
909    `compute_clear_fields(current, to_clear)`.
910  - `refs.rs` — `validate_entity_refs(load: F, refs)` donde `F`
911    es un cierre `Fn(&str, Uuid) -> Option<Value>`. Decoupling vía
912    closure en lugar de trait — evita atar el crate a cualquier
913    backend específico (no hay `Store` trait acá), y los callers
914    pasan `|e, id| store.load(e, id)` trivialmente.
915  - `format.rs` — `human_label_for_record(value, id)`,
916    `render_value(opt_value)`, `value_to_input_text(value)`,
917    `short_uuid(id)`.
918- **33 tests propios** en el crate nuevo (cubren todos los helpers
919  movidos + edge cases).
920
921Cambios en `nakui-ui`:
922- **Nueva dep** `yahweh-meta-runtime` en `Cargo.toml`.
923- **Imports**: agrega `use yahweh_meta_runtime::{...}` con todos los
924  helpers extraídos. Borrado el código local equivalente
925  (~200 líneas).
926- **`validate_entity_refs` callsite**: pasa de
927  `validate_entity_refs(&*store, &refs)` a
928  `validate_entity_refs(|e, id| store.load(e, id), &refs)` — el
929  closure es ergonómico sobre cualquier `Store`.
930- **Tests duplicados borrados** (~34 tests que ahora viven en
931  `yahweh-meta-runtime`):
932  - `parse_field_*` (text/number/boolean variants)
933  - `infer_param_value_*`
934  - `delta_*` (5 tests)
935  - `clear_fields_*` (3 tests)
936  - `validate_entity_refs_*` (5 tests)
937  - `resolve_param_*` (6 tests)
938  - `parse_field_entity_ref_*` (4 tests)
939  - `human_label_*` (3 tests), `render_value_*`,
940    `value_to_input_text_inverse_of_parse`
941- **Tests que se quedan en nakui-ui** (runtime-específicos):
942  - `lookup_field_simple_and_nested` — helper local del list renderer.
943  - `append_compact_msg_handles_both_branches`,
944    `runtime_compact_cycle_resets_counter_after_threshold`,
945    `snapshot_path_for_replaces_extension`,
946    `maybe_compact_log_*` (3) — wiring de persistencia a EventLog.
947  - `load_ui_modules_via_brahman_cards_*` (3) — integración con el
948    brazo de cards.
949  - `value_to_input_then_parse_round_trip` — round-trip del par
950    `value_to_input_text + parse_field_value` (toca ambos lados).
951  - `event_log_replay_restores_memory_store`,
952    `morphism_pipeline_executes_real_sales_vender`,
953    `event_log_replay_handles_full_crud_cycle` — E2E nakui-core.
954
955Distribución de tests:
956- `nakui-ui`: 48 → 14 (los 34 movidos viven en runtime).
957- `yahweh-meta-runtime`: 33 (nuevos).
958- `yahweh-meta-schema`: 8 (sin cambio).
959- `brahman-cards`: 26 (sin cambio).
960- Total cubriendo el área: 81.
961
962Build: cada crate afectado compila y testea limpio individualmente.
963Workspace build full no se completó esta corrida por OOM al
964compilar `surrealdb-core` (problema ambiental no relacionado al
965refactor).
966
967Lo que NO hace Fase 2:
968- No mueve los widgets render (`render_form`/`render_list`/
969  `render_entity_ref_selector`/`render_confirm_delete_banner`) a
970  yahweh — eso es Fase 2b/3, requiere diseñar el `MetaBackend`
971  trait porque las render functions tocan el state de `MetaUi`
972  (form_inputs, pending_delete, executors).
973
974**Pendientes** (orden):
9751. **Fase 2b**: extraer widget render a un crate yahweh nuevo
976   (sugerencia: `yahweh-widget-meta-form`). Requiere diseñar
977   `MetaBackend` trait.
9782. **Fase 3**: thin shell — `nakui-ui` queda reducido a una impl
979   de backend wireada a `nakui-core`.
9803. **KCL → Nickel** + **card.k eliminado**.
981
982### refactor(yahweh): Fase 1 — `nakui-ui-schema` → `yahweh-meta-schema`
983Primer paso del refactor yahweh. El schema de UI declarativa
984(entities, menús, listas, formularios, acciones) vivía bajo
985`crates/modules/nakui/ui-schema/` y se llamaba `nakui-ui-schema` —
986un nombre que sugería acoplamiento con Nakui que en realidad no
987existe (el crate sólo depende de `serde`/`serde_json`/`thiserror`).
988Lo movemos a yahweh para que sea consumible por cualquier app de UI
989metadata-driven sin hacer pasar la dep "rara" por nakui.
990
991Cambios mecánicos:
992- **`git mv`**: `crates/modules/nakui/ui-schema/` →
993  `crates/modules/ui_engine/libs/meta-schema/`.
994- **Cargo.toml del crate movido**:
995  - `name = "nakui-ui-schema"` → `name = "yahweh-meta-schema"`.
996  - Description actualizada: "Yahweh — meta-schema: descriptores
997    declarativos de UI ... independiente del backend".
998- **Workspace `Cargo.toml`**: la entry del members[] pasa de
999  `crates/modules/nakui/ui-schema` a
1000  `crates/modules/ui_engine/libs/meta-schema` (en su sección
1001  yahweh, no en la sección nakui).
1002- **`brahman-cards`**:
1003  - Cargo.toml: dep path/name a `yahweh-meta-schema`.
1004  - lib.rs: `pub use nakui_ui_schema::Module` →
1005    `pub use yahweh_meta_schema::Module`.
1006  - readers.rs: comment + doc-link al nuevo nombre.
1007- **`nakui-ui`**:
1008  - Cargo.toml: dep path/name a `yahweh-meta-schema`.
1009  - main.rs: `use nakui_ui_schema::{...}` →
1010    `use yahweh_meta_schema::{...}`.
1011- **Self-test del crate movido**
1012  (`tests/example_modules.rs`): `nakui_ui_schema` → `yahweh_meta_schema`,
1013  y se rebasa el path del repo root (5 niveles arriba ahora, era 4).
1014
1015Cambios documentales:
1016- **Doc de crate** (`lib.rs`): "Schema declarativo de la metainterfaz
1017  Nakui" → "Schema declarativo de la metainterfaz (yahweh
1018  meta-schema)" + "backend-agnostic" en la filosofía. La sección
1019  Persistencia universal pasa de "el runtime conecta cada vista al
1020  `nakui_core::store::Store`" a un wording neutro: "el runtime que
1021  consume este schema conecta vistas a su backend".
1022- **Doc del field `Module.nakui_module_dir`**: ahora marcado como
1023  "path opaco al backend, lo interpreta el runtime concreto". Se
1024  describe la convención actual de Nakui (nsmc.json + KCL + Rhai)
1025  como ejemplo, no como contrato del schema. El nombre del campo
1026  se mantiene por compat con módulos ya escritos; agregado
1027  `#[serde(alias = "backend_module_dir")]` para que un futuro
1028  rename no rompa los actuales.
1029
1030Tests:
1031- yahweh-meta-schema (crate movido): 13 tests propios siguen
1032  verdes tras el path rebase.
1033- brahman-cards: 26/26 verdes (17 integration + 9 nickel).
1034- nakui-ui: 48/48 verdes.
1035- Workspace build verde.
1036
1037Lo que NO hace Fase 1:
1038- No mueve los widgets de UI (form/list/modal/EntityRef selector)
1039  a yahweh — eso es Fase 2.
1040- No introduce un trait `MetaBackend` para desacoplar la lógica
1041  de runtime de Nakui — eso es Fase 3.
1042- No renombra el field `nakui_module_dir`. Se hará cuando aparezca
1043  un segundo backend que también lo necesite.
1044
1045**Pendientes** (orden):
10461. **Fase 2**: extraer widgets render (form/list/modal/EntityRef
1047   selector + helpers parse_field_value/render_value/etc.) a un
1048   nuevo crate `yahweh-widget-meta-form` (o nombre similar).
10492. **Fase 3**: trait `MetaBackend` + thin shell — `nakui-ui` queda
1050   reducido a una impl de backend wireada a `nakui-core`.
10513. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de
1052   Nickel contracts.
10534. **card.k eliminado** (REFERENCE ONLY).
1054

nahual.md

Changelog — nahual

Motor GPUI: libs + widgets. Renombrado de yahweh el 2026-05-19.

feat(nahual-widget-text-input): modo enmascarado para contraseñas

TextInput::with_mask() dibuja el contenido como puntos (•, uno por carácter Unicode) en vez del texto real; text() sigue devolviendo el contenido crudo. Lo usa el campo de contraseña de mirada-greeter. Lógica de enmascarado en la función pura display_text, con tests.

feat(nahual-theme): exportación del tema a GTK (módulo toolkit)

Módulo nuevo nahual-theme/src/toolkit.rs: traduce el Theme activo a ~/.config/gtk-3.0/gtk.css y gtk-4.0/gtk.css con overrides @define-color. Las apps GTK adoptan el acento exacto del tema + un neutro claro/oscuro coherente. Los fondos en gradiente de nahual no se pueden reproducir en ventanas GTK sólidas, así que el neutro se sintetiza con un ramp de luminancia tintado por el matiz del borde del tema.

feat(yahweh-launcher): F3 — extracción del shell standard de explorers

Iter 19. Patrón con 4 consumers idénticos (nakui-explorer, nouser-explorer, minga-explorer, brahman-broker-explorer) declaraban ~20 líneas de boot: `Application::new + Theme::install_default + cx.open_window

Crate nuevo crates/modules/ui_engine/libs/launcher/ (yahweh-launcher):

Migración 4 consumers:

Naming: el slot natural era yahweh-shell, pero ya existe un crate yahweh-shell en crates/apps/ (bootstrap heavyweight con file explorer + DB explorer + viewers). El helper es liviano y específico al patrón de launch, así que yahweh-launcher evita confusión.

Total ahorro: ~75 líneas hardcoded de boilerplate UI a 4 líneas en total. Cambios de boot (window opts, theme, etc) ahora viven en un solo lugar.

Tests stack: 2 nuevos en launcher; suites de los 4 consumers intactas. Todo verde.

feat(yahweh-widget-app-header): promover el 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.

Crate nuevo crates/modules/ui_engine/widgets/app-header/ (yahweh-widget-app-header):

Migración de los 4 consumers:

Total ahorro: ~50 líneas de código UI hardcoded en consumers. Cambios visuales en el header (padding, border, text_size) ahora viven en un solo lugar.

Tests stack: 3 nuevos en app-header; suites de los 4 consumers intactas. Todo verde.

Decisión: el sidebar header del MetaApp (que también incluye theme_switcher) NO se migra — es un header de sidebar, no de app top, y tiene styling distinto (px(12/10/13), sin bg/border-bottom porque ya está dentro del panel). Diferente patrón → diferente widget si emerge segundo consumer.

feat(yahweh-widget-stat-card): promover el patrón stat card como widget

Iter 15. El patrón "tarjeta de dashboard con border-l accent + label + valor grande + descripción + listing opcional" tenía 2 consumers (minga-explorer y brahman-broker-explorer); ahora vale extraer al stack yahweh para reusabilidad y mantenimiento single-place.

Crate nuevo crates/modules/ui_engine/widgets/stat-card/ (yahweh-widget-stat-card):

Cambios consumer:

Tests stack: nuevos 3 del widget. Suites de los 2 consumers intactas (4 minga-explorer, 2 broker-explorer). Stack total ~120 verdes (varía por compilation cache).

Beneficio operativo:

Pequeña simplificación documentada: el sub-header del listing pasa de "recent (N de TOTAL):" a "recent (N):". El "TOTAL" ya no se calcula porque el widget no lo conoce — el caller que quiera mostrarlo lo formatea en el label/value (ej. label "Nodos AST (5 de 247)"). Acceptable trade-off por la reusabilidad genérica.

feat(yahweh-theme): persistencia de la preferencia de theme entre runs

Iter 13. El theme switcher ya cambiaba el chrome en runtime, pero al cerrar y reabrir la app el theme volvía a Nebula default. Ahora el name del theme se persiste en $XDG_CONFIG_HOME/yahweh/theme (default ~/.config/yahweh/theme) y se restaura al boot.

Cambios en yahweh-theme:

5 tests nuevos (persistence_tests):

Tests stack: ~5 nuevos en yahweh-theme. Todos los downstream (nakui-ui, *-explorer) compilan sin tocar nada — la API pública de Theme::install_default y Theme::set no cambió shape. Smoke run del binario verificado: bootstrap OK, panic esperado sin display.

Beneficio operativo:

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. Caret blinking en text-input (yahweh-widget-text-input):

2. Slots ornament en yahweh-theme (5 nuevos):

3. MetaApp ornament cleanup (yahweh-widget-meta-form):

Antes de este commit MetaApp tenía la paleta principal themed (iter 5) pero el ornament secundario (hovers, separators, botones inline) seguía hardcoded. Ahora el theme switcher cambia absolutamente todo el chrome del MetaApp en runtime.

Tests: 117 verdes (sin cambios numéricos, pero downstream sigue compilando). Smoke run de nakui-ui: bootstrap completo OK.

Limitación restante: nouser-explorer todavía no migra al stack yahweh themed — patrón idéntico a nakui-explorer aplicado pero más nuevo. Próxima iter.

feat(yahweh-widget-text-input): focus-aware border + caret sólo on focus

Iter 7 (mini-iter — el text-input ya estaba themed, faltaba sólo el polish de focus visibility). Antes el border era siempre accent_strong y el caret | siempre estaba presente — imposible distinguir cuál input está activo en un form con varios fields.

Cambios en yahweh-widget-text-input:

Sin cambios en API pública — todo es interno al render. El binario no requiere migración.

Tests: sin cambios (los tests del crate son struct constructors, no rendering). Tests downstream del widget (yahweh-widget-meta-form, nakui-ui) siguen verdes — el cambio es backward compatible.

Beneficio operativo:

Limitación pendiente: el caret | literal no parpadea (no hay animation timer). Cuando emerja la necesidad, agregar via cx.spawn con un loop de toggle. Por ahora el caret estático on focus es suficiente signal.

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. nakui-ui y nakui-explorer lo incrustan en sus headers — un click cambia toda la paleta.

Crate nuevo: crates/modules/ui_engine/widgets/theme-switcher/ (yahweh-widget-theme-switcher):

Migración de consumers:

Tests stack: 115 → 117 (+2 del switcher). Cada crate compila individualmente.

Beneficio operativo:

Decisión técnica: el handler usa Theme::set(cx, ...) que invalida el global. GPUI marca todos los views como dirty y re-renderea — los widgets que leen Theme::global en su render ven el nuevo automáticamente. No requiere cx.observe_global explícito en cada widget consumidor.

Limitación: TextInput entities ya creadas no se actualizan visualmente si el theme cambia los colors del input bg/border (esos colors están hardcoded en yahweh-widget-text-input). Migrar text_input al theme es una iter futura — bajo scope porque actualmente vive suficientemente bien con sus defaults dark.

feat(yahweh-widget-meta-form): paleta del chrome migrada a Theme::global(cx)

Iter 5 de integración. El MetaApp::render tenía 7 vars locales con colors hardcoded (bg/panel/border/text/text_dim/accent/ accent_active) que se pasaban a las funciones internas (render_sidebar/render_main/render_list/render_form/ render_entity_ref_selector). Ahora salen del Theme::global(cx) que el binario shell instala al boot. El confirm_delete_banner también usa themed_colors(Banner::Warning) / themed_colors(Banner::Error) para sus colors base.

Cambios en MetaApp::render:

Cambios de firma (internas, no API público):

Cambios en render_confirm_delete_banner:

Lo que NO migra esta iter (queda como ornament hardcoded; iter futura si emerge la necesidad):

Estos son detalles ornamentales que un theme switcher real querría integrar; los aislo para una pasada futura cuando esté claro qué slots semánticos del theme conviene agregar (ej. bg_row_selected distinto de bg_row_hover, accent_destructive, etc.).

nakui-ui shell ya instalaba Theme::install_default(cx) desde la iter pasada — sigue siendo el contract entre el shell y el widget. Smoke test del binario verificado: bootstrap completo OK, panic esperado en open_window sin display.

Tests stack: 115 verdes (sin cambio — los tests del widget no acceden al render).

Beneficio operativo:

feat(yahweh): theme integration en banner + card + nakui-explorer consume themed

Iter 4 de la integración. Los widgets banner y card ahora ofrecen variants _themed(cx, ...) que leen Theme::global(cx). Las versiones sin theme se preservan para apps sin theme global. nakui-explorer migra a versiones themed + Theme::install_default al boot — el chrome hardcoded del explorer (5 variables let bg = rgb(...)) sale del theme.

Cambios en yahweh-widget-card:

Cambios en yahweh-widget-banner:

Migración de nakui-explorer:

Distribución de tests: 112 → 115 (+3 del banner derivation). Workspace stack pasó por la migración sin errores.

Beneficio operativo:

Decisiones:

Próximas integraciones pendientes:

feat(yahweh-widget-card): container card-shape compartido para timeline entries

Iteración 3 de la 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. Sin acoplamiento a colores: el caller decide bg/border/accent.

Crate nuevo: crates/modules/ui_engine/widgets/card/ (yahweh-widget-card):

Migración de nakui-explorer:

Tests stack: 111 → 112 verdes (+1 del crate card). Cada crate afectado compila y testea individualmente.

Beneficio operativo:

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): un div con bg

Crate nuevo: crates/modules/ui_engine/widgets/banner/ (yahweh-widget-banner):

Migración de consumers:

Tests stack: 109 → 111 verdes (+2 del crate banner).

Beneficio operativo:

Próximo candidato natural: el confirm_delete_banner de MetaApp es Banner::Warning + 2 botones embedded. Cuando emerja un segundo consumer de modal-style banners, extraer un widget compositivo arriba del Banner base.

feat(yahweh): MockBackend público + tests E2E del widget con gpui::TestAppContext

Cierra el ciclo de testabilidad del widget metainterfaz. Hasta ahora los tests del trait MetaBackend vivían como impl privada en backend.rs; el widget no tenía forma de testear handlers reales sin levantar NakuiBackend (que depende de event log + Rhai + nakui-core). Ahora el mock es público y los tests del widget lo consumen con TestAppContext.

Cambios en yahweh-meta-runtime:

Cambios en yahweh-widget-meta-form:

Helpers de tests:

Distribución de tests:

Beneficio operativo:

Limitaciones:

feat(yahweh-meta-runtime): promover short_hash y preview_value desde nakui-explorer

Continúa la integración de las apps nakui al stack yahweh. Los helpers visuales que nakui-explorer tenía locales y son reusables suben a yahweh-meta-runtime/format para que cualquier app pueda consumirlos sin duplicar.

Cambios en yahweh-meta-runtime:

Migración de nakui-explorer:

Tests:

Beneficio operativo: 3 helpers visuales centralizados. Cualquier app nueva que muestre UUIDs/hashes/JSON-previews los importa sin re-implementar la heurística de truncamiento.

Pendiente arquitectural: el render del card timeline en nakui-explorer (border-l-4 colored + flex_col + texto en niveles) es un pattern reusable que también aparece en yahweh-widget-meta-form (render_list filas). Cuando aparezca un tercer consumer de ese pattern se extrae a un widget yahweh.

refactor(yahweh): Fase 2c — extracción del widget al crate 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 el binario nakui-ui y pasa a un crate yahweh nuevo, genérico sobre MetaBackend. nakui-ui queda como un shell de bootstrap de 424 líneas.

Crate nuevo: crates/modules/ui_engine/widgets/meta-form/ (yahweh-widget-meta-form):

Cambios en nakui-ui (shell):

Distribución final del refactor yahweh:

Cada crate compila individualmente. El widget consume el trait sin saber qué backend hay debajo; nakui-ui provee el trait wireado a nakui-core; cualquier futuro shell (mock para tests, otro stack de storage) puede reusar el widget sin cambio.

Lo que NO hace Fase 2c:

Pendientes:

  1. KCL → Nickel: kcl_wrapper en nakui-core reemplazado por evaluación de Nickel contracts. Migrar los 3 schemas .k de sales/inventory/treasury a .ncl.
  2. card.k eliminado (REFERENCE ONLY documentado en su header).

refactor(yahweh): Fase 2b — MetaBackend trait + NakuiBackend + MetaUi consume el backend

Materialización del trait que diseñamos en charla. Tres pasos combinados en un solo commit:

Step A — trait + WriteOutcome en yahweh-meta-runtime:

Step B — NakuiBackend en nakui-ui/src/backend.rs:

Step C — MetaUi consume el backend:

Distribución de tests post-refactor:

Build: cada crate compila individualmente.

Nota sobre Fase 2b/c estado:

Pendientes:

  1. Fase 2c: extraer widget render al crate yahweh (yahweh-widget-meta-form o similar) — MetaApp<B: MetaBackend> genérico, nakui-ui queda como ~50 líneas de shell con MetaApp::<NakuiBackend>::new(...).
  2. KCL → Nickel: kcl_wrapper reemplazado por evaluación de Nickel contracts.
  3. card.k eliminado (REFERENCE ONLY).

refactor(yahweh): Fase 2 — extraer helpers puros a yahweh-meta-runtime

Sigue de la Fase 1 (lift del schema a yahweh). Ahora extraemos los helpers puros que cualquier widget renderer o backend ejecutor necesita sobre el schema: parse, delta, validation, format. Sin GPUI, sin acoplamiento a un backend específico.

Crate nuevo: crates/modules/ui_engine/libs/meta-runtime/ (yahweh-meta-runtime):

Cambios en nakui-ui:

Distribución de tests:

Build: cada crate afectado compila y testea limpio individualmente. Workspace build full no se completó esta corrida por OOM al compilar surrealdb-core (problema ambiental no relacionado al refactor).

Lo que NO hace Fase 2:

Pendientes (orden):

  1. Fase 2b: extraer widget render a un crate yahweh nuevo (sugerencia: yahweh-widget-meta-form). Requiere diseñar MetaBackend trait.
  2. Fase 3: thin shell — nakui-ui queda reducido a una impl de backend wireada a nakui-core.
  3. KCL → Nickel + card.k eliminado.

refactor(yahweh): Fase 1 — nakui-ui-schema → yahweh-meta-schema

Primer paso del refactor yahweh. El schema de UI declarativa (entities, menús, listas, formularios, acciones) vivía bajo crates/modules/nakui/ui-schema/ y se llamaba nakui-ui-schema — un nombre que sugería acoplamiento con Nakui que en realidad no existe (el crate sólo depende de serde/serde_json/thiserror). Lo movemos a yahweh para que sea consumible por cualquier app de UI metadata-driven sin hacer pasar la dep "rara" por nakui.

Cambios mecánicos:

Cambios documentales:

Tests:

Lo que NO hace Fase 1:

Pendientes (orden):

  1. Fase 2: extraer widgets render (form/list/modal/EntityRef selector + helpers parse_field_value/render_value/etc.) a un nuevo crate yahweh-widget-meta-form (o nombre similar).
  2. Fase 3: trait MetaBackend + thin shell — nakui-ui queda reducido a una impl de backend wireada a nakui-core.
  3. KCL → Nickel: kcl_wrapper reemplazado por evaluación de Nickel contracts.
  4. card.k eliminado (REFERENCE ONLY).