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