# Plan — apps de usuario final (montón A del ADR 0015), MEDIDO **Fecha de la medición:** 2026-09-03 · **Catálogo contra el que se midió:** 1079 recetas (corpus + las cuatro colas) · **Instrumento:** `makedepends`+`depends` del APKBUILD de Alpine de cada candidata, cruzados contra los nombres de receta que existen en el disco. ## Por qué existe este documento El [ADR 0015](adr/0015-imagenes-ajenas.md) parte las apps que faltan en tres montones y dice del **A** —Firefox, Chromium, LibreOffice, mpv, GIMP, Inkscape, Blender, OBS— que no lo bloquea *«nada estructural: son Wayland-nativos y compilables. Falta escribirlas»*, y propone el orden **mpv → OBS → Firefox**. `mpv` se escribió el 2026-09-03 y esa parte se confirmó. Al ir por el segundo, la medición **desmiente la premisa para el resto del montón**: OBS y Firefox no están a una receta de distancia, están detrás de muros que esta distro levantó a propósito. Este documento deja el dato escrito para que la decisión se tome mirándolo y no de memoria. ## Cómo leer la tabla Dos columnas, y **la que importa es la segunda**: - **recetas que faltan** — cuántos `makedepends` de Alpine no tienen receta acá. Es una **cota superior floja**: Alpine construye con todo activado y nosotros no. El control lo da `mpv`, que YA ESTÁ SELLADA y aun así aparece con 16 faltantes — son exactamente las perillas que su receta apaga a propósito (jack, sndio, libbluray, zimg, rubberband…). O sea: un número alto no prueba que sea caro. - **muros de diseño** — dependencias que NO son «una receta más», sino decisiones ya tomadas de esta distro: GTK3 aparcado, X11 al tacho, Qt6 que vive sólo en `incoming-kde`, toolchain wasi inexistente. **Un muro no se paga escribiendo una receta: se paga cambiando una decisión.** ## La tabla | app | faltan | muros | veredicto | |---|---:|---|---| | ~~wf-recorder~~ | — | — | ✅ **SELLADA 2026-09-03** (b3:25caa359), con audio. Se destrabó al promover pipewire al corpus. Sólo en `escritorio-sway`: captura por `wlr-screencopy` y KWin/mutter no lo implementan. | | ~~imv~~ | 7 | **GL de escritorio** | ❌ **BLOQUEADA 2026-09-03** — el muro no lo veía este método; ver abajo. Reemplazada por `swayimg`, sellada el mismo día. | | ~~swayimg~~ | — | — | ✅ **SELLADA 2026-09-03** (b3:9bcd39c1, **5.5**), en las CUATRO imágenes. Visor sin GL: rasteriza a `wl_shm`. Trae backend DRM de yapa. | | ~~luajit~~ | — | — | ✅ **SELLADA 2026-09-03** (b3:115d4c1c). Escrita para destrabar swayimg 5.x; mpv también la prefiere. | | ~~zathura~~ | — | — | ✅ **SELLADA 2026-09-03** (b3:9c5eac92) + `zathura-pdf-poppler`. **Es GTK4, no GTK3**: upstream vació girara. En gnome/cosmic/sway; **NO en KDE**, por colisión de poppler. | | ~~mupdf~~ | 9 | X11 **y GL de escritorio** | ❌ **BLOQUEADA 2026-09-03.** La nota era falsa: `mupdf-gl` **sí** es un muro — su `platform/gl/` va sobre GLUT + OpenGL de escritorio, el mismo muro que imv. mupdf **no tiene visor Wayland**: sus dos UIs son `mupdf-x11` y `mupdf-gl`. El motor (`libmupdf`+`mutool`) no tiene muros, pero no hace falta: el PDF ya lo da poppler. | | ~~inkscape~~ | 14 | **GTK3** (en la release) | ❌ **BLOQUEADA 2026-09-03.** Era GTK3 encubierto, como se sospechaba: `INKSCAPE_1_4_4` pide `gtkmm-3.0>=3.24` **y** `gtk+-3.0>=3.24`. `master` **sí** migró a GTK4 (`gtk4>=4.14`, `gtkmm-4.0>=4.13.3`) pero está sin publicar; y de todos modos el catálogo **no tiene NI UN binding C++**: faltan sigc++, glibmm, cairomm, pangomm y gtkmm enteros. | | *mpv* | *16* | *X11* | *YA SELLADA — es el control del método, no una candidata.* | | **gimp** | 18 | **GTK3**, X11 | GIMP 2.10 es GTK2/3. Fuera de alcance mientras GTK3 esté aparcado. | | **obs-studio** | 20 | **Qt6**, X11 | ver abajo. | | **firefox** | 26 | **GTK3**, X11, **wasi** | ver abajo. | | **chromium** | 38 | **GTK3**, **Qt6**, X11 | peor que Firefox por todos lados. | | **libreoffice** | 59 | **GTK3**, **Qt6**, X11 | 59 recetas y tres muros. No es un ticket, es un año. | ## ⚠ El defecto del método, encontrado al usarlo (y lo que destapó) La columna «recetas que faltan» pregunta **¿existe esta receta en el disco?** y esa es la pregunta equivocada. La correcta es **¿la alcanza el consumidor?** — porque una receta resuelve sus deps sibling-first (su propia cola) y después el catálogo PADRE, **nunca una cola hermana**. Se descubrió al ir a escribir `wf-recorder`, que la tabla daba en 0/0: su `makedepends` pide `pipewire-dev` y `pulseaudio-dev`, los dos existen… **en las tres colas de escritorio y en ninguna en el corpus**. Una app del corpus no los ve. wf-recorder construye igual (`-Dpulse=disabled -Dpipewire=disabled`, `have_audio=false`) pero graba **sin sonido**, y no tiene backend ALSA con el que caerse. ### Lo que eso destapa, que es más grande que wf-recorder > **El corpus no tiene ningún cliente de audio salvo ALSA.** `libpipewire-0.3` y `libpulse` viven > sólo en `incoming-{kde,gnome,cosmic}`. Toda app multimedia que llegue al corpus va a chocar acá. Ya chocó dos veces en un día: `mpv` terminó con `--ao=alsa` en vez de su salida nativa de PipeWire (y por eso hubo que dar vuelta `-Dpipewire-alsa` en las tres pipewire, que fue el arreglo correcto pero fue un rodeo), y ahora `wf-recorder` se queda mudo. **DECIDIDA Y HECHA el 2026-09-03 — opción 1, con una salvedad medida.** Lo que sigue queda como registro de por qué. La decisión era: promover UNA pipewire al corpus. Construye —el corpus tiene su propia `glib`, `dbus` y `pcre2`— pero el problema no es construirla: es que una imagen que hidrate `corpus/pipewire` **y** la de su cola pondría dos `libpipewire-0.3.so` distintas en la misma ruta, que es la enfermedad de las dos glib. Las salidas posibles, para pensarlas despierto: 1. **Una sola pipewire, en el corpus, y las tres colas jubilan la suya.** Es lo más limpio y es lo que ya se hizo con otras 13 copias que sellaban igual — pero éstas NO sellan igual (difieren en glib/dbus/pcre2), así que hay que decidir cuál gana y volver a sellar wireplumber y los portales de las tres colas. 2. **Aceptar ALSA como la única ABI de audio del corpus**, que es lo que hoy quedó funcionando de hecho: `-Dpipewire-alsa=enabled` hace que un cliente ALSA desemboque en PipeWire. Barato y ya está hecho; el precio es que las apps del corpus nunca usan la API nativa (sin control por-stream, sin metadata de sesión) y que las que sólo hablan pulse/pipewire —wf-recorder— quedan afuera. 3. **Que las apps multimedia vivan en las colas**, una copia por escritorio. Es lo que la estructura empuja hoy y es exactamente lo que no queremos para una app que sirve a las cuatro imágenes. ### Cómo se resolvió Ganó la **opción 1**, y salió barata porque la variante de COSMIC ya resolvía su cierre entero contra el catálogo padre: `recipes/pipewire.toml` sella el MISMO hash que tenía en la cola. Con ella subieron `libsndfile` y `dbus-shared` (duplicados exactos). 9 copias de cola jubiladas, **5 rebuilds**. Lo que hizo segura la promoción fue una medición, no un argumento: **ningún pipewire enlaza glib** —sus NEEDED son `libdbus-1.so.3`, `libpulse.so.0`, `libsndfile.so.1`— así que `libpipewire-0.3.so` se comparte entre las cuatro imágenes sin repetir el episodio de las dos glib. **Y la salvedad, que es la parte que valía la pena medir:** `pulseaudio` NO se promovió. `libpulse-mainloop-glib.so.0.0.6` mide **45.712 bytes** en la variante de GNOME (NEEDED `libglib-2.0.so.0`) y **2.455.600** en la del corpus, que se traga la glib ESTÁTICA. Esa dentro del proceso de gnome-shell —que ya carga la glib sombra dinámica— es el cuadro de colord. Las tres pulseaudio se quedan: son variantes deliberadas. Sin esa salvedad los rebuilds habrían sido 8, incluido `gnome-shell`. La regla general que sale de acá, y sirve para la próxima promoción: > **Enlazar contra una variante y correr contra otra es legítimo cuando lo que cruza es un SONAME** > (misma ABI). Lo que no se puede es **hidratar dos artefactos distintos en la misma ruta**. Por eso > pipewire se puede compartir y pulseaudio no. Consecuencias inmediatas: `mpv` pasó a `-Dpipewire=enabled` (con ALSA detrás, para imágenes sin demonio) y `wf-recorder` se escribió y selló el mismo día. ## ⚠ EL TERCER DEFECTO DEL MÉTODO: la receta existe, es alcanzable, y aun así no sirve El defecto anterior era «¿existe la receta?» ≠ «¿la alcanza el consumidor?». Al ir a escribir `imv` apareció un **tercer eje**, y es el peor de los tres porque las dos preguntas anteriores dan que sí: > **La receta existe, el consumidor la alcanza, y la VARIANTE que se construyó no publica la ABI > que hace falta.** `imv` pide OpenGL. `mesa` existe, está en el corpus, y cualquier receta lo alcanza. Pero: - `imv/src/canvas.c` hace `#include ` y dibuja con `glBegin` / `glOrtho` / `GL_TRIANGLE_FAN` / `GL_TEXTURE_RECTANGLE`, o sea **OpenGL de función fija**, perfil de compatibilidad. No existe equivalente en GLES: no es una perilla, es otra API. - Su `meson.build` hace `gl_dep = dependency('gl', required: false)` y, si no la encuentra, `dependency('opengl')` **sin `required: false`** ⇒ el configure muere. - Y esta distro **no tiene ningún proveedor de OpenGL de escritorio**. Medido en los artefactos sellados, no supuesto: las **tres** variantes de mesa (`mesa`, `mesa-swrast`, `mesa-llvmpipe`) llevan `-Dglx=disabled -Dglvnd=false`, y sus artefactos publican `libEGL.so.1`, `libGLESv2.so.2`, `libgbm` y `egl.pc`/`glesv2.pc`/`gbm.pc` — **ni un `libGL.so*`, ni `gl.pc`, ni `opengl.pc`**. No hay receta `libglvnd` en ninguna cola. **La trampa fina, y es la parte que hay que recordar:** mesa SÍ instala `/usr/include/GL/gl.h` (el header viene con `-Dopengl=true`). O sea que una app así **compila entera** y muere recién al LIGAR. Un triaje que mire headers da verde. ### Qué costaría darlo vuelta (para que la decisión se tome con el número) El camino que el propio `meson.build` de imv nombra —«libglvnd fallback for pure-wayland systems»— es autorar `libglvnd` (que provee `libOpenGL.so`/`opengl.pc`) y reconstruir mesa con `-Dglvnd=true`. Eso es **radio 139 dependientes directos** sobre `mesa`, y además convierte a libglvnd en pieza obligatoria de las cuatro imágenes: sin ella no hay EGL. Es una decisión de distro, no un ticket de visor de imágenes. ### La salida, que resultó mejor que el original `swayimg` cubre el mismo caso de uso **sin tocar GL**: rasteriza a un buffer y lo entrega por `wl_shm`. Sus deps requeridas (xkbcommon, fontconfig, freetype2, wayland, wayland-protocols) ya estaban todas en el corpus ⇒ **cero recetas nuevas**. Y trae un backend **DRM** que muestra imágenes en una TTY pelada, sin compositor, que es exactamente lo que le falta al instalador. Entró primero como **4.7** —la última sin Lua— porque desde la 5.0 `dependency('luajit')` es obligatoria y no había receta (la `lua5.2` del OSC de mpv es otra ABI). Se escribió `luajit` el mismo día y subió a **5.5**. La cadena quedó verificada de punta a punta: `swayimg -e` reporta `Lua 5.1 · jit=true · LuaJIT 2.1.1787165859`, que es exactamente el `relver` de nuestra receta. `luajit` queda además disponible para mpv, que la prefiere sobre 5.2. **Regla que sale de acá, para el próximo triaje de apps:** la pregunta no es «¿existe la receta?» ni sólo «¿la alcanza?», sino **«¿la variante sellada publica el `.pc` y el `.so` que el consumidor pide?»**. Se contesta mirando el artefacto, no el catálogo. Y como acá cada punto ciego se convierte en un guardián, esa pregunta ya tiene instrumento: **`scripts/provee.py`**, que indexa los `.pc` y las librerías de TODOS los artefactos sellados y contesta quién publica cada nombre y desde qué colas se alcanza. Los tres defectos del método caen con un solo comando: ```sh scripts/provee.py --desde corpus gl opengl # ✗ nadie lo publica → exit 1 (el caso imv) scripts/provee.py exiv2 librsvg # ✓ existen, alcanzables SÓLO desde su cola ``` Es el hermano de build de `vigia-sonames.py`, que cubre la mitad de runtime. **El triaje de la próxima app empieza por acá**, no por contar `makedepends` contra `recipes/*.toml`. ## El montón A quedó AGOTADO de cosas sin muro (2026-09-03) Con `zathura` cerrada, se re-triaron con `scripts/provee.py` las tres candidatas que la tabla daba como baratas o dudosas. **Las tres son muro**, y ninguna lo era por el motivo que decía la tabla: - **imv** → no hay proveedor de OpenGL de escritorio (sección de arriba). Sustituida por `swayimg`. - **mupdf** → sus DOS visores son X11 o GLUT+OpenGL de escritorio. No tiene UI Wayland. - **inkscape** → la release es GTK3; el `master` con GTK4 está sin publicar y además pide toda la torre de bindings C++ que este catálogo no tiene (`provee.py` da ✗ en sigc++-2.0, glibmm-2.4, cairomm-1.0, pangomm-1.4, gtkmm-3.0 **y** gtkmm-4.0). **Todo lo que queda del montón A está detrás de UNA de cuatro decisiones**, no de trabajo de recetas: GTK3 (inkscape-release, gimp, firefox), Qt6-en-el-corpus (obs), OpenGL de escritorio (imv, mupdf-gl) o X11 (chromium, mupdf-x11). Ver «los dos veredictos» abajo y el orden propuesto. ## ⚠ Lo que SÍ estaba roto y no era falta de recetas: imágenes sin terminal Al terminar zathura se cruzó la membresía de los cuatro perfiles contra una lista de emuladores de terminal, editores y gestores de ficheros. El resultado no tiene nada que ver con el montón A y es más grave que cualquier app que falte: | perfil | terminal | editor | archivos | |---|---|---|---| | `escritorio-cosmic` | cosmic-term | cosmic-edit | cosmic-files | | `escritorio-sway` | foot | vim | — | | `escritorio-gnome` | **—** | **—** | — | | `escritorio-kde` | **—** | **—** | — | Es la lección de `foot` otra vez y a mayor escala: sway llegó a 121/121 **sin emulador de terminal** porque nadie lo declaraba, y la métrica de clausura no lo puede ver porque mide lo DECLARADO. **GNOME arreglado el 2026-09-03 con CERO recetas nuevas**: `foot` y `helix` viven en el corpus, ya estaban selladas y **no las declaraba ningún perfil**. foot es Wayland puro sobre `xdg-shell` (no pide protocolos de wlroots) así que corre bajo mutter; su único NEEDED es `libc.so`. **KDE es un hallazgo aparte y NO se tocó**: tiene **22 apps con `.desktop` selladas en su cola y sin declarar** —konsole, dolphin, kate, okular, spectacle, ark, gwenview, systemsettings, kcalc…— y además le faltan `plasma-desktop`, `plasma-nm`, `plasma-pa`, `powerdevil` y `kscreen`, o sea red, audio, energía y pantalla. Su perfil son **13 raíces de ARRANQUE EN METAL**, no la imagen de escritorio completa (ésa se hidrató aparte, por hash). Reordenarlo es decisión del frente KDE. ## Los dos veredictos que corrigen al ADR ### Firefox NO es montón A Su APKBUILD pide `gtk+3.0-dev` — **Firefox no tiene backend GTK4 ni lo va a tener a corto plazo**, y GTK3 es una de las tres deudas que el frente GNOME aparcó por diseño. ⚠ **CORREGIDO 2026-09-03: el X11 de Firefox NO es un muro.** Se listaba como tal por los `libxt` y `libxcomposite` del APKBUILD, pero en el árbol de Gecko todo el código X11 está detrás de `CONFIG["MOZ_X11"]` (`widget/gtk/moz.build:131`): es una PERILLA de build, no una dependencia estructural, y hay builds Wayland-only. El muro real de Firefox es **GTK3**, más un **toolchain wasi** (`wasi-sdk`, `wasi-compiler-rt`) que no existe en el corpus, más `nodejs`, `icu`, `nss/nspr`, `clang/llvm/lld` versionados y `cbindgen`. La memoria del repo decía que a Firefox «le falta subir el techo MSRV». Eso es cierto **y es lo menor**: el bloqueo real es GTK3. **Consecuencia estratégica, que es la decisión que hay que tomar despierto:** todos los navegadores del mundo Linux son o GTK3 (Firefox y derivados) o Chromium (que además arrastra Qt6 y GTK3 en Alpine). Entonces **«tener navegador» no es un ticket del montón A**: es elegir entre 1. **autorar el stack GTK3** —revierte una decisión de diseño y reabre GIMP/Inkscape de paso—, o 2. **darle el navegador a [qorpa](adr/0015-imagenes-ajenas.md)**, o sea tratarlo como montón B: el navegador corre en la imagen ajena, hablando Wayland por socket. La opción 2 es coherente con todo lo demás que ya se decidió, y tiene una ventaja que conviene nombrar: el navegador es *justamente* el proceso al que menos ganas dan de darle el sistema entero. ### OBS no puede ser una app del corpus Pide `qt6-qtbase-dev`, `qt6-qtbase-private-dev` y `qt6-qtsvg-dev`, y **Qt6 vive sólo en `incoming-kde/`**. Una receta resuelve sibling-first y después el catálogo padre: desde el corpus no se alcanza una cola. O sea que OBS hoy sólo puede existir **dentro de la cola de KDE**, y entonces no es una app de las cuatro imágenes sino una app de KDE. Súmese X11 (`libx11`, `libxcb`, `libxcomposite`, `libxinerama`) y 20 recetas nuevas (`libdatachannel`, `librist`, `libsrt`, `mbedtls`, `rnnoise`, `x264`, `luajit`, `swig`, `vlc`…). **Si lo que se quiere es grabar la pantalla** —que es el 80% de para qué se instala OBS— eso ya está al alcance de la mano: **`wf-recorder` sale con CERO recetas faltantes y cero muros**, porque su cierre (wayland, ffmpeg, libdrm) quedó completo cuando entró mpv. ## Orden propuesto, en reemplazo del del ADR 1. ✅ **mpv** — hecho (2026-09-03). 2. **decisión de audio del corpus** (el ⚠ de arriba). Destraba wf-recorder y le da a mpv su salida nativa. Es una decisión, no una campaña. 3. **wf-recorder** — inmediato una vez tomada esa decisión; cubre el caso de uso principal de OBS. 4. ✅ **visor de imágenes** — hecho (2026-09-03), pero con **`swayimg` 4.7 y no `imv`**: imv está bloqueada por el muro de GL de escritorio (sección de arriba). En las cuatro imágenes. 4b. ✅ **`luajit`** — hecha (2026-09-03). Destrabó swayimg 5.5 y queda disponible para mpv. 5. ✅ **zathura + backend PDF** — hecho (2026-09-03). **La sospecha de GTK3 era falsa**: upstream VACIÓ `girara` (en 2026.07.18 son siete `.c` de utilidades, cero GTK) y toda la UI se mudó a zathura, que pide GTK4 >= 4.12 — que está en el corpus. Costó 5 recetas nuevas (`girara`, `xxhash`, `poppler-glib`, `zathura`, `zathura-pdf-poppler`) + 4 promociones gratis al corpus (`json-glib`, `lcms2`, `glib-shared`, `pcre2-shared`, `bzip2-shared`). **No va en `escritorio-kde`**: su backend necesita una poppler con `ENABLE_GLIB=ON` y esa imagen ya trae la de Qt6 vía okular; las dos instalan `/usr/lib/libpoppler.so.146`. 6. ✅ **terminal y editor en GNOME** — hecho (2026-09-03), cero recetas: `foot` + `helix` del corpus, que ya estaban selladas y sin declarar. Ver la sección de arriba. 7. **DECISIÓN pendiente, y ahora bloquea a TODO lo que queda** — no es trabajo de recetas: **(a)** autorar el stack GTK3 (destraba inkscape-release, GIMP y Firefox de una vez), **(b)** dárselo a [qorpa](adr/0015-imagenes-ajenas.md) como montón B, o **(c)** autorar la torre de bindings C++ (sigc++/glibmm/cairomm/pangomm/gtkmm-4) y seguir el `master` sin publicar de Inkscape. Hasta que se tome, el montón A no tiene ningún ticket barato: está agotado. 8. **Aparte y sin decisión: las 22 apps de KDE selladas y sin declarar** (sección de arriba). No cuesta un build — cuesta acordarlo con el frente KDE. OBS, Firefox, Chromium, GIMP y LibreOffice **salen del montón A** hasta que se decida GTK3 o qorpa. Que se llame montón A y no se pueda construir es peor que no tenerlo en la lista: es deuda fantasma, la misma figura que la terna de GNOME que había que sacar de `targets.toml` para que el grafo dejara de mentir. ## Reproducir la medición ⚠ **Ya no se reproduce así.** Lo que sigue describe el método ORIGINAL, que es el que falló las tres veces documentadas arriba; se deja escrito para que se entienda de dónde salieron los números de la tabla. El triaje de una app nueva se hace hoy cruzando sus `makedepends` contra `scripts/provee.py --desde corpus`, que pregunta por el artefacto y no por el catálogo. `/tmp/.../triaje-apps.py` fue un script de una sola vez; si hace falta repetirlo, lo que hace es: bajar el APKBUILD de cada candidata, extraer `makedepends`/`depends`, quitarles el sufijo `-dev`, y cruzar contra `recipes/*.toml` + `recipes/incoming-*/*.toml`, separando una lista fija de nombres que son muros de diseño (gtk+3.0, qt6-*, lib X11, wasi-*). No se guarda en `scripts/` porque la lista de candidatas es del momento, no un invariante que valga la pena vigilar.