Commits
- Commit:
97b83a82103197915fefaa3b0daf649f14f57280- From:
- ale <ale_bnes@tuta.com>
- Date:
flake.nix: agrega packages.default — paquete Nix real, no solo devShell
Hasta ahora solo había devShells.default (venv + pip, pensado para
desarrollar/debuggear). Para poder declararlo en la config de NixOS como
un paquete normal (mismo patrón que usa Ale para sus otros proyectos
propios, ej. inputs.psysonic.packages.${system}.default en /nixdots) hace
falta un output packages.${system}.default de verdad.
Se arma con python313Packages.buildPythonApplication usando las deps de
nixpkgs directamente (textual, pyfiglet, matplotlib, textual-image y
flatlatex ya estaban empaquetadas ahí) — solo cowsay (el paquete de
Python) no está, así que se empaqueta a mano (wheel puro de PyPI, sin
deps propias, build corto).
Ventaja sobre el devShell: numpy/matplotlib de nixpkgs ya vienen enlazados
correctamente contra libstdc++, así que este paquete NO necesita el fix
de LD_LIBRARY_PATH que sí hace falta en el devShell (venv + pip instala
wheels compilados que esperan rutas FHS que no existen en NixOS).
Confirmado con 'nix build .#default' + prueba de imports (matplotlib,
textual_image, flatlatex, cowsay, nezzontli_ctl) sin exportar nada a mano.
El devShell queda intacto para seguir desarrollando/debuggeando igual que
hasta ahora.
- Commit:
cb763040fa838bc539204b648b8769dfd2ba972b- From:
- ale <ale_bnes@tuta.com>
- Date:
Agrega wrapper bin/ctl para comando global (evita el bug de pipx en nixpkgs)
pipx no está en el cache binario de nixpkgs para este snapshot — su propio
test suite tiene un bug (parametrize mal armado en test_inject.py) que
rompe el build desde cero. Y aunque se lo evite, un pipx install plano
tampoco alcanzaría: la app necesita libstdc++.so.6 (numpy, para el
renderizado de LaTeX del preview) que en NixOS solo está en el
LD_LIBRARY_PATH que arma el shellHook del flake.
bin/ctl es un wrapper chico que resuelve su propia ubicación (para andar
symlinkeado desde ~/.local/bin), se para en el directorio del repo, y
delega todo a 'nix develop . --command ctl' — reusa el flake real en vez
de duplicar su lógica, así que no se puede desincronizar. Actualiza el
README con la instalación (symlink en vez de pipx) y el estado real del
proyecto (menú de dos niveles, eliminar, preview LaTeX/imágenes).
- Commit:
50855c87a04309240229d68af9ce3ce5bab7faf2- From:
- ale <ale_bnes@tuta.com>
- Date:
Confirmación de eliminar: una sola pregunta, diálogo centrado; centra el mensaje de cowsay
ConfirmDeleteScreen pasa de doble confirmación a una sola (el usuario pidió
revertir eso), y en vez de reusar .form-container (pensado para formularios
que ocupan toda la pantalla) ahora es un diálogo compacto de ancho fijo (64
cols) centrado con align: center middle sobre la propia screen.
#success-container (el cowsay del final) le faltaba width: 100% — sin eso
'align: center middle' no tenía espacio extra para centrar contra, así que
quedaba pegado a la izquierda. Se agrega, más content-align: center middle
en el Static del arte ASCII para que el bloque de texto multilínea también
se centre horizontalmente dentro de su caja.
- Commit:
67e37ea4e902fd4b618e5df04e97d18df742d5ab- From:
- ale <ale_bnes@tuta.com>
- Date:
Menú de dos niveles (Crear/Editar) + eliminar entradas con doble confirmación
El menú principal pasaba de 4 a 9 opciones con post/page/album duplicados
entre crear y editar — más difícil de escanear. Ahora es: Crear nuevo…,
Editar existente…, Configuración, Salir; cada uno abre un submenú con las
3-4 opciones específicas y un '← Volver'. 'Agregar fotos a un álbum
existente' se movió a Editar (opera sobre un álbum ya creado).
Se agrega la opción de editar páginas extra de un proyecto (ej.
content/blog/cortometraje/rama1.md) — antes solo se podía editar el
index.md de cada proyecto, no las páginas extra generadas con 'page'.
list_extra_pages() las lista, NewPageScreen ahora acepta existing_file
igual que NewPostScreen (oculta proyecto/slug, precarga título/descripción/
tags/páginas relacionadas, preserva la fecha original).
Se agrega un botón 'Eliminar' en los tres formularios de edición (post,
página, álbum) con confirmación DOBLE (ConfirmDeleteScreen, mismo modal
pregunta dos veces antes de devolver True) antes de borrar y pushear:
- post: borra content/blog/<slug>/ completo (incluye sus páginas extra)
- página: borra solo ese .md, no toca el resto del proyecto
- álbum: borra content/photos/<slug>/ Y static/images/<slug>/ (las fotos
en sí, para no dejar basura LFS huérfana)
Probado de punta a punta contra un fixture git descartable (bare repo
como origin) para los tres flujos de borrado: confirma que el path
correcto se borra del disco, se commitea y se pushea al origin.
- Commit:
418394b83f2ec0036a9953fffbf57be5c0ec36e4- From:
- ale <ale_bnes@tuta.com>
- Date:
Editor: posiciona el cursor al inicio del documento al abrir, no al final
Tenía sentido para posts nuevos (arrancar escribiendo al final de un
frontmatter ya armado), pero al editar un archivo existente el usuario
quiere empezar viendo el principio del documento, no el final.
- Commit:
b1bb43919e8bed91acc21b4bb94d0ad9d34e3105- From:
- ale <ale_bnes@tuta.com>
- Date:
Preview: no parte los bloques de código ``` en varios widgets
split_paragraphs_with_offsets (agregado para el sync por párrafo) cortaba
por cualquier línea en blanco, sin distinguir si estaba dentro de un
bloque ```código``` — muy común tener líneas en blanco entre funciones/
comentarios en código real. Cada pedazo se montaba como su propio widget
Markdown con su propio margen, así que el bloque se veía hecho pedazos con
huecos enormes en vez de un solo bloque contiguo.
Ahora un bloque ```...``` se detecta primero y se trata como un párrafo
atómico — nunca se parte por las líneas en blanco que tenga adentro. El
texto antes/después del bloque sigue partiéndose en párrafos finos para
el sync, como antes.
- Commit:
51d10b6fa8a8b60a714a894943377221cc93e435- From:
- ale <ale_bnes@tuta.com>
- Date:
Sync del scroll: un anchor por párrafo (no solo por imagen/fórmula) y arregla el salto de imágenes
Dos bugs quedaban con el sync por línea-ancla:
1. Se anclaba CADA widget de un grupo (imagen + su caption, o cada foto de
una gallery en la misma línea de origen), y el algoritmo de selección
se queda con el ÚLTIMO anchor que empata esa línea — al llegar ahí el
scroll saltaba directo a la caption (o a la última foto), dejando la
imagen en sí arriba, fuera de vista. Ahora es un solo anchor por línea
de origen distinta, apuntando siempre al primer widget ahí.
2. Un tramo largo de texto plano sin imágenes/fórmulas en el medio se
montaba como un único widget Markdown gigante con un solo anchor — así
que después de la última fórmula/imagen del documento, no había más
anchors y el preview se quedaba pegado ahí por el resto del documento
por más que se siguiera bajando en el editor. Ahora cada párrafo
(separado por línea en blanco) es su propio widget con su propio
anchor, repartidos parejo en todo el texto y no solo donde hay medios.
_render_segment ahora devuelve (widget, línea_o_None) en vez de solo
widgets — None para los que no deben ser su propio punto de anclaje
(la caption de una imagen).
- Commit:
0bb0d5a98d03a51974236b5e5f6b8acc2d5118a0- From:
- ale <ale_bnes@tuta.com>
- Date:
Sync del scroll: usa la línea lógica real, no la fila visual (rompía con wrap)
scroll_y de TextArea está en filas VISUALES, no en líneas lógicas del
documento — con soft_wrap (el default, y lo normal para escribir párrafos
de blog como una sola línea larga) una línea lógica ocupa varias filas
visuales. Los anchors del preview se calculan en líneas lógicas
(text.count('\n', ...)), así que compararlos directo contra scroll_y
(filas visuales) se desalineaba cada vez más division a medida que había
párrafos largos arriba en el documento — exactamente el desfase que se
seguía viendo.
TextArea.wrapped_document.offset_to_location() traduce la fila visual de
vuelta a la línea lógica real (confirmado con params: fila=45 → línea 16,
no fila=45 → línea 45), que es la misma unidad de los anchors.
- Commit:
54fd27f205edbde15f090faeab6df8bd63c321ec- From:
- ale <ale_bnes@tuta.com>
- Date:
LaTeX inline: aproxima a texto Unicode en vez de romper el párrafo en imágenes
El problema real no era el tamaño de la imagen sino que CADA $...$ inline
generaba un widget de imagen separado, partiendo una oración en varios
bloques apilados (texto/imagen/texto/imagen) — se perdía la lectura fluida
sin importar qué tan chica fuera la imagen.
Ahora el $...$ inline se reemplaza en el lugar por su aproximación en
texto Unicode (griego, sub/superíndices, ·, ×, →, √, fracciones simples,
etc.) vía flatlatex, así el párrafo sigue siendo un solo bloque de Markdown
que fluye normal. Se agrega preprocesamiento para los casos que flatlatex
no cubre (\sin/\cos/\log/..., \text{}/\mathrm{}, \left/\right, \to).
El bloque $$...$$ (que ya vive aislado en su propio párrafo en el
Markdown fuente) sigue renderizándose como imagen real vía matplotlib —
ahí sí tiene sentido, es una ecuación destacada, no texto corrido.
tokenize() ahora devuelve (texto_mostrado, segmentos) en vez de solo
segmentos, porque los offsets de línea para el sync del scroll deben
calcularse sobre el texto YA sustituido, no el original (la sustitución
cambia longitudes de línea pero nunca la cantidad de saltos de línea).
- Commit:
998bc9b164edf78f09d9b800f3e31f4ffba2b63a- From:
- ale <ale_bnes@tuta.com>
- Date:
LaTeX del preview: renderiza con fondo transparente en vez de cartel blanco
matplotlib.mathtext.math_to_image() no expone transparent=True, así que
savefig() terminaba pintando un fondo blanco sólido — se veía como un
recuadro pegado encima del preview oscuro, con el texto crema casi
ilegible contra el blanco. Se reimplementa el mismo procedimiento (parser
+ Figure.text + savefig) que usa esa función internamente, pasando
transparent=True, para que la fórmula se vea como texto renderizado
flotando sobre el fondo del tema en vez de una imagen con marco.
Se versiona la clave de caché (v2) para invalidar los renders viejos con
fondo blanco que ya estaban en disco.
- Commit:
9ee28605fccfb2af048a3d221c9a573f44c44534- From:
- ale <ale_bnes@tuta.com>
- Date:
Preview: detecta el protocolo de imágenes antes de que Textual tome la terminal, sync por línea-ancla, indicador de carga
Causa real del renderizado pixelado/borroso: textual_image negocia Kitty
TGP/Sixel mandando escape sequences y leyendo la respuesta del terminal,
pero esa negociación solo funciona ANTES de que Textual ponga la terminal
en raw mode + alt screen. Como el import era lazy (recién al abrir el
editor), la negociación fallaba y caía al renderer de half-cell (bloques
de color de baja resolución — exactamente lo que se veía). Se fuerza el
import arriba de todo en app.py, antes de NezzontliApp().run().
El sync de scroll entre el TextArea y el preview era por proporción de
alturas, que se rompe en cuanto el preview tiene bloques de altura fija
(imágenes, fórmulas) distintos a la del texto — quedaban desfazados y al
volver a editar no se veía la línea correspondiente. Ahora se ancla por
línea de origen real: TextArea es un widget de líneas monoespaciadas así
que scroll_y ya es el número de línea top, y se busca qué widget del
preview corresponde a esa línea (tokenize ahora devuelve el offset de
cada segmento) en vez de escalar por altura.
También: indicador '⏳ Renderizando...' mientras se resuelven imágenes/
fórmulas, y el render de cada segmento corre en paralelo (asyncio.gather)
en vez de secuencial, para que abrir el editor no se sienta tan lento.
- Commit:
eabf2e720ed1f9f352c93c56ace8614e9237a0c0- From:
- ale <ale_bnes@tuta.com>
- Date:
Preview del editor: renderiza LaTeX e imágenes (locales/remotas), fija libs de NixOS
El preview de Markdown ahora tokeniza fórmulas $...$/$$...$$ (renderizadas
a PNG vía matplotlib.mathtext) e imágenes referenciadas por markdown estándar
o por los shortcodes de Zola (photo/gallery/image), mostrándolas con
textual-image (Kitty/Sixel/Unicode). El rebuild del preview corre debounced
y en un worker para no bloquear la UI con el render/descarga.
flake.nix agrega libstdc++/zlib al devShell — los wheels compilados de
numpy (dependencia de matplotlib) los buscan en rutas FHS que no existen
en NixOS.
- Commit:
4b3dbef4eb99c9a691e02a0ec65087a5a749c7d0- From:
- ale <ale_bnes@tuta.com>
- Date:
Editar entradas existentes (post/página/álbum) + fixes de UI
- screens/post.py: NewPostScreen acepta existing_file, precarga
campos y cuerpo real, escribe sobre el mismo archivo en vez de
crear uno nuevo (oculta el campo slug, no aplica al editar)
- screens/edit_album.py: título/descripción/tags/cuerpo de un álbum
existente (las fotos se siguen agregando por "Agregar fotos")
- screens/select_existing.py: picker genérico reutilizado por ambos
- menu.py: dos opciones nuevas ("Editar un post..." / "...álbum")
- content.py: parse_frontmatter() (tomllib) para leer el TOML
existente al editar
Bugs encontrados al probar edición real:
- La fecha se pisaba con la de hoy en cualquier edición (dt=None por
default, nunca se pasaba la fecha original) — agregado _toml_date()
que además tolera fechas guardadas como string entre comillas
(pasó con un post tocado antes por Sveltia CMS; Zola lo acepta
igual, tomllib lo lee como str en vez de date)
- El preview de Markdown en EditorScreen no scrolleaba con la rueda
del mouse: el widget Markdown no es scrolleable por si solo
(extiende Widget, no ScrollableContainer). Envuelto en un
VerticalScroll, y agregado sync proporcional real vía
self.watch(text_area, "scroll_y", ...) — el "eco" de scroll_y no
tiene el mismo problema de timing que tuvo el de slug porque acá
se usa watch() en vez de comparar valores manualmente
- Sacado el "— Ctrl+S para guardar, Esc para cancelar" del título del
editor (ya está en el Footer de abajo, quedaba duplicado)
- Commit:
415a550db68b43a152ad5b756f4b919ae67f3045- From:
- ale <ale_bnes@tuta.com>
- Date:
Arregla el autocompletado de slug (se pegaba en la primera letra)
El "eco" del Input.Changed que dispara asignar slug-input.value
programáticamente se procesa async, así que una bandera que se
prendía y apagaba en el mismo handler síncrono no llegaba a tiempo:
para cuando se procesaba el evento de eco, la bandera ya estaba en
False de nuevo, y quedaba marcado como "tocado a mano" tras la
primera letra. Ahora compara el valor contra el último slug que puse
yo mismo, en vez de una bandera transitoria.
- Commit:
220472d4062987e9fe737a17983ac824114b2ec7- From:
- ale <ale_bnes@tuta.com>
- Date:
Fija flake.lock
- Commit:
9674a8f813d32178fc9de63eedffd15586af6c07- From:
- ale <ale_bnes@tuta.com>
- Date:
App Textual completa: menú, editor con preview, 4 flujos, cowsay
- app.py + theme.py: tema Gruvbox (misma paleta que nezzontli.xyz)
- screens/menu.py: banner Figlet + OptionList
- screens/editor.py: TextArea(markdown) + Markdown preview en vivo,
reemplaza abrir $EDITOR externo
- screens/post.py, page.py, album.py, add_photos.py: los 4 flujos,
con Select/SelectionList/DirectoryTree en vez de texto libre donde
aplica
- screens/confirm.py, success.py: diff+preview+commit/push real,
pantalla final con cowsay (animal configurable)
- screens/settings.py, folder_picker.py
Validado de punta a punta contra un clone local descartable (bare +
working copy, no el repo real): los 4 flujos completos, con commit y
push reales, y confirmado con `git show :ruta` que las fotos quedan
como punteros LFS reales, no blobs crudos. Encontrado y arreglado en
el camino: commit_and_push no manejaba con gracia un `git commit` sin
nada para comitear (podía tirar abajo el worker); el botón de
confirmar ahora se deshabilita al primer click para evitar envíos
duplicados.
`pipx install -e .` no se pudo validar tal cual: pipx falla al
compilarse desde este snapshot de nixpkgs (bug de su propio test
suite, no relacionado a este proyecto). El entry point `ctl` sí se
probó directo vía `.venv/bin/ctl` y funciona.
- Commit:
bd3729dd6f06c79e0afe271673ff815f499637fc- From:
- ale <ale_bnes@tuta.com>
- Date:
Estructura inicial: pyproject.toml, config/content/git_ops, flake.nix
Proyecto separado de website — opera sobre ese repo (NEZZONTLI_REPO_PATH)
en vez de vivir adentro. En desarrollo, todavía no reemplaza a
website/scripts/ctl.py.
