ab1cf9998a
Las vistas de lista de meta-form ganan: orden por columna (clic en header cicla asc→desc→off con indicador ▲/▼), búsqueda en vivo (caja 🔍 que filtra por search_in mientras se teclea, vía cx.observe del TextInput) y paginación (25/página, controles ◀▶). Sin cambios de schema: son estado del widget. Helpers puros cmp_values (meta-runtime) y next_sort con tests. Tests verdes (meta-runtime 63, meta-form 8); clippy limpio. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1122 lines
52 KiB
Markdown
1122 lines
52 KiB
Markdown
# Changelog — nahual
|
||
|
||
Motor GPUI: libs + widgets. Renombrado de `yahweh` el 2026-05-19.
|
||
|
||
### feat(meta-form): listas profesionales — orden, búsqueda, paginación
|
||
|
||
Fase 4 del ERP nakui. Las vistas de lista de `meta-form` ganan:
|
||
|
||
- **Orden por columna** — clic en un header cicla ascendente →
|
||
descendente → sin orden (indicador ▲/▼). Comparación de valores con
|
||
`cmp_values` (nuevo en `meta-runtime`): números por valor, strings
|
||
case-insensitive, `null` primero.
|
||
- **Búsqueda en vivo** — caja 🔍 que filtra por substring contra las
|
||
columnas de `search_in` mientras se teclea (vía `cx.observe` del
|
||
`TextInput`). `search_in` ya existía en el schema; ahora se renderiza.
|
||
- **Paginación** — 25 filas por página, controles ◀ ▶ y «página N/M».
|
||
|
||
Sin cambios de schema: orden y página son estado del widget, se
|
||
reinician al navegar. Helpers puros `cmp_values` y `next_sort` con
|
||
tests.
|
||
|
||
### feat(meta-*): ficha de detalle (Fase 3 del ERP nakui)
|
||
|
||
La metainterfaz gana una tercera clase de vista:
|
||
|
||
- **`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`:
|
||
- Nueva dep `yahweh-widget-card`.
|
||
- Los 2 patterns de timeline entry (Seed y Morphism) pasan de:
|
||
```rust
|
||
div().flex().flex_col().px(12).py(8).mb(4).bg(card_bg)
|
||
.rounded(4).border_l_4().border_color(accent).gap(2)...
|
||
```
|
||
a:
|
||
```rust
|
||
card().bg(card_bg).border_l_4().border_color(accent)...
|
||
```
|
||
- Reducción ~7 calls → ~3 por entry; legibilidad mejor (la
|
||
intención "card with accent" emerge del nombre `card()`).
|
||
|
||
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:
|
||
1. Carga modules via `brahman_cards::load_cards_from_dir` +
|
||
`load_ui_modules` (filtra UiModule body, valida, dedup).
|
||
2. Carga executors para módulos con `nakui_module_dir`.
|
||
3. `NakuiBackend::open(...)` para inicializar el backend.
|
||
4. `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**:
|
||
1. **KCL → Nickel**: kcl_wrapper en nakui-core reemplazado por
|
||
evaluación de Nickel contracts. Migrar los 3 schemas .k de
|
||
sales/inventory/treasury a .ncl.
|
||
2. **`card.k` eliminado** (REFERENCE ONLY documentado en su header).
|
||
|
||
### refactor(yahweh): Fase 2b — `MetaBackend` trait + `NakuiBackend` + MetaUi consume el backend
|
||
Materialización del trait que diseñamos en charla. Tres pasos
|
||
combinados en un solo commit:
|
||
|
||
**Step A** — trait + WriteOutcome en `yahweh-meta-runtime`:
|
||
- 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**:
|
||
1. **Fase 2c**: extraer widget render al crate yahweh
|
||
(`yahweh-widget-meta-form` o similar) — `MetaApp<B: MetaBackend>`
|
||
genérico, `nakui-ui` queda como ~50 líneas de shell con
|
||
`MetaApp::<NakuiBackend>::new(...)`.
|
||
2. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de
|
||
Nickel contracts.
|
||
3. **`card.k` eliminado** (REFERENCE ONLY).
|
||
|
||
### refactor(yahweh): Fase 2 — extraer helpers puros a `yahweh-meta-runtime`
|
||
Sigue de la Fase 1 (lift del schema a yahweh). Ahora extraemos los
|
||
**helpers puros** que cualquier widget renderer o backend ejecutor
|
||
necesita sobre el schema: parse, delta, validation, format. Sin
|
||
GPUI, sin acoplamiento a un backend específico.
|
||
|
||
Crate nuevo: `crates/modules/ui_engine/libs/meta-runtime/`
|
||
(`yahweh-meta-runtime`):
|
||
- **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):
|
||
1. **Fase 2b**: extraer widget render a un crate yahweh nuevo
|
||
(sugerencia: `yahweh-widget-meta-form`). Requiere diseñar
|
||
`MetaBackend` trait.
|
||
2. **Fase 3**: thin shell — `nakui-ui` queda reducido a una impl
|
||
de backend wireada a `nakui-core`.
|
||
3. **KCL → Nickel** + **card.k eliminado**.
|
||
|
||
### refactor(yahweh): Fase 1 — `nakui-ui-schema` → `yahweh-meta-schema`
|
||
Primer paso del refactor yahweh. El schema de UI declarativa
|
||
(entities, menús, listas, formularios, acciones) vivía bajo
|
||
`crates/modules/nakui/ui-schema/` y se llamaba `nakui-ui-schema` —
|
||
un nombre que sugería acoplamiento con Nakui que en realidad no
|
||
existe (el crate sólo depende de `serde`/`serde_json`/`thiserror`).
|
||
Lo movemos a yahweh para que sea consumible por cualquier app de UI
|
||
metadata-driven sin hacer pasar la dep "rara" por nakui.
|
||
|
||
Cambios mecánicos:
|
||
- **`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):
|
||
1. **Fase 2**: extraer widgets render (form/list/modal/EntityRef
|
||
selector + helpers parse_field_value/render_value/etc.) a un
|
||
nuevo crate `yahweh-widget-meta-form` (o nombre similar).
|
||
2. **Fase 3**: trait `MetaBackend` + thin shell — `nakui-ui` queda
|
||
reducido a una impl de backend wireada a `nakui-core`.
|
||
3. **KCL → Nickel**: kcl_wrapper reemplazado por evaluación de
|
||
Nickel contracts.
|
||
4. **card.k eliminado** (REFERENCE ONLY).
|
||
|