Changelog¶
Todos los cambios relevantes de Aurora se documentan aquí.
El formato sigue Keep a Changelog y el proyecto usa
Versionado Semántico. Mientras la versión sea 0.x, una versión
menor puede eliminar lo que se marcó como obsoleto en la anterior; cómo se retira cada cosa lo
explica la política de estabilidad.
Sin publicar¶
Añadido¶
- Política de estabilidad de la API
(documentada): qué es público (lo que se
importa de
aurora,aurora.animate,aurora.dialogsyaurora.menus, las props de estilo, los tokens, la CLI y[tool.aurora.build]) y qué es interno, qué puede cambiar en cada tipo de versión, y el plazo antes de eliminar algo: al menos una versión menor marcado como obsoleto mientras sea0.x, y hasta la siguiente mayor desde la 1.0. aurora.AuroraDeprecationWarning: el aviso que emite lo obsoleto, con la versión desde la que lo está, la versión en que se eliminará y la alternativa. Hereda deFutureWarning(se ve por defecto);-W error::aurora.AuroraDeprecationWarninglo convierte en error en los tests de una app.- Soltar archivos desde el sistema:
on_drop=lambda rutas: ...en cualquier elemento recibe las rutas de los archivos y carpetas arrastrados desde el explorador (en Windows, el elemento bajo el puntero o su primer ancestro conon_drop; en macOS y Linux X11, que no informan del punto, el primeron_dropde la vista).on_drop_hoverrecibe las rutas mientras el arrastre pasa sobre la ventana yNoneal salir o soltar, para resaltar la zona. En Wayland winit aún no los entrega.Pilot.hover_files(...)yPilot.drop_files(..., key=...)lo simulan en los tests, yexamples/notepad.pyabre el archivo que se suelta sobre la ventana. - Componentes nuevos de entrada:
number_input: campo numérico con flechas y ↑/↓, recortado amin/max, eninto enfloatredondeado a los decimales destep; acepta la coma decimal y espera mientras el texto está a medias o fuera de rango. Se anuncia comospinbutton(rol nuevo).combobox: unselecten el que se escribe para filtrar las opciones (sin distinguir mayúsculas ni tildes), con ↑/↓ para recorrerlas, Enter para elegir y Esc para cancelar.date_picker: selector de fecha (datetime.date) con calendario del mes, límitesmin/max, «Hoy», semana desde lunes o el día que se pida (first_weekday), formato configurable y teclado (días, semanas y meses con las flechas y RePág/AvPág).aurora.dialogsyaurora.menusdeclaran su__all__.- Interno:
tests/test_api.pyfija la superficie pública (tests/api.txt, nombres y firmas) yaurora._deprecationreúne los ayudantes para retirar API (@deprecated,@renamed_kwargs,warn). La suite convierte los avisos de obsolescencia en errores.
0.5.0 — 2026-10-18¶
Añadido¶
- Accesibilidad con AccessKit: lo que describe la vista llega a los lectores de pantalla (Narrador y NVDA en Windows, VoiceOver en macOS, Orca en Linux) y sus acciones vuelven a la app.
- Los componentes anuncian su rol y su nombre solos (
button,checkbox,switch,radio,slider,select,tabs,menu,dialog,progress,toast,virtual_list,table…), y cualquier elemento acepta props de accesibilidad:role,label,description,value,checked,selected,expanded,level,live,position_in_set,set_size,sort,modal,hidden,value_min/value_max/value_stepyalt(en imágenes). El andamiaje de layout no se anuncia (una caja sin rol ni nombre es decorativa). - El foco, los estados y los valores se publican al sistema, incluido lo que se está escribiendo en un campo; el lector puede activar, enfocar, escribir valores y ajustar deslizadores (acciones de AccessKit). Sin lector conectado, el árbol ni se construye.
Pilot.a11y_tree()yPilot.a11y_action(...)comprueban y simulan lo que ve y hace un lector en los tests.- Navegación por teclado: flechas (y Home/End) en
slider, flechas en el selector segmentado, y el foco del lector ya no se pierde si su nodo sigue en la vista (puede caer en un texto, que no es enfocable con Tab:pilot.a11y_action(..., "focus")). - Varias ventanas:
App.window(view, title=..., size=...)crea ventanas adicionales, cada una con su vista;app.windows()las lista. Comparten el tema y losSignal(un cambio se ve en todas) y el inspector (F12) es independiente en cada una. Window.open()/close()las gobiernan en marcha desde cualquier hilo;titleysizese cambian al momento;is_opendice su estado.on_closepermite cancelar el cierre (por ejemplo, para pedir confirmación).- La app termina al cerrarse su última ventana;
app.quit()la cierra cuando haga falta yrun(keep_alive=True)la mantiene viva sin ventanas. Window.pilot()yWindow.render_png()reutilizan los mismos ayudantes sin pantalla queApp, yexamples/windows.pyenseña lista + detalle en dos ventanas.- Interno:
window.rsse reparte en host + unaViewpor ventana;wake()pasa a una época compartida por todas las ventanas, y lo memorizado por@componentse guarda por dueño (App/Window/Pilot) para que dos ventanas no se pisen las memorizaciones. - Diálogos nativos de archivo:
aurora.dialogs(open_file,open_files,save_file,pick_folder) abre los diálogos del sistema para elegir archivos o carpetas. Son síncronos, la ventana desde la que se llaman es su padre (salen modales sobre ella) y durante el diálogo se suelta el GIL, así que los hilos y las corrutinas deapp.spawnsiguen trabajando. En Linux usan el XDG Desktop Portal (ozenity); en los tests se sustituyen por un doble. - Bandeja del sistema:
app.tray(icon, tooltip=..., menu=..., on_click=...)pone un icono en la bandeja con su menú (aurora.menus: opciones, separadores y submenús) yapp.tray_hide()lo quita. Mientras hay bandeja, la app sigue viva aunque se cierren todas las ventanas (hastaapp.quit()). En Linux usa StatusNotifierItem (sin GTK; en GNOME hace falta la extensión AppIndicator). - Menús nativos:
App(view, menu=menus.bar(...))pone la barra de menús (Windows y macOS) con atajos del sistema (shortcut="mod+o") y entradas estándar (menus.predefined("copy")); en macOS se añade delante el submenú estándar de la app.on_context_menu(lista o callable) abre un menú contextual nativo con el clic derecho. En Linux no hay menús nativos (requieren GTK): ese hueco lo cubre el componentemenudentro de la app. - La ventana principal tiene mango:
app.maines unWindowcomo los demás — cambia eltitleo elsizeen marcha, ciérrala y reábrela (app.main.close()/open(), el patrón de la bandeja) o ponle unon_closeque cancele el cierre.app.windows()sigue devolviendo solo las adicionales, y la vista de la principal sigue siendo la de laApp(el hot reload la mantiene). - Ejemplos nuevos:
examples/notepad.py(barra de menús con atajosmod+o/mod+s/mod+n, diálogos de abrir y guardar, título con el archivo y los cambios sin guardar, y confirmación al salir desde el menú y desde la X) yexamples/tray.py(la app vive en la bandeja: cerrar la ventana no la termina, se reabre desde el icono y solo «Salir» la acaba), con su icono generado por la propia librería (examples/icon.png). Ambos se renderizan en CI y tienen galería. dialogsyapp.tray(...)aceptan además rutasPath(no solostr) para el directorio y el icono.- Notificaciones del sistema:
app.notify(title, body, icon=...)muestra un aviso del sistema (Centro de notificaciones en Windows y macOS, libnotify/D-Bus en Linux y BSD, connotify-rusty sin dependencias C). Se puede llamar desde cualquier hilo —también desdeapp.spawn()con la ventana cerrada— y sin app en marcha; si el sistema no las admite, se avisa por stderr y la app sigue.examples/tray.pynotifica al añadir un recordatorio y reabre la ventana al pulsar el aviso. - Callback de clic:
on_clickse llama —sin argumentos— al pulsar el cuerpo del aviso, en un hilo propio (cambiar unSignaldespierta la ventana sola). En Linux el aviso declara la accióndefault—la que los servidores asocian al cuerpo y no dibujan como botón—; en macOS hace falta la app en marcha (run()) para que el sistema entregue el clic. - Windows: los avisos llevan la identidad de la app (
App(app_id=...)); en una app empaquetada (sys.frozen) se deriva del título y se registra sola al arrancar con un acceso directo del menú Inicio —lo que Windows exige a las apps sin instalador—, así que dejan de salir firmados como «Windows PowerShell».app.register_app_id()lo hace a mano (en desarrollo apunta apython.exe+ el script) y devuelveFalsefuera de Windows. aurora buildcon--upx/upx = true: opción para comprimir los binarios con UPX (debe estar instalado) cuando se quiere un ejecutable más pequeño.
Cambiado¶
- Interno — el parse del spec reutiliza las claves de cada dict: cada prop se consulta con un
PyStringya creado (y con su hash memorizado) en vez de fabricar y hashear una cadena por consulta. Es ~2× más rápido por consulta y la fase de parse baja alrededor de un tercio en los benchmarks (lista_10k, 50 003 nodos: ~230 → ~160 ms); las props de accesibilidad salen gratis. - La documentación explica cómo instalar el paquete publicado: el README, el inicio y la guía
enseñan
pip install aurora-ui(ouv add aurora-ui) como primera vía —los wheels ya traen el núcleo compilado, sin Rust ni compilar nada— y dejan el clonado del repositorio (uv + Rust) para trabajar sobre Aurora. El roadmap marca las wheels en PyPI como hechas. aurora buildya no usa UPX por defecto: aunque UPX esté instalado, el ejecutable se empaqueta igual en cualquier máquina (antes, tenerlo en elPATHcambiaba el resultado y podía llenar la salida de trazas deNotCompressibleException); UPX además dispara falsos positivos de antivirus y arranca más lento. Quien lo quiera, lo activa conupx = trueo--upx.- La salida de
aurora builddeja de colar el encabezado de PyInstaller: el nivel de log va por entorno, que sí filtra las líneasINFO:que--log-levelno alcanzaba al invocarlo con-m; un build limpio imprime soloEmpaquetando …yListo: …, y los avisos que importen.
0.4.0 — 2026-10-17¶
Añadido¶
- Movimiento por defecto en los componentes:
switch,checkbox,tabsyselectaniman sus cambios sin declarar nada — el knob se desliza, la marca de la casilla entra encogiéndose, el subrayado de las pestañas funde y el menú del desplegable aparece deslizándose y se va fundiéndose. Comparten una escala común de duraciones cortas. App(view, motion=False)o la variable de entornoAURORA_MOTION=0lo desactivan para toda la app (tests de snapshot, equipos que lo prefieren sin movimiento).- Los efectos de entrada no se reproducen en el primer frame ni en
render_png: las capturas y la documentación siguen siendo deterministas. examples/studio.pyaprovecha las animaciones: las páginas se funden al cambiar, el modal y el toast entran y salen, las tarjetas del tablero y las barras de progreso se deslizan.- Componentes nuevos (con las animaciones anteriores ya puestas):
dialog: modal completo — fondo, tarjeta centrada, cierre con clic fuera o Esc, foco dentro de la tarjeta al abrirse y atrapado, y entrada/salida animadas. DevuelveNonecerrado.toast: aviso flotante abajo a la derecha que se descarta con un clic (enlazado a unSignal[str | None]) y entra deslizándose.progress: barra de progreso de 0 amaxque crece hasta su nuevo valor y admite color del tema o gradiente.pages: una vista por valor de unSignalcon transición entre páginas y su propio scroll.examples/studio.pyusa los componentes nuevos:dialog,toast,progressypagessustituyen sus versiones locales y montadas a mano.- Vaivén perpetuo
animate.pulse(): opacidad que baja hastafloory vuelve, sin terminar. Se pasa en la propiedadpulsede un elemento; conApp(motion=False)queda fijo. - El color del texto ahora anima con
transition(antes se cambiaba de golpe): los párrafos se rehacen con el color interpolado mientras dura la transición. El color heredado de un contenedor no se propaga a los hijos: para animarlo, el texto necesita sutransition. top/right/bottom/leftaceptan porcentajes (left="50%"), resueltos contra la caja de relleno del padre. Es lo que permite anclar pulgares y barras por fracción sin medir nada.- Componentes nuevos con movimiento por defecto:
radio: grupo de opciones; el punto interior entra encogiéndose.segmented: selector con pulgar deslizante (usa los porcentajes enleft).slider: deslizador arrastrable (widthen píxeles) con relleno y pulgar que siguen el valor.spinner: tres puntos que laten en cadena (pulse); nunca queda en reposo.skeleton: bloques con la forma del contenido que llega, latiendo despacio.- Las pestañas (
tabs) funden también el color de su texto, yexamples/studio.pyusa elsegmenteddel catálogo. overflow="hidden": el contenido que se sale de su caja se recorta (pintado y clics) sin tocar el layout. Conshrink=0en el hijo, desborda en vez de aplastarse: es la base del despliegue del acordeón.- Componentes nuevos:
menu: lista de acciones que se abre desde un disparador, en superposición, con cierre al elegir, al hacer clic fuera o con Esc.accordion: secciones que se despliegan con altura animada y recorte, y se quedan montadas al plegarse (conservan su estado).field: etiqueta + control + mensaje de ayuda o error ($danger).examples/studio.pyestrena los tres:fielden el formulario de nueva tarea,menuen las tarjetas del tablero y un acordeón de «Zona de peligro» en Ajustes.
Cambiado¶
- Los ejemplos se actualizan a lo último de la librería:
hello.pymuestra la versión real en su insignia y ajusta el paso del contador con unslider;animate.pyusadialogpara el modal, lleva el progreso en una barra y funde el color del texto al completar;canvas.pymueve la carga del clúster con un deslizador;charts.pyelige la forma del gráfico de cierres con unsegmented;table.pypinta avatares en las celdas;brand.pyrefleja el ejemplo del README; ystudio.pyañaderadioyslidera Ajustes (densidad del tablero y objetivo semanal) y su número de versión deja de estar escrito a mano. La galería de la documentación gana capturas de esos estados. - Reorganización interna en módulos (misma API pública):
python/aurora/components.pypasa a ser el paquetepython/aurora/components/, con un módulo por familia (basic,forms,navigation,overlays,feedbackylists).- El núcleo nativo se reparte por fases y dominios de entrada:
src/render.rs→src/render/,src/runtime.rs→src/runtime/ysrc/chart.rs→src/chart/. - Los iconos SVG y el parseo de las variables
AURORA_*quedan compartidos en_icons.pyy_flags.py;menuyselectcomparten la mecánica del desplegable (_dropdown.py). - Con esa unificación, el desplegable de
selectcierra con Esc/Enter «limpios» (sin modificadores), comomenuydialog; antes también cerraba con Shift+Esc o Ctrl+Enter.
Corregido¶
examples/brand.pyya no falla al imprimir «✓» en una consola de Windows sin UTF-8 (como ya hacíastudio.py), así queuv run examples/brand.pytermina con código 0 y regenera el banner y el logo.- En un
textarea,Ctrl+Inicio/Ctrl+Fin(Windows/Linux) yCmd+Inicio/Cmd+Fin(macOS) saltan al principio o al final del texto; sin modificador, Inicio/Fin siguen yendo al borde de la línea. Antes, en macOSCmd+Iniciono hacía el salto. - El test de la rejilla del
chartcontaba cualquier píxel parecido al borde: en Linux, el anti-aliasing del trazo o de la fuente producía falsos positivos. Ahora compara el total de píxeles de la rejilla entregrid=Trueygrid=False.
0.3.0 — 2026-10-16¶
Añadido¶
- Documentación publicada con ejemplos vivos en https://yoshilol0526.github.io/aurora/
(MkDocs Material, desplegada en GitHub Pages en cada cambio de
main): - Los bloques
```python livese ejecutan al construir la documentación y debajo se publica la captura que renderiza Aurora, en tema claro y oscuro según el del lector. Un ejemplo que falla rompe la construcción ytests/test_docs.py, así que no pueden quedarse desfasados. - Admiten programas completos, fragmentos que terminan en un elemento, pasos que continúan el
programa anterior (
continue), scripts deexamples/(file=) y código oculto para preparar un estado (# oculto). -
La guía, la referencia de componentes y una galería con los ejemplos del repositorio usan ejemplos vivos.
-
Hot reload que conserva el estado. Con
aurora dev app.py,AURORA_RELOAD=1oApp(view, reload=True), al guardar el script o un módulo local la ventana se actualiza con el código nuevo sin cerrarse: - Se vuelven a ejecutar el módulo cambiado, los módulos de la app que usan algo suyo y el
script principal; la app en marcha adopta la
viewy elthemenuevos. - Los
Signalque siguen en el mismo sitio (global, atributo de un objeto de la app, dict o lista) conservan su valor y su identidad: hilos, corrutinas y otros módulos no notan nada. - Si el código nuevo no compila o falla, los módulos vuelven a como estaban y la ventana
muestra el traceback hasta que se guarda una versión que funciona; una excepción en
view()también se muestra en vez de cerrar la app.
Para tests, app.watch(module) devuelve el Reloader sin hilo vigilante
(reloader.check() y el siguiente frame del piloto aplica los cambios).
- Inspector visual del árbol y profiler de frames. Con
App(view, inspector=True)o la variable de entornoAURORA_INSPECTOR=1, F12 acopla un panel a la derecha de la ventana: - Árbol navegable (virtual, con ramas plegables) con tipo, key y texto de cada nodo; al elegir uno se ven su caja, su estilo resuelto, su texto y sus eventos, y se resalta sobre la interfaz.
- Modo selección (Ctrl+Mayús+C o el botón ⌖): pasar el ratón resalta el nodo bajo el puntero con su padding y su tamaño, sin llamar a Python; un clic lo elige sin llegar a la app.
- Profiler de frames: coste por fases de los últimos 240 frames en barras apiladas frente al presupuesto de 60 fps, con último, media, p95, máximo y desglose por fase.
Desactivado no cuesta nada. El panel está hecho con las primitivas de Aurora; el núcleo pone el
modo selección, el resaltado y el historial (_core.inspection()). En tests,
app.pilot(inspector=True) y Pilot.inspection.
- Pilot.stats["total"]: suma de las fases del frame.
- Cursor "crosshair" en la propiedad de estilo cursor.
aurora build: empaqueta una app en un ejecutable distribuible con un comando (un solo archivo, una carpeta o una.appen macOS), sin consola, con icono y recursos incluidos. Se configura con opciones o en[tool.aurora.build]depyproject.tomly valida la configuración antes de empezar. Usa PyInstaller, como dependencia opcional (aurora-ui[build]). También comopython -m aurora build.-
resource(path): ruta de un recurso de la app relativa al script principal, que funciona igual con Python y dentro del ejecutable empaquetado. -
Animaciones declarativas (
aurora.animate), interpoladas en Rust sin llamar a Python en cada frame. Cuatro propiedades de estilo nuevas: transition=animate.spring()oanimate.tween(0.2): los cambios debg,border_color,opacityyradiusentre reconstrucciones se animan desde lo que se ve (cambiar de destino a medias no da saltos).animate_layout=True: el elemento se desliza a su nueva caja cuando el layout lo mueve (otros que aparecen, se van o se reordenan); fondo, borde y sombra cambian de tamaño con él.enter=yexit=con los efectosanimate.fade(),animate.slide(x, y),animate.zoom(scale)yanimate.effect(...)(opacidad, desplazamiento y escala). Lo que sale se pinta como una instantánea que ya no ocupa sitio ni recibe eventos; si vuelve antes de terminar, entra desde donde estaba. Las entradas no se reproducen en el primer frame.- Curvas: muelle físico (
stiffness,damping,mass) o interpolación con easing de CSS, ambas condelaypara escalonar.
Las animaciones se repintan por zonas como el resto (cada frame coincide con un repintado
completo) y pilot.frame()/pilot.settle() las esperan, salidas incluidas.
table(rows, columns, *, key, ...): tabla de datos con cabecera fija, ordenación (clic en la cabecera: ascendente → descendente → original; vacíos al final, orden estable y memorizado), columnas redimensionables (arrastrando el borde de la cabecera, conmin_widthy desplazamiento horizontal si no caben) y filas virtualizadas sobrevirtual_list(100 000 filas sin coste al desplazarse). Las filas pueden ser dicts, tuplas u objetos;Columndefine título, ancho, alineación,value,format,cell(cualquier elemento) y si ordena o se redimensiona. Orden y anchos se guardan porkeyo enSignalpropios (sort,widths);on_row_click(fila)hace las filas pulsables. Ejemplo enexamples/table.py.on_dragen cualquier elemento, conDragEvent(phase:"start","move","end";x,y;dx,dydesde donde se pulsó): el elemento recibe el arrastre hasta soltar aunque el puntero salga de su caja, y un arrastre no completa el clic.- Propiedad de estilo
cursor("pointer","text","grab","grabbing","col-resize","row-resize","default"). -
Pilot.drag(key, dx, dy, steps=4)yPilot.cursor. -
Downsampling (LTTB) en Rust: los
chartreducen cada serie a ~2 puntos por píxel al grabarla (Largest-Triangle-Three-Buckets), tramo a tramo (losNaNcortan la línea) y con presupuesto por ancho en pantalla, así que los picos se conservan (con x desordenada, como una curva cerrada, el presupuesto es el ancho entero). Un gráfico de millones de puntos se graba en milisegundos: con x ordenada (y sin huecos en x) solo se recorren los índices visibles, más un punto de margen a cada lado cuyo segmento se corta justo en el borde. Los datos se siguen leyendo sin copiarlos y solo se releen cuando la ventana o los datos cambian. chart(..., zoom=True): zoom y pan sobre el eje x. La rueda acerca y aleja con el puntero como ancla (el dato bajo el cursor no se mueve), el arrastre y la rueda horizontal desplazan la ventana y el doble clic vuelve a la vista completa; el eje y se ajusta a lo visible salvo que fijesy_min/y_max. Al llegar a un tope, la rueda pasa alscrollde fuera, y el cursor cambia a "agarrar" sobre un gráfico con zoom. La ventana es estado porkeyque sobrevive a las reconstrucciones yPilot.chart_view(key)la lee en los tests. El ejemplo deexamples/charts.pyincluye una señal de un millón de puntos con zoom.chart(y, *, kind="line", **estilo): gráficos nativos de línea, área, barras y dispersión, con los ejes (ticks redondos), la rejilla y la leyenda calculados solos. Los datos se leen sin copiarlos, con el protocolo de búfer: arrays de numpy (f32/f64y enteros, en cualquier orden de bytes, contiguos o con strides: vistas, matrices por columnas, arrays en orden Fortran),array.array,memoryviewy Series/DataFrames de polars o pandas (víato_numpy), además de listas (también de escalares de numpy). Varias series con una lista, un array 2-D o un DataFrame;color(un color o la paleta del tema por serie),names(leyenda),labels(categorías),x,y_min/y_max,grid,stroke_width,point_sizeybar_width(las barras de varias series se agrupan). El dibujo se graba en unPicturede Skia que se repinta sin releer los datos: solo se vuelve a grabar si cambian los datos u opciones, o la caja del gráfico. Ejemplo enexamples/charts.py.Pilot.stats["draw"]incluye también el grabado de loschart.canvas(draw, **estilo): lienzo de dibujo inmediato.draw(c)recibe unCanvascon su tamaño (c.width,c.height) y dibuja rectángulos, círculos, elipses, arcos, líneas, polilíneas, polígonos, trazados libres (Path) y texto, con relleno, trazo, guiones, transformaciones y recortes; los colores aceptan tokens del tema. Los comandos cruzan al núcleo en un solo lote y se graban en unPicturede Skia que se repinta sin volver a Python.drawse llama tras el layout, al reconstruirse la vista o cambiar el tamaño; dentro de un@component, solo si cambia algúnSignalque lea. Los puntos admiten arrays de numpy sin recorrerlos en Python. Ejemplo enexamples/canvas.py.Pilot.stats["draw"]: coste de las funcionesdrawde loscanvasen el último frame.- Backend GPU: la ventana pinta con Skia sobre Vulkan (Windows y Linux) o Metal (macOS), y vuelve a
la CPU (softbuffer) si la GPU no arranca o se pierde el dispositivo. El repintado parcial se
conserva con un lienzo persistente en la GPU.
App.run(backend="auto" | "gpu" | "cpu")y la variable de entornoAURORA_BACKENDeligen el backend. Pilot.stats: coste del último frame por fases (build,parse,layout,paint), número de nodos y si se reconstruyó la vista.- Suite de benchmarks reproducible (
benchmarks/run.py): lista de 10 000 filas, dashboard de 50 paneles y escritura en un formulario de 500 filas, con mediana y p95 por fase, salida JSON, comparación con una medición anterior (--compare) y ejecución en CI con resumen en cada build. Pilot.stats["layout_cached"]: si el frame reutilizó el layout del anterior.- Reactividad de grano fino con
@component: un componente no se reconstruye si sus argumentos son iguales y no cambió ningúnSignalque leyera (al construirse o en sus textos con lambda). Reutiliza el mismoElement, su serialización y el nodo ya parseado en el núcleo. En la lista de 10 000 filas con filas como componentes, cambiar la selección pasa de ~0,8 s a ~0,19 s. virtual_list(count, item, item_height=..., key=...): lista de filas de alto fijo que solo construye las visibles. Con 100 000 filas, reconstruir cuesta menos de 1 ms y las filas nuevas aparecen en el mismo frame del desplazamiento.on_scrollen los contenedoresscroll, conScrollEvent(x,y,width,height): se llama cuando cambian el desplazamiento o la zona visible, sin reconstruir la vista por sí solo.Pilot.stats["style"](coste de la fase de estilo) yPilot.stats["damage"](zona repintada en el último frame, oNone). La suite de benchmarks muestra la fasestyle.- Comprobación de tipos con ty en la CI, junto a ruff, sobre la librería, los tests, los ejemplos y los benchmarks.
Cambiado¶
- Incompatible: Python ≥ 3.11 (antes 3.10). Los
chartleen los arrays sin copiarlos con el protocolo de búfer, que la API estable (abi3) expone desde 3.11; el wheel sigue siendo uno por plataforma y arquitectura. - El layout se cachea entre frames: si no cambia el árbol, el tamaño ni el texto de los campos (hover, scroll, animaciones, parpadeo del cursor), solo se vuelve a pintar. En una lista de 10 000 filas un frame sin reconstrucción pasa de ~1,2 s a ~30 ms.
- Al reconstruir la vista se reutiliza el shaping de los párrafos que no cambiaron (por contenido, no por key) junto con sus mediciones, que además se memorizan dentro de cada layout. El layout de esa misma lista al reconstruir baja de ~1,06 s a ~0,3 s.
- Al reconstruir, el árbol de layout se reconcilia por key en vez de rehacerse: solo se recalculan las ramas cuyo estilo de layout, texto, imagen o hijos cambiaron. Un cambio que solo afecta a la pintura (fondo, color de borde, sombra…) no rehace el layout. En la lista de 10 000 filas el layout al reconstruir baja a ~60 ms, y en el formulario de 500 filas a ~2 ms por tecla.
- Invalidación por fases (estilo → layout → composición → pintura): cada cambio rehace solo las fases que le afectan.
- Al reconstruir, el árbol nuevo se compara con el anterior: si nada afecta a la geometría (colores, fondos, opacidad, color de un texto…), no se rehace el layout y solo se repintan esos nodos. Cambiar la selección en la lista de 10 000 filas ya no hace layout (~57 ms): la comparación cuesta ~12 ms.
- Desplazar un
scrollsolo recoloca su capa: no rehace el layout ni las regiones. - Solo se repinta la zona que cambió (hover, transiciones, cursor, tooltip, capas movidas), sin recorrer lo que queda fuera, y la ventana solo presenta esa zona. Un frame sin cambios no pinta nada. En la lista de 10 000 filas, hover pasa de ~30 ms a ~1 ms, desplazar de ~36 ms a ~1,5 ms y un frame ocioso de ~31 ms a menos de 1 ms.
- Un
on_scrollque reconstruye la vista (listas virtuales) ya no obliga a pintar dos veces el mismo frame. - Un layout ya no repinta la ventana entera: se comparan por key las regiones nuevas con las anteriores y solo se repintan los nodos que cambiaron de geometría, de aspecto o de orden de pintura, más los que aparecen o desaparecen. Al escribir en el formulario de 500 filas, pintar pasa de ~0,42 ms a ~0,16 ms. Un cambio de tamaño de la ventana, un cambio que afecta a más de la mitad de los nodos o una zona dañada de más del 70 % de la ventana se siguen pintando enteros.
- Incompatible: un elemento sin
keyrecibe la de su padre seguida de su posición ("fila-7/0"), no la ruta completa desde la raíz. Los hijos de un elemento con key conservan su identidad (hover, foco, scroll) aunque el padre cambie de sitio. Un test que buscara un nodo por su ruta ("0/1/2") tiene ahora como prefijo la key de su ancestro con key más cercano. - Crear elementos y resolver tokens de tema es algo más rápido (un solo recorrido de los argumentos).
- Empaquetado para PyPI: las capturas del README se referencian con URLs absolutas (antes
relativas), así que se ven también fuera de GitHub;
Cargo.tomldeclararepository,homepageydocumentation(ya no avisa al empaquetar); y el sdist se limita con el whitelistincludedeCargo.tomla lo necesario para recompilar el paquete (antes arrastraba.github/,docs/,examples/,benchmarks/ytests/: 1,3 MB → 195 KB).
Corregido¶
- La ventana ya no muestra un destello blanco al abrirse: se crea oculta y aparece con su primer frame ya pintado, en vez de enseñar el fondo vacío del sistema mientras arranca la GPU y se construye la vista.
0.2.0 — 2026-10-15¶
Apps de verdad: scroll, teclado y foco, campos de texto, imágenes y más componentes.
Añadido¶
- Componentes
divider,avatar,checkbox,switch,tabs(con ← →) yselect(desplegable en capa superior, con scroll, Esc y ↑ ↓), enlazados aSignaldonde aplica. tooltip(child, tip)y la propiedadtooltip: burbuja tras medio segundo de hover, colocada dentro de la ventana, con colores heredablestooltip_bgytooltip_color;Pilot.tooltip.- Primitiva
image(src, fit=...): PNG, JPEG, WebP, GIF, BMP, ICO y SVG (vectorial) desde una ruta o desdebytes, con proporción automática,fit(contain,cover,fill,none), recorte a las esquinas redondeadas, caché de decodificación que detecta cambios en disco y errores claros (FileNotFoundError,ValueError). - Campos de texto
text_inputytextarea, enlazados en los dos sentidos a unSignal[str]: cursor que parpadea, selección con teclado y ratón (doble y triple clic), recorrido por grafemas y palabras, portapapeles, deshacer/rehacer agrupando lo escrito, composición con IME, placeholder,on_submit, desplazamiento horizontal en una línea y crecimiento con el contenido en varias. - Propiedades
placeholder_coloryselection_color; cursor de texto del ratón sobre los campos. Pilot.type(),ime_preedit(),ime_commit(),selection()yclipboard.- Teclado y foco: Tab / Shift+Tab en orden de pintura (atrapado dentro de barreras
capture), activación con Enter y Espacio, anillo de foco animado solo al navegar con teclado (focus_color, heredable) y desplazamiento automático delscrollpara mostrar el foco. on_keyconKeyEvent(key,text, modificadores,matches("mod+s")), que burbujea desde el foco hasta la raíz;on_focus,on_blur,focusableyautofocus.- Desplazamiento con flechas, RePág/AvPág, Inicio/Fin y Espacio.
Pilot.press(),key_down(),key_up(),focus()yfocused.- Contenedor
scroll(axis="y" | "x" | "both"): recorte, rueda con desplazamiento suave, trackpad, encadenado alscrollexterior en el límite y barra superpuesta que se oculta sola y se puede arrastrar. La posición se conserva entre reconstrucciones. - Propiedades
min_height,max_heightyscrollbar_color. Pilot.wheel(),Pilot.scroll()yPilot.scroll_offset().app.pilot(): piloto headless para tests de interacción (click,move_to,hovered,rect,settle,png…) que usa el mismo runtime que la ventana.Signal.setes seguro desde cualquier hilo y despierta el bucle de eventos: la ventana se actualiza sin esperar a un evento del usuario.app.spawn(coro): ejecuta corrutinas en un bucleasyncioen segundo plano.- Propiedad
layer="overlay": pinta un elemento y le da los eventos por encima de todo el árbol. - Manejadores genéricos: cualquier argumento
on_<evento>de un elemento es un manejador.
Cambiado¶
Element.to_spec()serializa los manejadores en"handlers": {"click": fn}en lugar de"on_click".Element.on_clicksigue disponible como propiedad de solo lectura.- El núcleo separa el estado de la app (
Runtime) del bucle de ventana. - El hit-testing respeta el recorte de los contenedores
scroll. - La demo
studio.pydesplaza el contenido de cada página y estrena búsqueda en el tablero, un formulario de nueva tarea (Ctrl+N y el botón lo abren en vez de añadir una tarea de ejemplo), una página de Ajustes y tooltips. - Nuevas dependencias del núcleo:
arboard(portapapeles) yunicode-segmentation. Las features deskia-safecambian por sistema (svg,webpy, fuera de macOS,ganesh+vulkan) para seguir usando binarios precompilados de Skia.
Corregido¶
examples/studio.py --pngya no falla en consolas de Windows sin UTF-8.
0.1.0 — 2026-10-15¶
Primera versión: la base del framework.
Añadido¶
- Núcleo nativo en Rust expuesto como
aurora._core(PyO3 + maturin, wheel abi3 para Python ≥ 3.10). - Layout flexbox con Taffy:
direction,gap,padding,align,justify,grow,shrink,wrap, tamaños en píxeles o porcentaje,min_width/max_widthyposition="absolute"contop/right/bottom/left. - Render con Skia: esquinas redondeadas, bordes, sombras difuminadas, gradientes lineales y opacidad.
- Texto con shaping real, fallback de fuentes, herencia de estilos de texto, alineación por baseline,
max_linescon elipsis ytext_align. - Ventana nativa con winit; el bucle de eventos corre sin el GIL.
- Estados hover y pressed animados en el núcleo (
bg_hover,bg_pressed,press_scale). - Hit-testing con burbujeo al ancestro interactivo y barreras de eventos (
capture). - API declarativa:
box,row,column,text,spacer, hijos con[...],keyyon_click. Signalpara el estado reactivo.- Temas con tokens (
"$primary"), temasMIDNIGHTyDAYLIGHT, y tema reactivo víaSignal[Theme]. - Componentes:
button(variantesprimary,soft,ghost),card,badgeystat. App.render_png()para renderizar sin ventana.- Ejemplos:
hello.py,studio.py(demo completa) ybrand.py(genera las imágenes del README). - Documentación: guía, referencia, arquitectura, visión y roadmap.