PERFORM A THRU C ejecuta A, B y C; antes el transpilador sólo
ejecutaba A (lo marcaba como aproximado).
- charka-codegen: Symbols registra ahora los párrafos en orden con su
nombre de método; Symbols::build toma el Ir completo.
paragraph_range(name, thru) da los métodos del rango; emit_perform
emite la llamada a cada uno.
- charka-shadow: run_paragraph_range ejecuta los párrafos de name a
thru inclusive.
- Corpus: programa nuevo 17-rangopar (PERFORM PASO-A THRU PASO-C sobre
tres párrafos). Verificado: el intérprete sombra y el crate
compilado por scaffold dan la misma salida.
Tests: charka-codegen 24, charka-shadow 22. fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
La cara de escritura de los nombres de condición de COBOL: si
IF ES-VALIDO los lee, SET ES-VALIDO TO TRUE los escribe.
- IR: Stmt::SetTrue { conditions }.
- Parser: SET cond-1 cond-2 ... TO TRUE. Otras formas de SET
(índices, TO FALSE) caen a Stmt::Unknown.
- Codegen y shadow: SET cond TO TRUE asigna a su dato padre el valor
del 88 (un MOVE del valor a la variable).
- Corpus: programa nuevo 16-bandera (cambia banderas de texto y de
número con SET). Verificado: el intérprete sombra y el crate
compilado por scaffold dan la misma salida.
Tests: charka-ir 29, charka-codegen 23, charka-shadow 21. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El verbo de COBOL para volver un dato (o un registro entero) a su
valor por defecto.
- IR: Stmt::Initialize { targets }. El model de charka-ir registra
ahora los grupos y sus datos elementales (DataModel::groups,
GroupInfo { name, members }).
- Parser: INITIALIZE name-1 name-2 ...
- Codegen y shadow: cada destino, si es un grupo, se expande a sus
miembros; cada dato elemental se pone a 0 (numérico) o a espacios
(alfanumérico); una tabla OCCURS resetea todos sus elementos.
- Corpus: programa nuevo 15-resetear (resetea un grupo y un escalar).
Verificado: el intérprete sombra y el crate compilado por scaffold
dan la misma salida.
Tests: charka-ir 28, charka-codegen 22, charka-shadow 20. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Completa el EVALUATE con sus dos formas que faltaban.
- IR: la rama WhenBranch pasa de values: Vec<Operand> a
tests: Vec<WhenTest>, donde WhenTest es Value (igualdad), Range
(WHEN lo THRU hi) o Cond (EVALUATE TRUE WHEN cond).
- Parser: detecta EVALUATE TRUE y entonces cada WHEN parsea una
condición; en modo valor reconoce WHEN lo THRU hi.
- Codegen y shadow: una prueba Range se traduce a lo <= s <= hi; una
Cond, a la condición directa.
- Corpus: programa nuevo 14-clasifica (clasifica notas con rangos THRU
y un EVALUATE TRUE). Verificado: intérprete sombra y crate compilado
dan la misma salida.
Tests: charka-ir 27, charka-codegen 21, charka-shadow 19. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El verbo de COBOL para analizar y limpiar campos de texto.
- IR: Stmt::Inspect { target, op } con InspectOp::TallyingForAll
(cuenta apariciones y las suma a un contador) y
InspectOp::ReplacingAll (reemplaza apariciones).
- Parser: INSPECT t TALLYING n FOR ALL lit y
INSPECT t REPLACING ALL a BY b. Una forma no soportada cae a
Stmt::Unknown.
- Codegen: TALLYING -> str::matches(..).count(); REPLACING ->
str::replace.
- Shadow: el intérprete cuenta / reemplaza el texto.
- Corpus: programa nuevo 13-inspeccion. Verificado: el intérprete
sombra y el crate compilado por scaffold dan la misma salida.
Alcance v1: TALLYING FOR ALL y REPLACING ALL; sin LEADING, FIRST,
CHARACTERS, BEFORE/AFTER.
Tests: charka-ir 26, charka-codegen 20, charka-shadow 18. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dos verbos comunes de COBOL para construir y partir cadenas.
- IR: Stmt::StringConcat { sources, into } y
Stmt::Unstring { source, delimiter, into }.
- Parser: STRING a b DELIMITED BY SIZE INTO t END-STRING y
UNSTRING s DELIMITED BY d INTO a b c END-UNSTRING.
- Codegen: STRING -> format! concatenado; UNSTRING -> un bloque que
parte con str::split y reparte los trozos a los destinos.
- Shadow: el intérprete concatena / parte el texto y lo reparte.
- Corpus: programa nuevo 12-cadenas. Verificado: el intérprete sombra
y el crate compilado por scaffold dan la misma salida.
Alcance v1: STRING con DELIMITED BY SIZE (otros delimitadores se
ignoran); sin WITH POINTER ni ON OVERFLOW.
Tests: charka-ir 25, charka-codegen 19, charka-shadow 17. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Los arrays de COBOL, que antes el transpilador descartaba en silencio.
Una rebanada vertical amplia que atraviesa el pipeline entero.
- Parser: la cláusula OCCURS n [TIMES] se captura en DataItem.
- IR: Operand::Indexed { name, index } — una referencia ELEM(I), con
subíndice 1-based. Los destinos de los statements pasan de
Vec<String> a Vec<Operand>, así que se puede escribir a un elemento
de tabla (MOVE x TO ELEM(I), COMPUTE ELEM(I) = ...). model::Field
gana occurs: Option<u32>.
- Codegen: un campo OCCURS se emite como Vec<Num>/Vec<Text>,
inicializado con vec![..; n]; una referencia con subíndice indexa el
vector (1-based -> 0-based).
- Shadow: en el intérprete todo campo es un vector — un escalar es de
longitud 1, una tabla de n; las referencias se resuelven a
(nombre, índice).
- Corpus: programa nuevo 11-tabla (llena una tabla con cuadrados y los
suma). Verificado: el intérprete sombra y el crate compilado por
scaffold dan ambos SUMA DE CUADRADOS = 000055.
Alcance v1: OCCURS elemental, una dimensión, subíndice de un operando.
Fuera: OCCURS de grupo, multidimensional, DEPENDING ON.
Tests: charka-parser 16, charka-ir 24, charka-codegen 18,
charka-shadow 16. fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Los nombres de condición de COBOL (IF ES-VALIDO), que antes el
transpilador evaluaba siempre como false. Y, de paso, se elimina la
duplicación de la resolución del modelo de datos.
- charka-ir gana un módulo `model`: resolve_data(&[DataItem]) ->
DataModel aplana el árbol de datos a campos elementales (Field con
FieldKind) y a nombres de condición (ConditionName). El Ir lleva
ahora un campo `model` — la fuente única de verdad sobre la
clasificación de PICTURE.
- charka-codegen y charka-shadow consumen ir.model en vez de
reimplementar cada uno la clasificación, el ancho de PICTURE y la
normalización de VALUE. charka-codegen ya no depende de charka-bcd.
- Cond::Named (un nivel 88) se resuelve a `padre = valor`: el codegen
emite la comparación, el intérprete sombra la evalúa.
- Corregido: un dato con hijos de nivel 88 antes se perdía como si
fuera un grupo; ahora se reconoce como campo elemental.
- Corpus: programa nuevo 10-condicion (semáforo con 88 de texto y de
número). Verificado: intérprete y crate compilado dan igual salida.
Tests: charka-ir 23, charka-codegen 17, charka-shadow 15. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
EVALUATE atraviesa el pipeline entero — antes el parser lo guardaba
crudo como Stmt::Unknown.
- IR: Stmt::Evaluate { subject, whens, other } con
WhenBranch { values, body }. Varios WHEN apilados comparten cuerpo;
WHEN OTHER es el caso por defecto.
- Parser: EVALUATE subject WHEN v1 WHEN v2 ... [WHEN OTHER ...]
END-EVALUATE.
- Codegen: lo baja a una cadena if / else if / else — una rama se
elige si el sujeto es igual a alguno de sus valores, sin caída.
- Shadow: el intérprete evalúa el sujeto y ejecuta la primera rama
cuyos valores casen, o el WHEN OTHER.
- Corpus: programa nuevo 09-evaluar (EVALUATE por valor anidado en un
PERFORM VARYING, con WHEN apilados y WHEN OTHER). Verificado: el
intérprete sombra y el crate compilado por scaffold dan la misma
salida.
Alcance v1: EVALUATE por igualdad de valor; no la forma EVALUATE TRUE
con condiciones ni los rangos THRU.
Tests: charka-ir 19, charka-codegen 16, charka-shadow 14. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El bucle más usado de COBOL, que antes el parser degradaba a un
PERFORM vacío (un hueco de corrección real). Ahora atraviesa el
pipeline entero como una rebanada vertical.
- IR: PerformControl::Varying { var, from, by, until }.
- Parser: reconoce PERFORM VARYING var FROM x BY y UNTIL cond en
línea (END-PERFORM) y fuera de línea (PERFORM párrafo VARYING ...).
- Codegen: emite var = from; while !(until) { cuerpo; var += by; }.
- Shadow: el intérprete inicializa la variable, evalúa la condición
antes de cada vuelta e incrementa al final.
- Corpus: programa nuevo 08-varying (suma 1..10). Verificado: el
intérprete sombra y el crate compilado por scaffold dan ambos
SUMA 1 A 10 = 00055 — las dos rutas concuerdan.
Tests: charka-ir 18, charka-codegen 15, charka-shadow 13. fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
App nueva crates/apps/charka — el binario `charka`, que vuelve usable
el pipeline COBOL->Rust desde la terminal.
- transpile <in.cob> [-o out.rs] — emite el código Rust.
- scaffold <in.cob> -o <dir> — genera un crate Rust completo
(Cargo.toml + src/main.rs) que depende de charka-runtime y compila.
- run <in.cob> — ejecuta el programa con el intérprete sombra, sin
compilar nada, y muestra su salida.
- check <in.cob> -e <esperado> — ejecuta y diferencia contra una
salida esperada; reporta las líneas que difieren.
Avisa de los verbos COBOL que aún no se transpilan. Verificado de
punta a punta contra el corpus: scaffold de 06-nomina genera un crate
que compila y produce la misma salida que el intérprete sombra — las
dos rutas de ejecución concuerdan.
4 tests; fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El pipeline COBOL->Rust queda completo (7 crates) y validado de punta
a punta.
charka-shadow certifica que el transpilador preserva la semántica del
COBOL original con una ejecución sombra: un intérprete que corre el Ir
directamente sobre charka-runtime, sin compilar nada. Es una segunda
ruta de ejecución, independiente del código que emite charka-codegen
— si la sombra y el transpilado divergieran, sería un bug.
- interpret(&Ir) -> Outcome ejecuta el IR y captura las líneas de
DISPLAY; run_source(&str) corre el pipeline completo.
- Tope de pasos (Halt::StepLimit): un bucle que no termina se corta
en vez de colgarse.
- Módulos: field (datos -> campos vivos) / interp (el motor).
Corpus nuevo crates/modules/charka/corpus/ — 7 programas COBOL de
complejidad graduada (01-hola .. 07-clasificar) con sus salidas
esperadas verificadas a mano: DISPLAY, aritmética con GIVING,
IF/ELSE, PERFORM TIMES/UNTIL, grupos, COMPUTE con paréntesis,
ROUNDED, IF anidado con AND. Material de prueba del pipeline entero.
11 tests (los 7 del corpus + fuente vacío, STOP RUN, tope de pasos,
error de léxico); fmt + clippy limpios.
No hay GnuCOBOL en la máquina: la referencia v1 es el corpus; un modo
futuro diferenciará contra el compilador real.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
La etapa final del transpilador. generate(&Ir) -> String produce un
fuente Rust (un main.rs) que, compilado contra charka-runtime, ejecuta
la lógica del programa COBOL.
- struct Program con un campo Num/Text por dato elemental; new() lo
inicializa desde las cláusulas VALUE.
- Un método p_<párrafo> por párrafo del PROCEDURE; run() los encadena
en orden (el «caer» de COBOL); main() construye y corre.
- Cada Stmt -> código Rust: MOVE->.store/.fill, DISPLAY->println!,
COMPUTE y aritmética -> expresiones Decimal, IF->if/else,
PERFORM-> llamada / for / while, STOP RUN->process::exit.
- Tolerante: lo no transpilable (Stmt::Unknown, dato sin resolver, **)
se emite como comentario // charka: — el código generado compila.
- Saneado de identificadores COBOL->Rust (choques con keywords).
- Verificado de punta a punta: un programa COBOL demo transpila a Rust
que compila contra charka-runtime y produce la salida esperada.
- Módulos: emit / sym / expr / stmt. 14 tests; fmt + clippy limpios.
El pipeline COBOL->Rust corre de punta a punta. Falta sólo
charka-shadow (validador en sombra).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El soporte que los programas COBOL transpilados enlazan. charka-codegen
emitirá Rust que llama a esta biblioteca, no Rust autónomo.
- Num: campo numérico (PIC 9(5)V99) — un Decimal conformado a su
Picture. store trunca a la escala declarada, store_rounded redondea;
al desbordar la parte entera conserva los dígitos de bajo orden (el
ON SIZE ERROR de COBOL sin cláusula). display da los dígitos con
relleno de ceros y signo.
- Text: campo alfanumérico (PIC X(n)) de longitud fija — store
justifica a la izquierda y rellena/trunca; fill mueve figurativas.
- cobol_text_cmp: comparación alfanumérica con relleno de espacios.
- Reexporta Decimal/Picture/Rounding de charka-bcd.
Construido antes que charka-codegen (la nota de orden del plan los
listaba al revés): el codegen emite contra esta API. 17 tests; fmt +
clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tercera etapa del transpilador: Program -> Ir. El PROCEDURE division
pasa de sentencias con tokens crudos a un árbol de instrucciones
tipadas.
- lower(&Program) -> Ir: total y tolerante, nunca falla. La DATA
division pasa tal cual y sirve de tabla de símbolos.
- Stmt cubre MOVE, DISPLAY, ACCEPT, COMPUTE, ADD, SUBTRACT, MULTIPLY,
DIVIDE, IF/ELSE/END-IF, PERFORM (fuera de línea, en línea, TIMES,
UNTIL), GO TO, STOP RUN, GOBACK, EXIT, CONTINUE.
- Expresiones de COMPUTE con precedencia y paréntesis (Pratt).
Condiciones con comparadores símbolo/palabra, AND/OR/NOT y nombres
de condición (nivel 88).
- Delimita statements por palabras frontera (COBOL no los separa con
un símbolo). Verbo no soportado -> Stmt::Unknown con tokens crudos.
- Módulos: ast / kw / cursor / expr / stmt. 17 tests; fmt + clippy
limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Segunda etapa del transpilador: Vec<Token> -> Program. Alcance v1 = el
esqueleto del programa.
- parse(&[Token]) -> Result<Program, ParseError>. AST: Program
(program_id, data, paragraphs), DataItem, Paragraph, Sentence.
- Particiona el flujo en las 4 divisions por sus encabezados; extrae el
PROGRAM-ID de la IDENTIFICATION.
- DATA division -> árbol de DataItem: nivel, nombre, PICTURE
reensamblado (S9 ( 5 ) V99 -> S9(5)V99) y VALUE. Anida por número de
nivel (01/77 raíces, 88 cuelga del precedente).
- PROCEDURE division -> Vec<Paragraph> con Sentence de tokens crudos
(sin parseo de statement). Sentencias previas al primer encabezado
van a un párrafo implícito "".
- Tolerante: salta SECTION, FD/SD y cláusulas que no sean PIC/VALUE.
- 15 tests verdes; fmt + clippy limpios.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primera etapa del transpilador COBOL→Rust (Fase D del plan macro):
texto COBOL → secuencia de Token. Lexer deliberadamente tonto (emite
Word para todo identificador, la clasificación es del parser). Tokens
Word/Number/String/Period/Symbol con línea+columna; soporta formato
fijo (tarjeta de 80 columnas) y libre; comentarios, comillas dobladas,
operadores de 1 y 2 caracteres. LexError tipado. 17 tests; clippy
limpio. Limitación v1: sin continuación de literales entre líneas.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Validación inline: al fallar un submit por campos required vacíos, el
form los marca (label destructivo + mensaje debajo), no sólo un toast.
MetaApp.form_errors + validate_required_fields. Secciones de formulario:
FieldSpec.section agrupa campos bajo encabezados; abrir_form del CRM las
usa. Campos condicionales y pulido puramente visual: scope-out conciente.
El plan docs/nakui-erp-masterplan.md queda completo (7/7 fases). Tests
verdes (meta-schema 16, meta-runtime 70, meta-form 8, nakui-ui 14);
clippy limpio en las libs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Toda vista de lista gana un botón «⬇ CSV» que exporta las filas
filtradas/ordenadas (con refs resueltas y montos formateados) a un
archivo <entity>-<timestamp>.csv. Serializador to_csv (RFC 4180, con
escape) en el módulo nuevo meta-runtime/csv.rs. Refactor:
list_filtered_sorted extraído como helper compartido entre el render
de la lista y el export.
Tests de to_csv; meta-runtime 70 + meta-form 8 verdes, clippy limpio.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
View::Dashboard: grilla de tarjetas de agregados. Metric Count/Sum/
GroupBy con filtro opcional (CardFilter), computado por compute_metric
en meta-runtime (MetricResult Scalar/Breakdown). meta-form render_dashboard
pinta cada tarjeta con el número grande formateado o un breakdown con
barras de texto. El CRM gana una vista «Panorama»: clientes,
oportunidades, pipeline, ganadas, y breakdowns por etapa y canal.
Tests de compute_metric; verificación del panorama en nakui-ui. Clippy
limpio en las libs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
View::Detail: ficha de un record con sus campos + listas de records
relacionados (RelatedList, back-references por via_field) + botones
Volver/Editar. ListView.row_detail enlaza lista→ficha con un botón 👁
por fila; Module::validate exige que apunte a una vista detail. En
meta-form: render_detail/render_related + select_detail con retorno.
El CRM: 👁 en Clientes y Oportunidades abre su ficha; la del cliente
lista sus oportunidades e interacciones. Tests en meta-schema y
nakui-ui verdes; clippy limpio.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Column.ref_entity resuelve un UUID al label del record referido;
Column.format (ValueFormat Number/Currency) agrupa miles y prefija
símbolo. El campo entity_ref en formularios muestra el record elegido
por su label, no el UUID. human_label_for_record reconoce nombre/titulo
(español). El módulo CRM: las listas muestran el nombre del cliente y
monto como $12,000.
Helper format_value en meta-runtime. Tests en meta-schema, meta-runtime
y nakui-ui verdes; clippy limpio.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Primera fase del plan maestro. La metainterfaz gana dos tipos de campo:
Select (chips de un conjunto cerrado, con options validadas) y AutoId
(UUID autogenerado read-only). NakuiBackend::seed inyecta el id de la
entity = clave del store. El módulo CRM los adopta: etapa/canal son
selects, los ids de idempotencia se autogeneran, el form de cliente ya
no pide id. Ningún formulario pide un UUID a mano.
Tests en meta-schema, meta-runtime y nakui-ui verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sólo formato (orden de imports + wrapping), arrastrado por cargo fmt -p
nakui-ui al trabajar en el módulo CRM.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
examples/nakui-modules/crm/module.json: el módulo crm se ve ahora como
un ERP en nakui-ui (sidebar + listas + formularios), no sólo como el
timeline del event log. 7 vistas — lista+form de Clientes, Oportunidades
e Interacciones — con los formularios de morfismo Abrir/Mover/Registrar
que disparan los morfismos reales del kernel (nakui_module_dir engancha
el módulo crm). 2 tests verifican parseo, validación y carga por el
camino brahman_cards.
Correr: NAKUI_MODULES_DIR=examples/nakui-modules cargo run -p nakui-ui
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Módulo CRM declarativo (schema.ncl + nsmc.json + morfismos Rhai) con
tres entities (Cliente, Oportunidad, Interaccion) y tres morfismos:
abrir_oportunidad, mover_oportunidad (pipeline con validación de
transiciones) y registrar_interaccion.
crm_demo: demo realista de 18 eventos que —a diferencia de los otros
demos— conserva el event log e imprime el comando de nakui-explorer,
así el explorador muestra un CRM con cuerpo. tests/crm.rs: 8 tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
App GPUI con app_id carmen.greeter: formulario usuario+contraseña que
autentica con brahman-auth en un hilo de fondo y, en éxito, emite un
SessionTicket por stdout para que el compositor haga el traspaso a modo
sesión. Backend mock (MIRADA_GREETER_MOCK) o PAM.
Incluye brahman-auth::SessionTicket (contrato de tiquet greeter→compositor,
serializado a una línea con prefijo versionado) y el modo enmascarado de
nahual-widget-text-input (TextInput::with_mask para contraseñas).
18 tests nuevos; greeter verificado por compilación + clippy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Base del DM/greeter de carmen. Contrato Authenticator agnóstico:
authenticate(usuario, secreto) -> UserInfo (uid/gid/home/shell).
PamAuthenticator verifica contra PAM (/etc/pam.d/carmen); MockAuthenticator
con credenciales en memoria para tests. AuthError grueso: BadCredentials
vs AccountUnavailable, sin filtrar existencia de cuentas. resolve_user
vía getpwnam. data/carmen como servicio PAM; ejemplo auth-probe.
11 tests; el camino PAM real se ejercita.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Segunda mitad de la uniformización del tema. nahual-theme::toolkit
traduce el Theme activo a gtk-3.0/gtk.css y gtk-4.0/gtk.css con overrides
@define-color (acento exacto + neutro claro/oscuro sintetizado).
Theme::set/install_default exportan best-effort; guarda de no-pisar
respeta un gtk.css ajeno. El compositor inyecta XDG_CURRENT_DESKTOP=mirada
y QT_QPA_PLATFORMTHEME=gtk3 a cada hijo, así GTK y Qt siguen el tema.
8 tests nuevos en toolkit; ejemplo dump-toolkit-css.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Backend de xdg-desktop-portal para carmen: implementa
org.freedesktop.impl.portal.Settings y publica color-scheme,
accent-color y contrast desde el tema activo de nahual. GTK4, Qt6,
Firefox y Chromium voltean claro/oscuro + acento por protocolo, sin
tocar sus configs. Watcher con notify del archivo de nahual-theme →
emite SettingChanged en vivo. 13 tests; smoke verificado sobre un bus
de sesión efímero.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 3c: el shell muestra la salida de los comandos en un cajón que se
despliega hacia arriba sobre el escritorio.
carmen — la ventana del shell deja de tener un alto fijo: `render_loc`
la ancla al pie de la salida y la coloca por su **tamaño real**, así
puede crecer hacia arriba. La franja reservada sigue siendo la barra
(40 px); el cajón, al abrirse, se solapa sobre las teseladas sin
re-teselar. `render_loc` toma ahora el alto de la salida.
shuma-shell — un clic en el estado alterna `drawer_open`: la ventana
crece (`Window::resize`, que GPUI 0.2 expone) a barra + cajón, o
vuelve a sólo barra. El cajón reusa `render_run` para pintar los
últimos comandos y su salida, con scroll. `render_launcher` pasa a una
columna: cajón opcional arriba, barra abajo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 3b: la barra del shell muestra ahora las ventanas abiertas del
escritorio y deja saltar entre ellas.
- `shuma-shell` depende de `mirada-brain` para hablar el protocolo de
control de carmen.
- `start_loop` sondea el socket de control cada ~1 s con `ListWindows`
— la llamada bloquea un instante, pero en el executor de fondo, no en
el hilo de la UI. El resultado se guarda en `Shell.windows_bar`.
- `render_launcher` dibuja una cajita por ventana entre el input y el
estado: la enfocada resaltada, las demás en gris. Un clic envía
`Do(FocusWindow(id))` y refleja el cambio al instante (el sondeo lo
confirma en el siguiente ciclo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 3a del plan «shell»: `shuma-shell --launcher` (o la variable
`MIRADA_SHELL`) arranca el shell como una barra compacta acoplada al
pie de carmen, en vez del panel de 3 columnas.
- `run_launcher` abre la ventana GPUI sin barra de título y con
`app_id = "carmen.shell"` — el acople del compositor la reconoce y le
reserva su franja. GPUI 0.2 admite `WindowOptions.app_id`.
- `Shell.launcher: bool`; `Render::render` deriva a `render_launcher`
cuando está activo: una barra de una línea — un glifo, la línea de
comandos y el estado del último comando (en curso / ✓ / ✗).
- La construcción de la fila del input (tokens coloreados + caret +
sugerencia fantasma) sale a un helper `input_row` que comparten el
panel completo y el modo launcher — sin duplicar el resaltado.
`shuma-shell --launcher` va al `autostart.example`. Falta (3b/c/d): la
barra de ventanas abiertas, el cajón de resultados y la config.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 2 del plan «shell»: carmen reconoce la ventana del shell y le
reserva su sitio, en vez de teselarla como una más.
Una ventana cuyo `app_id` es `carmen.shell` no entra en el teselado:
carmen le reserva una franja de 40 px al pie de la salida, la dimensiona
y la fija ahí, y la compone sobre todas las demás. El Cerebro tesela el
resto de ventanas en el área que queda.
- `mirada-protocol`: nuevo `BodyEvent::OutputResized { id, w, h }` — el
Cerebro cambia el área útil de una salida **sin** perder el escritorio
que muestra (a diferencia de quitar y volver a añadir la salida — que,
de paso, era un bug latente al redimensionar la ventana winit).
- `mirada-brain`: `Desktop` atiende `OutputResized` (test nuevo).
- `mirada-body`: `BodyState::resize_output`.
- `mirada-compositor`: `ManagedWindow.is_shell`, `App.output_size`,
`dock_shell`/`output_changed`; `register_toplevel` no registra el
shell en el Cerebro; al cerrarse libera la franja. El shell se compone
y se enfoca con el ratón aunque no viva en el Cerebro; no lleva marco.
El backend winit usa ahora `resize_output` al redimensionar.
GPUI no habla `wlr-layer-shell`, así que el acople es por `app_id`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fase 1 del plan «shell»: para que carmen pueda hospedar a `shuma-shell`
(y a cualquier app GPUI o navegador acelerado) hace falta que los
clientes con GPU puedan compartir su búfer de vídeo. carmen sólo hablaba
`wl_shm` (búferes de software) — por eso `foot` corría pero las apps
GPUI salían en negro.
- `App` lleva un `DmabufState`; `impl DmabufHandler` con `dmabuf_imported`
que acepta el búfer (el `GlesRenderer` ya importa DMA-BUF al componer,
vía `ImportAll`, así que la validación real ocurre al pintar).
- `delegate_dmabuf!(App)`.
- `announce_dmabuf` crea el global con los formatos de `dmabuf_formats()`
del renderer — se llama en ambos backends una vez creado el renderer.
Pendiente del plan: Fase 2 (`wlr-layer-shell`) y Fase 3 (modo launcher
de `shuma-shell` — barra + input + cajón de resultados).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un escritorio en «modo launcher» necesita un lanzador. `mirada-launcher`
es una app nueva, sin dependencias: escanea los `.desktop` del estándar
XDG y lanza el que elijas desde una lista de terminal que se filtra
escribiendo.
- Recorre los directorios `applications/` de XDG en orden de prioridad
(el del usuario tapa a los del sistema, dedup por id de archivo),
parsea el grupo `[Desktop Entry]` (salta `NoDisplay`/`Hidden`, exige
`Type=Application`), y limpia los códigos de campo del `Exec`.
- Interfaz de terminal sin raer modo: número = lanzar, texto = filtrar
(si deja una sola, la lanza), Enter vacío = salir. Las apps con
`Terminal=true` se envuelven en `foot -e`.
- Pensado para abrirse en una terminal pequeña; al lanzar termina y el
programa queda corriendo, reparentado a init.
El keymap por defecto ata `Super+p` a `spawn:foot -e mirada-launcher`
(`Super+d` ya era el layout CenteredMaster).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dos piezas para usar carmen como tu escritorio de verdad.
Conmutación de VT — `Ctrl+Alt+Fn` salta a otra TTY y vuelve sin romper
la sesión. El `SessionEvent` de `libseat` ahora hace trabajo de verdad:
- al ceder la VT, pausa el `DrmDevice` y suspende `libinput`; `render()`
no vuelve a tocar la GPU mientras la sesión esté cedida (`active`).
- al recuperarla, reanuda `libinput`, reactiva el `DrmDevice`, llama a
`DrmCompositor::reset_state` y repinta.
`DrmState` conserva ahora `drm` y un clon del contexto `libinput`.
Sesión — `~/.config/mirada/autostart` (un comando por línea, `#`
comenta) se lanza al arrancar el backend DRM, vía un `spawn_autostart`
que reusa `spawn_command`. Y `session/`: el script `mirada-session`
(fija el entorno XDG y exec del compositor) y `carmen.desktop` para
registrarlo en un gestor de login, más un `autostart.example`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El cursor dejaba de ser un cuadrado fijo. Ahora honra
`wl_pointer.set_cursor`: sobre el texto de una terminal sale la «I»,
sobre un enlace la mano, etc. — la forma la dibuja el cliente en una
superficie y el compositor la compone.
- `App` guarda un `cursor_status: CursorImageStatus`; el handler
`SeatHandler::cursor_image` lo actualiza.
- `render()` lo interpreta: `Surface` → compone el árbol de la
superficie del cursor en `pointer_loc - hotspot` (helper
`cursor_hotspot`, vía `CursorImageSurfaceData`); `Named` o sin tema →
el cuadrado de siempre; `Hidden` → nada.
- Sobre el escritorio pelado (sin cliente debajo) el cursor vuelve al
de por defecto, para que no se quede con la «I» de la última ventana.
- La superficie del cursor también recibe frame-callbacks (cursores
animados).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sin decoración, las ventanas se confundían entre sí. Ahora el backend
DRM dibuja un marco fino alrededor de cada ventana: azul la que tiene
el foco del teclado, gris las demás.
- `ManagedWindow` gana `focused: bool` (lo fija `exec_op` al atender
`BodyOp::Focus`/`Unfocus`) y `borders: [SolidColorBuffer; 4]` — un
búfer por lado, cada uno con su `Id` estable para el seguimiento de
daño; `SolidColorBuffer` sube su contador sólo si tamaño o color
cambian, así un marco quieto no fuerza recomposición.
- El enum `Frame` pasa de `Cursor` a `Solid`: una variante de color
sólido que sirve para el cursor y para los marcos (dos variantes con
el mismo tipo chocarían en el `From` que genera `render_elements!`).
- `render()` en dos pasos: refresca los búferes (tamaño = contenido,
color = foco) y luego arma los elementos. El marco va metido hacia
adentro, sobre el borde de la superficie, así no pisa al vecino.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un escritorio sin forma de abrir una terminal no es usable. Ahora el
keymap puede lanzar programas:
- `mirada-protocol`: nuevo `BrainCommand::Spawn(String)`.
- `mirada-brain`: `DesktopAction::Spawn(String)` con forma textual
`spawn:<comando>` (`Display`/`FromStr`); `Desktop::apply` la traduce
a `BrainCommand::Spawn`. El keymap por defecto trae
`Super+Shift+Return` → `spawn:foot`. `DesktopAction` deja de ser
`Copy` (lleva el comando) — `Keymap::lookup` clona en vez de copiar.
- `mirada-body`: `BodyOp::Spawn(String)`.
- `mirada-compositor`: `exec_op` ejecuta el spawn con un helper
`spawn_command` (`sh -c`, hereda `WAYLAND_DISPLAY`), que también
recoge el lanzamiento de `MIRADA_STARTUP` — antes duplicado.
`spawn:foot --title x` también funciona desde `mirada-ctl`. Tests
nuevos del round-trip textual y del flujo atajo→comando.
Nota: un keymap.ron ya existente no recibe el atajo nuevo; hay que
añadir la línea a mano o borrar el archivo para regenerarlo.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`Super`+arrastre interactivo en el backend DRM: botón izquierdo mueve
la ventana, botón derecho la redimensiona. Al arrastrarla, la ventana
pasa a flotar — comportamiento estilo dwm.
La verdad geométrica vive en el Cerebro, así que el arrastre viaja
hasta él:
- `mirada-protocol`: nuevo `BodyEvent::WindowFloatTo { id, rect }`.
- `mirada-brain`: `Desktop::on_event` lo atiende — busca el escritorio
de la ventana y la hace flotar en ese rectángulo
(`Workspace::set_floating`). Dos tests nuevos.
- `mirada-compositor`: `DragGrab`/`DragMode` en `App`; `handle_input`
arranca el arrastre con `Super`+botón sobre una ventana
(`keyboard.modifier_state().logo`), traga los botones mientras dura y
lo cierra al soltar. `drag_update` recalcula el rectángulo (mover =
esquina sigue al puntero; redimensionar = esquina inferior-derecha,
con un mínimo de 120 px) y emite `WindowFloatTo`. Durante el arrastre
el puntero no llega al cliente.
De paso, arregla un test de `mirada-link` que construía un
`WindowPlacement` sin los campos `floating`/`fullscreen`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un escritorio teselante no quiere barras de título de cliente. El
compositor anuncia ahora `xdg-decoration` y a todo toplevel le impone
`Mode::ServerSide`; como el servidor no dibuja decoración alguna, las
ventanas quedan sin marco.
Sin esto, clientes como `foot` se dibujan su propia barra (CSD) con
botones de minimizar/maximizar/cerrar — ruido en un WM teselante.
- `XdgDecorationHandler` para `App`: `new_decoration`, `request_mode`
y `unset_mode` fijan siempre `ServerSide` y reenvían el configure.
- `delegate_xdg_decoration!(App)`; el global se anuncia en `build_app`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM del Cuerpo deja de ser sólo-teclado: `libinput` ahora
mueve un cursor de software y reenvía clics y rueda a los clientes.
- Enum `Frame` (vía `render_elements!`) que mezcla superficies de
cliente y un `SolidColorRenderElement` para el cursor, marcado
`Kind::Cursor` y compuesto encima de todo.
- `handle_input` atiende `PointerMotion`/`PointerMotionAbsolute`/
`PointerButton`/`PointerAxis`; el puntero se acota a la salida.
- Foco-sigue-ratón: `window_at` hace el test de impacto (flotantes
sobre teseladas, contra el rectángulo real de la superficie) y, al
cambiar de ventana, emite `BodyEvent::PointerEntered`.
- `surface_px_size` en main.rs — tamaño presentado de una superficie,
reusado por el test de impacto.
Compila + clippy limpio; pendiente de verificar en hardware.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Un cliente que presenta una superficie más pequeña que su celda (p. ej.
un terminal que redondea a celdas de texto enteras) dejaba el hueco todo
a un lado. Ahora ManagedWindow recuerda el tamaño de la celda y
render_loc() centra la superficie en el sobrante. Lo usan los dos
backends (winit y DRM).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
mirada-compositor tiene dos backends: winit (anidado) y drm (nativo
sobre TTY, verificado en hardware). README con la selección de backend,
los requisitos de cada uno y MIRADA_STARTUP/MIRADA_DRM_TIMEOUT; SDD con
la estructura del backend DRM. Pendiente: puntero en DRM, VT switch,
hotplug.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM funciona en hardware (sesión, render, teclado, atajos,
clientes, salida limpia). Diagnosticado: la franja al pie con `foot` es
que `foot` redondea su superficie a celdas de texto enteras (1920×1040
de 1080) — comportamiento del cliente, no del compositor.
Se retira la instrumentación: log por tecla, censo de dispositivos y
volcado de tamaños de superficie cada 2 s. El tope de tiempo
(MIRADA_DRM_TIMEOUT) pasa a estar desactivado por defecto — Super+Shift+e
y Ctrl+C son la salida normal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El panel sólo ofrece modos 1920×1080, así que la franja negra no es del
modo. Para localizarla, el bucle DRM registra cada ~2 s la posición y el
tamaño real de cada superficie — así se ve si el cliente llena la
pantalla o se queda corto.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El borde negro seguía: la marca PREFERRED del panel no es fiable (a
veces apunta a un modo menor). Ahora se elige el modo de mayor área
(a igualdad, mayor refresco), y se registran todos los modos del
conector para diagnóstico.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
El backend DRM cogía conn.modes()[0], que no es el modo preferido —
en un panel 16:10 (1920×1200) suele ser un 1920×1080, dejando una
franja negra abajo.
Ahora elige el modo marcado PREFERRED (el nativo) y, si ninguno lo
está, el de mayor área.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>