gioser

tawasuyu/brahman

Rust 476TOML 190Markdown 50JSON 176602 Definiciones

⎇ c56ef2554615e03b5fcdfb80c0e5e5c826a94f00 docs/changelog/nahual.md Markdown · 1163 líneas · 54.3 KB Historia crudo

feat(nakui): Fase 7 del ERP — pulido (cierra el plan maestro) c56ef2554 · sergio ·

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

nahual.md

Changelog — nahual

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

feat(meta-form): pulido de formularios (Fase 7 del ERP nakui)

Cierre del plan ERP. Pulido de la metainterfaz:

feat(meta-form): export CSV de listas (Fase 6 del ERP nakui)

feat(meta-*): tablero de KPIs (Fase 5 del ERP nakui)

Cuarta clase de vista de la metainterfaz:

Tests de compute_metric en meta-runtime; verificación del tablero del CRM en nakui-ui.

feat(meta-form): listas profesionales — orden, búsqueda, paginación

Fase 4 del ERP nakui. Las vistas de lista de meta-form ganan:

Sin cambios de schema: orden y página son estado del widget, se reinician al navegar. Helpers puros cmp_values y next_sort con tests.

feat(meta-*): ficha de detalle (Fase 3 del ERP nakui)

La metainterfaz gana una tercera clase de vista:

Tests en meta-schema y nakui-ui.

feat(meta-*): relaciones legibles + formato (Fase 2 del ERP nakui)

Tests nuevos en meta-runtime (format_value, labels ES) y meta-schema. Ver el changelog de nakui para el plan maestro.

feat(meta-*): FieldKind Select y AutoId (Fase 1 del ERP nakui)

La metainterfaz declarativa gana dos tipos de campo:

parse_field_value trata ambos como string passthrough. Tests nuevos en meta-schema (validación de Select) y meta-runtime (parseo).

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).