Renderer (gioser-canvas-web):
- Spring shake (SpringDamper1, 7.5 Hz / ζ=0.13) aplicado como rotación Z
en el MVP. impulse_click() inyecta velocidad alternada → vibración fuerte
con ~5 ciclos decayendo en ~0.8s.
- release_tilt() pone target del tilt en (0,0) → la chacana cae al frente
con el rebote natural del spring sub-crítico.
- world_scale_for_aspect(): en portrait (aspect<1) escala baja proporcional
para que el aro exterior no se corte por los lados. Base 1.05, piso 0.45.
- click_radius_css_px() expone radio del aro en CSS-pixels desde el centro
del canvas; la app lo usa para hit-test del impulso.
- set_client_size() separa CSS-pixels de device-pixels (DPR).
- tilt_degrees() ahora retorna (pitch, yaw, roll) — el brand replica los 3.
- 4 nuevos uniforms u_aire/fuego/tierra/agua_color para el shader de
partículas.
Shader (gioser-shaders/FS_CHACANA):
- Función element_particles(tip, outward, color, kind) → 4 partículas por
cardinal con personalidad: AIRE drift+sway, FUEGO rise+flicker (siempre
hacia +Y), TIERRA cae, AGUA ondula descendiendo. Gauss + envelope
sinusoidal en la vida. ~16 partículas total, costo modesto.
App (gioser-web):
- pointerdown en canvas → si distancia al centro < click_radius_css_px →
impulse_click(). Touch y mouse vienen unificados por PointerEvent.
- mouseleave en canvas → release_tilt(). Sin set_target, el spring se
quedaría en la última posición — ahora vuelve al frente con rebote.
- position_tips ahora clampea raw_x/raw_y a [margin, viewport - taskbar -
margin] en CSS pixels. Los botones NUNCA salen del canvas ni cubren la
taskbar incluso en aspect extremos o tilt máximo.
- AppState + TaskbarState (RefCell): trackea drawers abiertos + activo.
open_tab/switch_tab/close_tab/home aplican mutación + sync().
- sync() rebuild de taskbar-list innerHTML por cada cambio de estado,
más swap de body classes + drawer .open classes.
- Click delegation en taskbar-list — un listener para todas las cajitas.
- Botón home con data-home en la barra (svg de casa) cierra todo y
limpia el taskbar.
- Escape también cierra el drawer activo.
- update_tilt_css ahora setea --tilt-z también — brand title roll
visible en el shake.
CSS:
- .drawer bottom: 52px (reserva taskbar).
- .taskbar full ancho fixed bottom, glass + gold border, scrollable horiz
para muchas cajitas.
- .taskbar-item con --task-color por elemento (aire/fuego/tierra/agua),
.active glow del color + inset border bottom.
- .taskbar-home con svg de casa dorado, hover glow.
- Responsive: taskbar 46px en mobile + ajustes.
- .brand transform agrega rotateZ(--tilt-z) para que el título vibre con
la chacana en click impulses.
Workspace verde + 18 tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Visual de la chacana retrabajado contra chakana.png de referencia:
- Sol detrás (gauss + corona, masked al interior de la chacana — sólo
asoma por la superficie de la cruz, no se cuela afuera).
- Doble outline dorado (línea principal + paralela offset 0.020), color
CHACANA_LINE pasa de cyan helado a dorado-ámbar del logo.
- Interior con niebla violeta-noche (u_dark_color) y rayos radiales
sutiles desde el centro, modulados por sin(t * 0.3).
- Aro doble exterior: ring fino interior + ring grueso con 4 grupos de
3 puntos cardinales (calculados angularmente, no rayos largos).
- WORLD_SCALE 1.45→1.05, MAX_TILT 35°→28° (más sólido, menos caricaturesco).
Título "GioSer" centrado dentro de la superficie de la chacana, sin
subtítulo. Se inclina junto con la chacana vía CSS perspective +
rotateX/rotateY desde u-tilt-x/y inyectadas cada frame por WASM.
Botones (4 tips):
- Reposicionados a `arm_extent * 1.32` (entre punta y aro grueso).
- Bigger: min-width 168px, glyph 54px, label Cinzel 0.95rem.
- Doble anillo en hover (::before con border + glow).
- Cuando un drawer se abre, fade-out de tips + canvas + brand.
Drawers MD (uno por elemento):
- `<aside class="drawer drawer-{element}">` con transform-origin desde
CSS vars (--origin-x/y) seteadas por WASM al click — crece desde la
posición exacta del botón hasta fullscreen en 700ms con cubic-bezier.
- Ambience por elemento: AIRE (radial drift), FUEGO (flicker keyframe),
AGUA (tide vertical), TIERRA (warm earth gradient).
- Cerrado con botón X, Escape o data-close-drawer.
- Carga MD desde ./md/{element}.md via spawn_local + Reader::open_url.
Pluma (visor MD agnóstico, dos crates nuevos):
- `crates/modules/pluma/pluma-md` — wrapper sobre pulldown-cmark 0.12.
API: to_html(), to_themed_html(md, theme) con sanitización del theme,
events() para AST stream. GFM completo. No deps web. 5 tests.
- `crates/modules/pluma/pluma-reader-web` — toma HtmlElement, expone
open_url async (fetch via wasm-bindgen-futures), render_md sync,
show_loading/show_error. NO inyecta CSS — el host estiliza
`.pluma-doc[data-pluma-theme="..."]` con sus colores.
CSS pluma-doc completo: h1/h2/h3, code/pre con border-left accent,
blockquote, tables, lists, hr gradient. Loader spinner + error state.
Placeholders en md/{aire,fuego,tierra,agua}.md con texto seed.
Workspace verde + 18 tests (6 geom + 4 palette + 3 physics + 5 pluma-md).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- gioser-geom: ChacanaSpec paramétrica con `steps` (default 2). bounding box
cuadrado (no cruz alargada), centro 6s×6s, brazos cortos de 2 niveles que
adelgazan hacia la punta. arm_extent = 0.65 con thickness=0.13.
- gioser-shaders: nubes FBM 5× más rápidas, 3 estratos de estrellas con
twinkle independiente, 4 meteoros procedurales con cola/cabeza y vida
cíclica. Chacana SDF rediseñada para 2 escalones, aro doble (interior +
exterior), 12 rayos angulares y 4 marcas cardinales animadas.
- gioser-canvas-web: MAX_TILT 22°→35°, WORLD_SCALE 0.92→1.45, spring
1.8 Hz / ζ=0.62 (más languido). uniform `u_center_half` agregado.
Las puntas DOM se desplazan visiblemente con el tilt.
- README: fix wasm-bindgen-cli 0.2.99 → 0.2.121 + `--locked`.
13 tests pasan (6 geom + 4 palette + 3 physics).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- axis.rs: paint_axes extraído a función pública reusable entre
crates de visualización. LapalomaChartElement::paint_axes ahora
es un thin wrapper.
- OhlcBuffer: stride 6 f32 por bar (t, o, h, l, c, v). Bar struct
con is_bull/is_bear. price_range y time_range. 5 tests.
- aggregate_time_bucketed (sección 3.2 del ARCHITECTURE.md):
buckets por TIEMPO (no índice) — open=first, close=last,
high=max, low=min, volume=sum. Preserva volatilidad (los wicks
sobreviven al downsample, a diferencia de LTTB). Fallback a
copy 1:1 si el span temporal es cero. 4 tests cubren bucket
count, preservation of volatility, fallback, empty input.
- paint_candlesticks: render agnóstico contra el trait Canvas.
Wick = stroke_line vertical (high → low). Body = fill_rect
open ↔ close con color bull/bear/neutral. body_width derivado
del spacing entre bars (con body_min_width floor).
- LapalomaCandlestickElement: Element GPUI que reusa paint_axes
+ paint_candlesticks. Sin pan-blit cache en v0.1 (≤500 bars
on-screen no lo necesita).
- crates/apps/lapaloma-financial-demo: random walk determinístico
(xorshift32 inline + seed fijo) de 120 bars, pan + zoom + reset
igual que el cartesian demo. Paleta nórdica para bull (#a3be8c)
y bear (#bf616a).
60 tests verdes (28 cartesian + 20 core + 9 financial + 3 render).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ChartCache + ChartCacheHandle (Arc<Mutex<...>>) cacheable entre
frames. El Render host crea uno con chart_cache() y lo pasa al
Element con .with_cache(handle). Sin handle, cada frame rebuild
completo (correcto pero sin la optimización).
- Hash estructural: plot rect + viewport.span (no x_min/y_min) +
per-series (data.revision + data.len + stroke). 5 tests cubren
estabilidad, pan no invalida, zoom invalida, data revision
invalida, plot rect invalida.
- En paint: si el hash matches, pan-blit = copia las coords
cacheadas con offset (dx_px, dy_px) calculado del diff entre
viewport.x_min cached vs actual. Salteamos LTTB + projection.
- LineSeries::compute_projected expone el pipe LTTB + project_buffer
como método público para que el Element pueda cachear sin pasar
por paint().
- Demo multi-series usa el cache; header muestra "cache: N
pan-blits / M rebuilds" en vivo para que se vea la métrica al
draguear (pan-blits crece) y al zoomear (rebuilds crece).
Limitación v0.1 anotada en código: el doc canónico (sección 4.4)
usa una textura offscreen blitable; GPUI 0.2 no expone esa primitiva
directa. La impl actual cachea coords proyectadas y emite las
polilíneas con offset — mismo ahorro de CPU (saltea LTTB) sin GPU
texture cache.
51 tests verdes (28 cartesian incluyendo 5 nuevos del structural_hash,
20 core, 3 render).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- lapaloma-phosphor: feature `gpui` (default). LapalomaPhosphorElement
divide el RingBuffer en N segmentos (default 16, configurable) y
pinta cada uno como una stroke_polyline con alpha = (k+1)/N. El
segmento más nuevo va con alpha 1.0, el más viejo casi
transparente — efecto fósforo persistente.
- Cada segmento incluye el primer punto del siguiente para evitar
gaps visibles entre tramos.
- Wraparound se parte en dos sub-polilíneas (no concatenadas) para
no introducir la línea horizontal "del slot cap-1 al slot 0".
- Glow opcional: pasada adicional con width × glow_width_mult y
alpha × glow_alpha — efecto halo CRT.
- crates/apps/lapaloma-phosphor-demo: misma señal sintética que
stream-demo, paleta verde Tektronix (#9bff8c sobre #050805),
trail 24 segs + glow 4× α 0.18.
Limitación v0.1: el doc canónico usa triangle strip con per-vertex
color (sección 4.3); GPUI 0.2 no expone esa API directa. La impl
actual es funcionalmente equivalente con N draw calls en lugar de 1.
Cuando wgpu directo esté disponible, swap inmediato sin tocar las
API públicas.
46 tests verdes (sin cambios; phosphor se valida via demo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- lapaloma-stream: feature `gpui` (default). LapalomaStreamElement
pinta un RingBuffer en modo sweep — dos polilíneas split-at-head
(segmento [head..cap) viejo + [0..head) nuevo) para evitar la
línea horizontal del wraparound. Pre-fill (count < cap) sólo
pinta [0, head) para evitar el flat-line del 1.0.2 fix.
- y_range configurable, background opcional, padding.
- crates/apps/lapaloma-stream-demo: osciloscopio sintético con
RingBuffer cap=512. Timer en cx.background_executor que hace
push(synthesize(t)) + cx.notify() cada 16ms (60 Hz). Señal =
suma de dos sinusoides desfasadas + jitter determinístico.
Header muestra cap / head / filled% / t / revision en vivo.
- Workspace: registrada la app lapaloma-stream-demo.
Showcase del P2 zero-alloc: push(v) son 2 writes + 2 increments,
zero allocations per frame, ningún Vec se reasigna.
46 tests verdes (sin cambios; el stream se valida en runtime via demo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Element ahora mantiene Vec<ChartSeriesItem> con DataBuffer +
StrokeStyle + nombre opcional por serie. Builder add_series y
add_series_named.
- En paint(), una pasada por cada serie reusando el mismo scratch.
N series = N paint_path (no N × por punto). Cumple P3 del
ARCHITECTURE.md por serie.
- `lapaloma_chart(data, vp, stroke)` queda como helper retrocompat
para el caso una-serie.
- Demo: 3 series simultáneas (sin, cos, mix) con colores nórdicos
+ leyenda textual en el header.
46 tests verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- viewport.rs: `pan_fraction(fx, fy)` — pan en fracción del viewport
independiente del plot_rect. Útil cuando el handler GPUI trabaja
en coords de window y no conoce el rect interno del chart.
- lapaloma-demo: state machine de drag (DragAnchor con snapshot del
viewport al click) + handlers on_mouse_down/move/up para pan,
on_scroll_wheel con sensitivity exponencial 0.0015 para zoom
anchor-preserving al cursor, on_click con click_count >= 2 para
reset al viewport inicial. El header muestra estado dragging.
- Maneja ScrollDelta::Pixels (trackpad) y ::Lines (mouse wheel
tradicional) unificando con line-height 16px.
46 tests verdes en lapaloma-{core,cartesian,render}.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- axis.rs: ticks_nice (Wilkinson sobre lapaloma_core::scale::nice_step),
decimate_labels con min_spacing_px, format_tick con decimales según
step, AxisStyle config. 8 tests.
- gpui_backend::draw_text: shape_line via window.text_system() + iterate
glyphs con paint_glyph. Sin dep en App context (sólo &mut Window).
- LapalomaChartElement.paint_axes: línea base + tick marks + labels
centrados (X) / right-aligned (Y) con decimación. Margins por defecto
reservan 32px izq + 24px abajo.
45 tests verdes en lapaloma-{core,cartesian,render}.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Cadena end-to-end DataBuffer → LineSeries → Canvas → gpui::Window
funcionando. cargo run -p lapaloma-demo abre una ventana con sin(x)
sobre 1024 muestras y una sola paint_path por frame.
- lapaloma-render: feature `gpui` opcional. WindowCanvas adapter
traduce el trait Canvas a paint_quad/paint_path de gpui 0.2.
Conversión RGB→HSL para integrar con el sistema de colores Hsla
del resto del codebase yahweh. 3 tests de conversión.
- lapaloma-cartesian: feature `gpui` (default). element::LapalomaChartElement
con impl Element + IntoElement. Arma WindowCanvas en paint() y
delega a LineSeries — un solo paint_path por chart.
- crates/apps/lapaloma-demo registrado en workspace.
Limitaciones conocidas v0.1: clip stack, triangle strips y draw_text
no implementados (los necesitan phosphor / Sankey / axes; se
agregan en sus fases).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- ChartViewport: pan/zoom anchor-preserving en coords de dominio.
- CoordinateSystem: proyección dominio→pixel + project_buffer zero-alloc.
- trait Series + LineSeries que emite una sola stroke_polyline por frame
(valida P3 del ARCHITECTURE.md). LTTB se dispara cuando data.len()
excede 3× el ancho del plot.
- hit_test sobre coords sorted-by-X con binary search + threshold 8px.
- 14 tests cubren pan, zoom, projection, downsample y hit-test.
Element GPUI queda para la siguiente fase (requiere pionear paint custom
sobre PaintContext — el monorepo no tiene precedente todavía).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Daemon escribe append-only a $XDG_STATE_HOME/shipote/audit.log además
del tracing. Single-line: ts=<ms> uid=<peer> action=<verb> <detail>.
Rotación simple a .log.1 al pasar 1 MiB.
- shipote-gateway: TCP listener 127.0.0.1:7378 default. POST /rpc traduce
JSON ↔ postcard contra daemon socket. GET / health text. HTTP parser
ad-hoc (~70 LOC), sin dep de hyper/axum. Sin auth — bind a localhost
+ SHIPOTE_TRUST_ANYONE=1 en prod.
E2E: curl --noproxy '*' POST /rpc → "Pong", Health JSON, Capabilities
JSON. Audit log persiste mutaciones con uid del peer.
85 tests pasan (features nuevos son binarios, no library mods).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- shipote-shell Flow channels card extiende con bytes_total + bytes/s
por socket. Lookup helper evita borrows en closures.
- DiscernPolicy.max_bytes_per_sec: splitter task hace sleep proporcional
al tamaño de chunk tras cada broadcast. Token-bucket simple v1.
- WorkspaceManager.dirty: AtomicBool. mark_dirty() en mutaciones que
afectan al snapshot. save_snapshot skip si clean y path existe.
restore_snapshot resetea dirty=false (hidratación no es mutation).
85 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 26, shipote-discern 5, yahweh-provider-fs 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Flow socket names usan pipeline_id full (ULID 26 chars) + edge_idx.
Cero colisiones entre pipelines (ULID es único global). Fallback con
suffix -N si el path existe (cap 1000 retries).
- WorkspaceState.stats_history (VecDeque cap 64) — workspace_stats
appendea cada call. API workspace_stats_history(id, tail). Protocol
WorkspaceStatsHistory. Shell pide history al primer probe → sparkline
hidratada al boot, sobrevive restart del shell.
84 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 25, shipote-discern 5, yahweh-provider-fs 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Daemon SIGTERM/SIGINT: snapshot ANTES, stop_with_grace(1s) de todos
los workspaces DESPUÉS. Grace permite app-level cleanup.
- Snapshot v3 con live_pipelines: pipeline_supervisors se persisten;
daemon relanza al restore con sus recursos (Incarnator+DiscernPipeline).
RestoreOutcome separado para que core no necesite incarnator.
Forward-compat v1/v2 via #[serde(default)].
- WorkspaceFullSummary: stats+quota+commands+flow_sockets en 1 roundtrip.
Shell reduce N×4 requests/probe a N×1 + 4 globales.
83 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 24, shipote-discern 5, yahweh-provider-fs 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- PipelineSpec.restart_backoff_ms + restart_max_backoff_ms + restart_max:
backoff exponencial entre relaunches (anti-thrash). take_pending_restarts
aplica restart_max (0 = infinito); excedido = supervisor descartado con
warning. Daemon hace tokio::sleep(backoff) antes del relaunch y escala
current_backoff x2 hasta el cap.
- shipote-shell card "Quota breaches": probe extiende con WorkspaceQuota
por workspace. Color rojo si hay breaches, verde si no.
- shipote logs --follow: poll cada 200ms al daemon, imprime suffix nuevo
hasta que el comando termine. Sin cambios al protocolo. Best-effort:
si el ring rota más rápido que el poll, se pierden bytes.
83 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 24, shipote-discern 5, yahweh-provider-fs 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- CPU% derivado server-side entre samples (WorkspaceState.last_cpu_sample).
100% = 1 core saturado. Primer sample devuelve None (sin baseline).
- shipote pipeline run --tail: tras lanzar, suscribe al primer flow_socket
y vuelca bytes hasta EOF. Auto-implica --tap.
- DiscernPolicy.replay_bytes: cap adicional por bytes para el replay
buffer del FlowChannel. evict_for_incoming considera el chunk entrante
para que post-push el buffer NUNCA exceda los caps.
- shipote-shell: stats history extiende sparkline con %CPU.
80 tests pasan (ente-incarnate 16, nouser-core 27, shipote-card 8,
shipote-core 21, shipote-discern 5, yahweh-provider-fs 3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pipeline runtime:
- Fan-out 1→N (splitter task replica al N consumers) y fan-in N→1 (merger
task con mpsc + reader-per-input). DAGs no lineales soportados.
- Flow channels: Unix socket + tokio broadcast con replay buffer
configurable por pipeline (DiscernPolicy.replay_chunks). Subscribers
externos vía `shipote flow tail <socket>`.
- Templating en specs con `${KEY}` (CLI `--var KEY=VALUE`). Walk
recursivo sobre serde_json::Value, soporta todos los strings del schema.
- Pipelines guardados (`pipeline save/saved-list/drop/run-saved`)
persisten con el snapshot.
Lifecycle de comandos:
- Log capture per-stream (stdout/stderr separados) via pipe O_CLOEXEC +
AsyncFd. CLI `shipote logs <ws> <cmd> --stream {stdout,stderr,both}`.
- Stop graceful con tiempo configurable: SIGTERM → grace → SIGKILL.
Tanto a nivel workspace como pipeline individual.
- TTL auto-stop ya existente (Fase C) sigue funcionando.
ente-incarnate:
- ChildStdio declarativo (Fase C) + ChildPreExec declarativo nuevo:
NoNewPrivs, ParentDeathSig, Dumpable, NewSession, Chdir, Umask.
- Aplicación pre-execve async-signal-safe en ambos paths (plain via
Command::pre_exec, namespaced via callback del clone(2)).
Observabilidad:
- WorkspaceStats: RSS + RSS peak (VmHWM o memory.peak cgroup) + CPU usec
+ uptime. Fuente per-proc o cgroup según delegation.
- shipote-shell con sparkline ASCII por workspace (history cap 24),
card de flow channels activos, vista de comandos + saved pipelines.
- Tap → broker: cada edge enriquecido con TypeRef se anuncia como Card
efímera vía SidecarPool (graceful si broker no corre).
Discern:
- Integrado en yahweh-provider-fs (mime_type en EntityNode).
- Integrado en nouser-core::cluster::pick_lens como fallback cuando la
extensión cae a Lens::Grid.
79 tests pasan: ente-incarnate (16), nouser-core (27), shipote-card (8),
shipote-core (20), shipote-discern (5), yahweh-provider-fs (3).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Iter 19. Patrón con 4 consumers idénticos: cada main() repetía el mismo
~20 líneas de boot (Application::new + Theme::install_default +
cx.open_window + WindowOptions + cx.activate). Sólo varían título,
tamaño y root factory.
crates/modules/ui_engine/libs/launcher/:
- pub fn launch_app(title, size, root_factory) → 1-line boot.
- pub fn launch_app_with(config, root_factory) → variante con config
armado afuera (env-var driven, etc).
- pub struct AppLaunchConfig::new(title, size).
- 2 tests cubren normalización del config.
Migración 4 consumers (nakui/nouser/minga/brahman-broker explorer):
- main() pasa de ~20 líneas a 1: launch_app(...).
- Imports gpui podados (no más App/Application/Bounds/WindowOpts/etc).
- Cada uno agrega dep yahweh-launcher.
Naming: yahweh-shell ya existe (bootstrap heavyweight con file/db/text
viewers en crates/apps/). Helper liviano queda como yahweh-launcher.
Ahorro ~75 líneas de boot hardcoded. Cambios de window/theme boot
ahora en un solo lugar.
2/2 tests launcher; 4 consumer suites intactas, todo verde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 17. Regresión surfaceada por verify_log_rejects_seed_after_schema_kcl_changes.
Bug: compute_schema_bundle_hash operaba sobre los bytes del bundle
compilado, que es `(import "/abs/path") & ...`. Esos bytes no cambian
cuando se edita el archivo apuntado; sólo cambian si se mueve el
módulo o se agregan/quitan schemas. El hash quedaba pegado y un seed
firmado con schema vN se verificaba ok contra schema vN+1.
Fix: nueva fn read_schema_files_concat que lee cada schema declarado
y los concatena con framing `\0NCL:<name>\0`. Esos bytes alimentan
los dos hashers (schema_bundle_hash y morphism_schema_hash). El bundle
compilado sigue siendo imports-style (Nickel necesita los paths para
resolver), sólo la fuente del hash cambia.
Impacto: logs versados con el binario anterior fallan SchemaMismatch
al verificarse — comportamiento correcto (re-seed).
Test renombrado: verify_log_rejects_seed_after_schema_kcl_changes →
_after_schema_changes (residuo de la migración KCL→Nickel).
10/10 schema_versioning verde.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 15. El patrón "tarjeta de dashboard con border-l accent +
label + value grande + descripción + listing opcional" tenía 2
consumers (minga-explorer + brahman-broker-explorer). Ahora vale
extraer al stack yahweh.
crates/modules/ui_engine/widgets/stat-card/:
- pub fn stat_card(cx, label, value: impl Into<SharedString>,
description, accent, text, text_dim, recent_items: &[String])
-> impl IntoElement.
- Compone yahweh-widget-card::card_themed; sin theme directo
(caller pasa text/text_dim ya resueltos).
- 3 tests #[gpui::test] con TestAppContext + theme: smoke con/sin
items, type-check value.
minga-explorer:
- Borra fn stat_card local (~60 líneas).
- Borra dep yahweh-widget-card.
- 3 callsites pasan value.to_string() (widget acepta
Into<SharedString>).
brahman-broker-explorer:
- fn state_card refactorizada como wrap del stat_card compartido,
preservando la traducción ProbeState→(accent,value,description)
como helper local app-specific.
- Borra dep yahweh-widget-card.
Sub-header del listing: pasa de "recent (N de TOTAL):" a
"recent (N):" — el widget no conoce TOTAL; el caller lo pone en
label si quiere ("Nodos AST (5 de 247)"). Trade-off aceptable
por reusabilidad genérica.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 13. El switcher ya cambiaba el chrome en runtime, pero al
cerrar/reabrir el theme volvía a Nebula default. Ahora se persiste
en $XDG_CONFIG_HOME/yahweh/theme (default ~/.config/yahweh/theme)
y se restaura al boot.
yahweh-theme:
- pub fn config_path() -> Option<PathBuf>: resuelve XDG; None en
sandbox/CI sin HOME ni XDG_CONFIG_HOME.
- pub fn load_persisted() / persist(theme): API pública.
Variantes load_from_path / persist_to_path con path explícito
para tests y apps custom.
- Theme::install_default(cx) ahora load_persisted o cae a Nebula.
- Theme::set(cx, theme) ahora persist + set_global. Best-effort:
io errors ignorados (no rebota la UX por un secundario).
- 5 tests nuevos: round-trip, missing file, unknown name, create
parent dir, XDG_CONFIG_HOME respetado.
API pública de install_default/set sin cambio de shape — todos los
downstream compilan sin tocar nada. Smoke run verificado.
Beneficio: 4 apps yahweh-themed comparten preferencia persistente.
Usuario puede preset via `echo Aurora > ~/.config/yahweh/theme`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iters 8-9 combinadas. Tres mejoras pequeñas que cierran la
integración del theme:
1) text-input caret blinking: caret_visible bool toggea cada 500ms
via cx.spawn loop. _blink_task se mantiene en self para que
drop cancele. render dibuja | sólo si focused && caret_visible.
2) yahweh-theme: 5 slots ornament secundario como methods (no
fields) derivados de is_dark via ornament_slots() helper:
bg_input/bg_button/bg_button_hover/accent_destructive/
bg_destructive_hover. No requiere modificar los 6 presets.
3) MetaApp ornament cleanup: 11 rgb(0x...) hardcoded → slots del
theme. Sidebar menu items, list row separator/buttons, icon ✕
delete y su hover, EntityRef selector hovers, form submit
button + fallback input bg, confirm modal hint y hovers.
Pattern: let X = theme.slot() antes de las closures + move |d|
d.bg(X) en hover/when para tomar ownership.
Antes MetaApp tenía la paleta principal themed (iter 5) pero el
ornament secundario seguía hardcoded. Ahora el theme switcher
cambia absolutamente todo el chrome.
Tests: 117 verdes. Downstream compila. Smoke nakui-ui: bootstrap
OK.
Limitación: nouser-explorer todavía hardcoded — próxima iter.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 7 (mini-iter — el text-input ya estaba themed, faltaba el
polish de focus visibility). Antes el border era siempre
accent_strong y el caret | siempre presente — imposible distinguir
cuál input está activo en un form con varios fields.
- Border focus-aware: focused → accent_strong; blur → border (slot
tenue del theme).
- Caret | sólo on focus; sin focus se muestra texto plano (reduce
ruido visual).
- render usa window.is_focused(focus_handle) para chequear.
Sin cambios en API pública. Tests downstream verdes.
Limitación: caret estático (no parpadea). Iter futura si emerge
la necesidad de animation timer via cx.spawn.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
crates/modules/ui_engine/widgets/theme-switcher/:
- pub fn theme_switcher(cx: &mut App) -> impl IntoElement: botón
clickable con bg=panel_alt, hover=row_hover, label "Tema: <name> ▸".
Al click hace Theme::set(cx, Theme::next_after(current.name)).
- 2 tests #[gpui::test]: smoke + verificación de cambio de global.
- Dev-dep gpui con test-support.
Migración:
- nakui-explorer: header pasa a flex_row con label flex_grow + switcher
alineado derecha.
- yahweh-widget-meta-form (sidebar): mismo pattern en el header
"Nakui" del sidebar.
Tests stack: 115 → 117.
Beneficio: click cambia toda la paleta en vivo. 6 presets disponibles
(Nebula, Aurora, Sunset, Flat Dark, Solarized Light, High Contrast)
ciclables circularmente.
Limitación: TextInput entities tienen colors hardcoded; migrar
text_input al theme es iter futura.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 5 de integración. MetaApp::render tenía 7 vars con colors
hardcoded (bg/panel/border/text/text_dim/accent/accent_active);
ahora salen de Theme::global(cx) que el shell instala al boot.
confirm_delete_banner usa themed_colors(Warning/Error) para sus
colors base.
render:
- 7 let X = rgb(0x...) → theme.bg_app/bg_panel/border/fg_text/
fg_muted/accent/accent_strong.
- toast_div / error_banner: banner() → banner_themed(cx).
Firmas internas:
- render_sidebar/main/list/entity_ref_selector/form cambian
Rgba → Hsla (Background donde aplica para panel). gpui::Div
acepta ambos via Into, uso interno no cambia.
confirm_delete_banner:
- 6 colors hardcoded → themed_colors(Warning) para banner base,
themed_colors(Error) para Confirm, theme.bg_panel_alt+fg_text
para Cancel.
NO migra esta iter (ornament hardcoded para una pasada futura):
row hovers, bordes sutiles entre filas, bg de inputs custom,
bg de botones del EntityRef selector, color del icon ✕ de delete.
Smoke test del binario verificado: bootstrap completo OK, panic
esperado en open_window sin display. 115 tests verdes (sin
cambio: los tests del widget no acceden al render).
Beneficio: theme switcher (cuando llegue) cambia toda la paleta
con 1 sola llamada Theme::set(cx, ...).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Iter 3 de 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.
crates/modules/ui_engine/widgets/card/:
- pub fn card() -> Div: flex_col + px(12) + py(8) + mb(4) +
rounded(4) + gap(2). Sin colores (caller decide via builder).
- 1 test smoke.
nakui-explorer:
- Los 2 timeline entry patterns (Seed/Morphism) pasan de ~7
calls a ~3, intención "card with accent" emerge del nombre.
Tests stack: 111 → 112. App-agnostic — el widget no impone paleta,
permite themes diversos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Patrón visual común a yahweh-widget-meta-form (toast success +
error banner) y nakui-explorer (error banner): div con bg + text
colored según severidad. Antes duplicado con colores hardcoded en
cada consumer.
Crate nuevo crates/modules/ui_engine/widgets/banner:
- pub enum Banner { Info, Success, Warning, Error } con bg()/fg()
hardcoded por variant.
- pub fn banner(kind, message) -> Div: padding/text_size defaults;
caller compone con .child()/.px()/.on_click()/etc.
- 2 tests sanity (no color collisions).
Migración:
- yahweh-widget-meta-form: 12 líneas hardcoded → 2 llamadas a
banner().
- nakui-explorer: error banner usa banner() + override de
padding custom del header.
Tests stack: 109 → 111 (+2). Cada crate compila individualmente.
Próximo natural: confirm_delete_banner (Warning + botones) puede
extraerse como modal-banner cuando emerja segundo consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cierra el ciclo de testabilidad del widget metainterfaz. Hasta
ahora los tests del MetaBackend trait vivían como impl privada en
backend.rs; el widget no podía testear handlers sin levantar
NakuiBackend (que depende de event log + Rhai).
yahweh-meta-runtime:
- Nuevo `pub mod testing` con MockBackend (renombre del MemBackend
privado, ahora público). Constructores: new(), with_records(iter),
with_morphism(name, handler) builder. Métodos de inspección
total_records / records_for. Bajo `pub mod testing` (no cfg test)
para que crates downstream lo usen en sus dev tests.
- Tests del trait en backend.rs simplificados: usan MockBackend en
vez del MemBackend duplicado. 8 backend.rs + 9 nuevos del mock.
yahweh-widget-meta-form:
- Dev-dep nueva: gpui con feature "test-support" (TestAppContext).
- MetaApp::apply_action ahora pub (era privado). Necesario para
invocar handlers desde tests E2E.
- Nuevo tests/widget_with_mock_backend.rs con 4 tests #[gpui::test]:
meta_app_constructs, open_view_action_does_not_panic,
backend_state_visible_from_widget_perspective,
morphism_handler_can_be_registered_and_called_via_widget.
Tests: 47→56 yahweh-meta-runtime, 3→7 yahweh-widget-meta-form.
Total stack 109 verdes.
Limitación: render() no se invoca (requiere window context más
rico). Tests verifican state machine, no pixels. Snapshot tests
serían scope futuro.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continúa la integración de apps nakui al stack yahweh. Los
helpers visuales que nakui-explorer tenía locales y son reusables
suben a yahweh-meta-runtime/format.
yahweh-meta-runtime:
- short_hash(h: &[u8; 32]) -> String: hex de los primeros 4 bytes.
- preview_value(v: &Value, max: usize) -> String: JSON one-liner
truncado con "..." (edge case max < 3 sin panic).
- 5 tests nuevos.
nakui-explorer:
- Nueva dep yahweh-meta-runtime.
- Borrado helpers locales (short_uuid + short_hash + preview_value)
+ 4 tests duplicados.
- Imports desde yahweh-meta-runtime.
Tests: 42→47 yahweh-meta-runtime, 7→3 nakui-explorer (los 3 que
quedan son específicos del explorer: load_log, breakdown,
missing_file). Resto intacto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cierra el plan original. El motor de validación de entities deja
de shellear el binario externo `kcl` y pasa a evaluar Nickel
contracts in-process via la dep nickel-lang (la misma que usa el
brazo de cards). Los 3 schemas de sales/inventory/treasury migran
de .k a .ncl.
nakui-core:
- Nueva dep nickel-lang = "2.0.0".
- Borrado kcl_wrapper.rs.
- Nuevo nickel_validator.rs con vet(schema_path, state, entity)
que evalúa `let bundle = (import "<schema>") in
(std.deserialize 'Json m%%"<json>"%%) | bundle.<entity>`.
- executor.rs: KclError → NickelError, KclPre/Post/PostCreate →
SchemaPre/Post/PostCreate, kcl_check → validate_entity.
build_schema_bundle ahora emite `(import "X") & (import "Y") & ...`
en lugar de concatenar bytes (cada .ncl es expresión completa).
- manifest.rs: default schema "schema.ncl", extract_schema_names
reescrito para sintaxis Nickel record (CapitalCase keys con
2-space indent).
Schemas migrados:
- sales/schema.ncl: Venta con std.contract.Sequence [record,
from_predicate] para combinar shape + invariante cross-field
(total == cantidad * precio_unitario). El patrón directo
`record | from_predicate` rebota con "missing definition" porque
el predicate evalúa antes de que el value populate el record;
documentado en cada schema.
- inventory/schema.ncl, treasury/schema.ncl: idem.
- 3 schema.k viejos borrados; sales/nsmc.json paths actualizados.
Tests: refs Kcl* renombradas; paths .k → .ncl; tests inline que
escribían schema.k cambian a schema.ncl con sintaxis Nickel.
84 tests verdes en nakui-core.
Doc-only borrados:
- crates/core/ente-card/schema/card.k (REFERENCE ONLY).
- crates/core/ente-brain/schema/rule.k (REFERENCE ONLY).
Beneficios: sin dep externa al binario `kcl` (build CI limpio),
errores Nickel en línea con caret pointing al field, mismo motor
que cards (una dep para todo el repo), sin tempfile JSON
intermedio.
Cierra el plan original yahweh + KCL + card.k. Pendientes salen
de nuevo trabajo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cierra el refactor de UI. El widget render (forms, lists, modal de
delete, EntityRef selector, sidebar, key handlers) deja de vivir en
nakui-ui y pasa a un crate yahweh nuevo, genérico sobre MetaBackend.
crates/modules/ui_engine/widgets/meta-form/ (yahweh-widget-meta-form):
- pub MetaApp<B: MetaBackend> con todo el state UI + impl Render
+ handlers + render_*. El bound `B: MetaBackend` se propaga.
- pub fn new(modules, backend, initial_toast, initial_error, cx):
constructor sin bootstrap. Caller pre-construye todo.
- Helpers locales del widget: lookup_field, append_compact_msg,
format_seed_toast.
- Cero deps a nakui o brahman-cards. Reusable por cualquier app.
- 3 tests funcionales puros (lookup_field, append_compact_msg,
format_seed_toast).
nakui-ui (shell):
- main.rs: 1959 → 424 líneas (78% reducción). Sólo bootstrap:
load_ui_modules + load executors + NakuiBackend::open +
cx.open_window con MetaApp::<NakuiBackend>::new como root view.
- Dep nueva yahweh-widget-meta-form.
- Tests E2E quedan: event_log_replay, morphism_pipeline real
sales, load_ui_modules x3 (4 shell). NakuiBackend tests siguen
en backend.rs (8). Widget tests en su crate.
Distribución total tests refactor yahweh:
- yahweh-meta-schema: 8
- yahweh-meta-runtime: 42
- yahweh-widget-meta-form: 3
- brahman-cards: 26
- nakui-ui: 12 (4 shell + 8 backend)
Total: 91 tests cubriendo el área.
Cada crate compila individualmente. Fase 3 (shell mínimo) era
implícita en F2c — el shell ya quedó en 424 líneas.
Pendientes restantes: KCL → Nickel, eliminar card.k.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 steps en un commit:
A) yahweh-meta-runtime/backend.rs: trait MetaBackend con 6 métodos
(list_records, load_record, seed, update, delete, morphism) +
WriteOutcome { id, changed, post_status }. 9 tests con MemBackend.
B) nakui-ui/backend.rs: NakuiBackend struct con store/log/executors/
compaction. NakuiBackend::open() compone log+snapshot+replay+tick;
impl MetaBackend mapea cada método al pipeline nakui-core.
snapshot_path_for / maybe_compact_log se mueven acá. 7 tests del
impl.
C) MetaUi consume el backend:
- 6 fields colapsan en `backend: NakuiBackend`.
- MetaUi::new pasa de ~150 líneas a ~10 (delega a NakuiBackend::open).
- commit_seed / commit_morphism / commit_delete delegan al trait;
CommitOutcome enum eliminado, reemplazado por WriteOutcome.
- tick_runtime_compact eliminado (interno al backend; el msg sale
por WriteOutcome.post_status).
- validate_entity_refs callsite usa cierre sobre backend.load_record.
- Imports nakui_core::delta y event_log salen de main.rs (sólo
quedan en tests E2E).
Tests: 33→42 yahweh-meta-runtime (+9 trait), 14→21 nakui-ui (+7
backend impl). 97 totales en el área. Cada crate compila individualmente.
Pendiente Fase 2c: extraer widget render (form/list/modal/EntityRef)
al crate yahweh — ahora trivial porque el render solo consume
&self.modules + self.backend (via trait).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sigue de la Fase 1 (lift del schema). Ahora los helpers puros que
cualquier widget renderer o backend ejecutor consume sobre el schema
viven en yahweh-meta-runtime. Sin GPUI, sin nakui — usa cierres en
lugar de traits para decoupling máximo.
Crate nuevo crates/modules/ui_engine/libs/meta-runtime:
- parse.rs: parse_field_value, infer_param_value, resolve_param_value.
- delta.rs: compute_field_delta, compute_clear_fields.
- refs.rs: validate_entity_refs(load: F, refs) con cierre
Fn(&str, Uuid) -> Option<Value> en vez de trait Store.
- format.rs: human_label_for_record, render_value, value_to_input_text,
short_uuid.
- 33 tests propios.
nakui-ui:
- Nueva dep yahweh-meta-runtime.
- Borrado código local equivalente (~200 líneas) + 34 tests
duplicados.
- validate_entity_refs callsite usa cierre:
validate_entity_refs(|e, id| store.load(e, id), &refs).
- 14 tests runtime-específicos quedan (compact/snapshot/event-log/
morphism pipeline/load_ui_modules).
Distribución tests: 48 → 14 nakui-ui; +33 yahweh-meta-runtime.
Cada crate afectado builds + tests limpio individualmente. Workspace
build full no completó esta corrida por OOM al compilar
surrealdb-core (ambiental, no relacionado).
Fase 2b pendiente: extraer render widgets (form/list/modal/
EntityRef selector) a yahweh — requiere diseñar MetaBackend trait.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Primer paso del refactor yahweh. El schema de UI declarativa no
tiene acoplamiento real con Nakui (sólo dep en serde/thiserror) —
movemos a yahweh para que cualquier app metadata-driven lo use sin
pasar por nakui.
Mecánico:
- git mv crates/modules/nakui/ui-schema → crates/modules/ui_engine/libs/meta-schema.
- Crate name: nakui-ui-schema → yahweh-meta-schema.
- Workspace members[] actualizado (sección yahweh, no nakui).
- Consumers actualizados: brahman-cards (Cargo.toml + lib.rs +
readers.rs), nakui-ui (Cargo.toml + main.rs).
- Self-test (example_modules.rs): import + path rebase (5 niveles
arriba ahora).
Documental:
- Doc del crate ahora dice "metainterfaz (yahweh meta-schema)" +
"backend-agnostic" en filosofía.
- Module.nakui_module_dir documentado como "path opaco al backend";
se conserva el nombre por compat con módulos ya escritos +
serde alias "backend_module_dir" para futuro rename suave.
Tests: 13 yahweh-meta-schema + 26 brahman-cards + 48 nakui-ui
verdes. Workspace build verde.
NO hace Fase 1: mover widgets a yahweh (Fase 2), trait MetaBackend
(Fase 3), renombrar nakui_module_dir.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Nueva variante semántica del kernel: Clear { path } remueve la key
del record, distinta de Set { value: Null } (que deja la key con
valor literal null). Habilita "limpiar" un field optional vaciando
el input en la UI.
nakui-core:
- delta::FieldOp::Clear + simulate_on + capability_token (mismo
shape que Set: "entity.field").
- MemoryStore::apply_dry_run y apply: Set/Clear comparten
pre-condition (record existe + es objeto). Clear de field
ausente = no-op silencioso.
- SurrealStore: equivalente con `UPDATE ... UNSET <field>`.
- Executor capability check: Set/Clear comparten match.
- Conservation rules NO consideran Clear (sólo Set) — documentado
como morphism-author responsibility.
nakui-ui:
- commit_seed acumula `to_clear: Vec<String>` con optional empties
en lugar de `continue` silencioso.
- EDIT branch: nuevo helper compute_clear_fields filtra a sólo los
fields con current value non-null. Combina Set + Clear ops.
NoChange ahora requiere ambos vacíos. Log entry incluye
`cleared: [...]` sólo si non-empty. Updated.changed cuenta
sets+clears.
Tests: +7 en nakui-core (4 store + 3 delta), +3 en nakui-ui.
Suites: 34/34 nakui-core, 40/40 nakui-ui. Workspace build verde.
E2E del morphism real intacto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cierra el principal trade-off documentado del commit anterior:
"Inputs UUID a mano (no dropdown)". Los formularios pueden declarar
un campo entity_ref que apunta a una entity y el runtime renderea
una lista clickable de records existentes; click selecciona, el UUID
queda guardado para el submit.
Schema:
- Nueva variante FieldKind::EntityRef (serializa como "entity_ref").
- FieldSpec.ref_entity: Option<String> nuevo. validate() chequea que
cualquier field con kind=entity_ref tenga ref_entity set.
- Nuevo SchemaError::EntityRefMissingTarget.
Runtime:
- render_entity_ref_selector helper: lista clickable debajo del input,
cada item con etiqueta humana (heuristica: name > label > title >
sku > sku_id > UUID corto) y click handler via cx.listener que
setea el TextInput con el UUID completo. Highlight en accent color
para el seleccionado.
- parse_field_value(EntityRef) devuelve string raw — validacion como
Uuid es responsabilidad de commit_morphism downstream.
- Mensaje "(sin {entity}: crea uno antes...)" cuando lista vacia —
el user sabe que hacer.
Demo actualizado sales_engine: vender_form.stock_id_input y
caja_id_input cambian a kind=entity_ref. Flujo nuevo: click en Stock
listado bajo input, click en Caja, escribir venta_id/cantidad/precio/
timestamp, submit. Sin copiar UUIDs.
Tests: 2 nuevos schema (validate detecta EntityRef sin ref_entity y
acepta con ref_entity) + 4 nuevos runtime (parse, human_label cubre
todos los key fallbacks). 29 tests totales (16 + 8 + 5).
Pendientes: confirmacion de delete, snapshot/compaction del log,
edit delta-only, validacion estricta de params del morphism via
FieldKind del FieldSpec en lugar de infer_param_value.
Cierra el ultimo gran TODO de la metainterfaz Nakui: las acciones
Action::Morphism ya no son un toast informativo; despachan al
Executor cargado del manifest nakui-core (nsmc.json + schemas KCL +
scripts Rhai), pasando por el pipeline completo: compute (con
dry-run + KCL post-checks) -> log append -> store apply.
Schema nakui-ui-schema extendido:
- Module.nakui_module_dir: Option<String> nuevo. Path al modulo
nakui-core. Sin esto, Action::Morphism quedan no-op con toast.
SeedEntity sigue funcionando (alta administrativa sin manifest).
- Action::Morphism gano dos campos opcionales:
- inputs: BTreeMap<String, String> — mapeo role -> field_name.
- params: Vec<String> — fields cuyos values van al params JSON.
Si vacio, todos los fields no-input van a params.
Runtime nakui-ui:
- MetaUi.executors: BTreeMap<String, Arc<Executor>> nuevo. Carga
Executor::load_module(nakui_module_dir) en MetaUi::new.
- commit_morphism: resuelve inputs (parsea UUIDs), arma params
(Value object con tipos inferidos), llama
execute_and_log_with_recovery. Toast con count de ops o error.
- infer_param_value: heuristica i64 -> f64 -> bool -> string.
Tests: 2 nuevos. E2E morphism_pipeline_executes_real_sales_vender
carga el modulo real crates/modules/nakui/modules/sales, ejecuta
"vender" con inputs Stock+Caja y params (cantidad=5, precio=200,
venta_id, timestamp). Asserta:
- el morphism produce ops (no vacio).
- stock.cantidad: 100 -> 95.
- caja.saldo: 1_000_000 -> 1_001_000.
12 tests verdes en nakui-ui (+1). Schema extension no rompio nada
(6 unit + 5 integration siguen verdes).
Demo nuevo: examples/nakui-modules/sales_engine/module.json apunta
al sales real via nakui_module_dir. 6 vistas (list+form para Stock/
Caja/Venta + "Vender" con Action::Morphism). El user crea Stocks +
Cajas con seed_entity, copia los UUIDs a los inputs de "Vender", y
ejecuta el morphism real con KCL post-checks.
Activacion:
NAKUI_EVENT_LOG=~/.nakui/state.jsonl \\
NAKUI_MODULES_DIR=examples/nakui-modules \\
cargo run -p nakui-ui
Trade-offs:
- Inputs UUID a mano (no dropdown). Nice-to-have: FieldKind::EntityRef
que renderee selector.
- Inferencia de tipo en params es heuristica.
Salto cualitativo: Nakui pasa de "library + demos + read-only viewer
del event log" a plataforma ERP con UI dirigida por datos. Cada
modulo de negocio se declara como un module.json (sin codigo Rust
nuevo) y el runtime GPUI lo carga dinamicamente: sidebar de menus,
listas con columnas configurables, formularios de alta.
3 entregables:
1. Crate nakui-ui-schema (datos puros): Module, View::List/Form,
FieldSpec con FieldKind {Text|Multiline|Number|Boolean|Date},
Action {OpenView|SeedEntity|Morphism}. Module::from_path,
Module::validate, load_modules_from_dir(dir). 6 tests unit + 4
integration.
2. Crate nakui-ui (binario GPUI): carga modulos desde
NAKUI_MODULES_DIR. Sidebar + main panel. List view con tabla
weighted; form view con campos labeled + submit que ejecuta
SeedEntity contra MemoryStore in-process compartido. Toast +
error banner. 6 tests unit.
3. 6 modulos demo en examples/nakui-modules/:
- customers (nombre, email, telefono, credito, notas)
- products (SKU, nombre, categoria, precio, stock)
- suppliers (razon social, ID fiscal, contacto, terminos pago)
- inventory_movements (fecha, tipo, SKU, cantidad, costo, motivo)
- sales_orders (numero, cliente, fechas, estado, totales)
- invoices (numero, cliente, fechas, totales, pagado, moneda)
Filosofia: UI como datos. Persistencia universal (MemoryStore hoy,
SurrealStore manana, sin tocar module.json). Schema primero, semantica
despues.
Activacion:
NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui
Limitaciones conocidas (proximos iters):
- Inputs sin teclado (GPUI no lo trae nativo; integrar
yahweh-widget-text-input).
- Click handlers no propagan mutacion al estado (refactor con
cx.listener pendiente).
- Action::Morphism queda como TODO hasta cargar Manifest junto al
Module.
- Sin persistencia entre runs (wire con EventLog/SurrealStore para
cuando el daemon Nakui exista).
Tests: 16 totales nuevos. Lo que esto desbloquea: cualquiera puede
escribir un module.json para su dominio (pacientes, alumnos,
reservaciones) y aparece en la UI sin recompilar.