Skip to content

fix(linux/x11): la tira alcanza el borde del área de trabajo (#179) - #205

Merged
egdev6 merged 9 commits into
egdev6:mainfrom
Rafaeldelinares:fix/x11-native-widget-resize-v2
Oct 29, 2026
Merged

egdev6 merged 9 commits into
egdev6:mainfrom
Rafaeldelinares:fix/x11-native-widget-resize-v2

Conversation

@Rafaeldelinares

@Rafaeldelinares Rafaeldelinares commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Related issue / Issue relacionada

Issue: #179
ODD: alcance aprobado por el mantenedor en #179 (comment) · evidencia de verificación en #179 (comment)

Scope / Alcance

La tira visible no alcanzaba el borde del área de trabajo bajo Linux/X11: la ventana del widget nace con resizable: false, así que el gestor de ventanas descarta cualquier resize programático y la ventana conserva su tamaño natural de 320×260 (medido: ~84 px sin usar en el borde inferior). La causa está en la superficie nativa, no en la posición: no es un offset.

  • Tamaño fijo y resize programático (sólo X11/XWayland): la ventana se fija con pistas iguales (PMinSize == PMaxSize == tamaño de la tira) y con la petición de tamaño del widget (gtk_widget_set_size_request), y el permiso de resize se abre y se cierra dentro de una sola llamada nativa (apply_size), que confirma su propio resultado. Fuera de ese resize no hay cursor de redimensionado ni arrastre de borde; lo que conserva el tamaño concedido es la petición de tamaño (medido: sin ella, retirar el permiso devuelve la ventana a 320×260).
  • Ciclo de colocación con read-back: validar → pedir tamaño y posición (con el borde anclado) → leer lo que concedió el compositor (origen y tamaño del servidor X) → recolocar con la geometría efectiva → volver a leer tamaño y posición después de retirar el permiso, que es justo el momento en que GTK recalcula la configuración. El informe usa esa geometría final: si tras el retiro el compositor cambió el tamaño, no se informa applied. Una lectura fallida nunca se convierte en 0×0, y el borde anclado se conserva aunque el tamaño concedido sea otro.
  • Ciclo acotado, cancelable y honesto: el cancelador se comprueba antes de cada fase nativa (también dentro del closure de UI); toda llamada nativa cuya ejecución no se puede confirmar no se da por buena (se reintenta con presupuesto acotado y, si tampoco, degrada a no verificada con read_failed); el backend nunca mapea ni muestra la ventana, y el frontend no vuelve a mostrar una tira que el usuario ocultó desde la bandeja.
  • El informe de colocación es la fuente de verdad del frontend: se cachea la geometría efectiva (nunca la solicitud) y, cuando el gestor no concede lo pedido, se informa un código de motivo (size_refused, position_refused, not_viewable, read_failed, superseded), cuya etiqueta vive en los catálogos ES/EN (widget.reason*), como el resto de la tira desde feat(i18n): migrar la tira y el detalle a los catálogos ES/EN — T2c slice 1 (#66) #195. La tolerancia de ±1 px (el redondeo del gestor de píxeles lógicos a píxeles de dispositivo) se aplica sólo a la geometría que vino del read-back de X11; Windows, macOS y Wayland nativo comparan exacto.

El transporte se detecta por el tipo real de display de GDK (GdkX11Display vs GdkWaylandDisplay), cacheado en el hilo de UI: con Wayland nativo el transporte es "otro" y la ruta anterior queda intacta.

Desviaciones documentadas del alcance aprobado

  • D1: el permiso de resize programático no permanece activo: se abre sólo alrededor del resize y se retira en la misma llamada nativa, que lo confirma. El granted size lo conserva la petición de tamaño del widget, porque el retiro es lo que hace que GTK recalcule la configuración (medido: con la petición, 61×181 sobrevive y el retiro no genera configure; sin ella, el mismo retiro devuelve 320×260). La imposibilidad del resize manual se apoya en tres cosas: el permiso cerrado fuera del resize (sin cursor ni arrastre de borde), las pistas min == max (que el gestor respeta y GTK también clampa) y la ausencia de decoraciones.
  • D2: el mínimo de 240 px de tauri.conf.json se sustituye en tiempo de ejecución por el tamaño real de la tira en la ruta X11 (set_geometry_hints + set_size_request). La config compartida no se toca; el mínimo de la config sigue aplicando en el resto de plataformas.

Fuera de alcance: Wayland nativo (#180, transporte y contrato distintos), Windows/macOS (siguen con detail_geometry::apply_widget y devuelven unverified), el imán al soltar (comportamiento preexistente: src/lib/dock.ts, sin cambios aquí), la persistencia de dockEdge/dockOffset/dockMonitor y la selección de monitor (#113).

Commits revisables (sobre upstream/main@a0e999b, la 0.8.0): fix(linux/x11) (backend, frontend y sus tests), docs(i18n) (inventario regenerado con el script de #186, sobre el árbol de la unidad) y docs(odd) (artefacto ODD), más dos merges: uno integra esta reconstrucción en la rama ya publicada y otro el main actual (4a5363e), sin reescribir la historia publicada; de modo que el diff de esta PR es el árbol final y el hilo de la review se conserva.

Tamaño: +2617 / -29 (código y tests +2352: 1274 tests, 1078 producción; más el artefacto ODD y el inventario regenerado). El grueso es el ciclo nativo con cobertura determinista (25 tests Rust del módulo + 1 prueba en vivo GTK+WebKit2 + tests de frontend y de catálogo): el ciclo tiene que ser verificable sin un compositor real en cada caso. Si prefieres dos PRs (ciclo nativo / contrato IPC), lo parto antes de mergear.

Coordination / Coordinación

  • For ODD: issue/PR assignees and overlapping work checked; claim verified, or explicit maintainer coordination linked.

Contribuidor / Contribuidor: @Rafaeldeliners
Claim or agreement evidence / Evidencia de asignación o acuerdo: alcance aprobado por el mantenedor en #179 (#179 (comment)); la issue sigue sin assignee porque nuestro token no puede asignar. Sin PR solapadas: al abrir, las PRs abiertas upstream son #203 (smoke de secretos), #207 (provider Antigravity) y #210 (arrastre por compositor en Wayland nativo, #180); ninguna sobre la geometría nativa de la tira bajo X11, que es lo que aporta esta PR.

Roadmap ODD (#45, revisión quorb-linux-local-v1): esta corrección aporta el caso X11 de #107 «Escritorio y widget Linux» (milestone 8, en desarrollo), cuyo pendiente incluye el smoke en sesión real X11; el resto de #107 (GNOME/XFCE y matriz Wayland) sigue abierto. La documentación de la vía 3 de #180 (GDK_BACKEND=x11 como alternativa) queda condicionada a integrar esta PR, como acordaste.

Acceptance and validation / Aceptación y validación

  • Criteria satisfied / Criterios cumplidos: S1 (el límite es el área de trabajo y=30…1080, respetando el panel reservado) · S2 (se corrige la superficie nativa, sin offset, y el resize manual queda cerrado: permiso fuera del resize, pistas min == max y sin decoraciones) · S3 (el mínimo de 240 px se resuelve en runtime y los bordes horizontales se comprueban: 61×181 exacto) · S4 (resize → estabilización → posicionamiento con geometría efectiva → read-back; ciclo acotado, cancelable y sin fingir aplicaciones no confirmadas; si el gestor rechaza, se informa el motivo y no se cachea la solicitud) · S5 (Wayland nativo sin read-back de posición absoluta) · S6 (tests deterministas de orden, estabilización, rechazo y solicitudes obsoletas; evidencia KDE/X11 por borde; imán y persistencia conservados).

  • Checks actually run and results / Comprobaciones ejecutadas y resultados: cargo test --locked (354 pasan, 0 fallan, 3 ignorados) · prueba en vivo GTK+WebKit2 bajo X11 (GDK_BACKEND=x11 cargo test --locked widget_placement -- --ignored, verde, con el cruce contra el servidor X) · npm test (751 vitest + 240 node, 0 fallan) · npm run typecheck (verde) · cargo clippy --all-targets (los mismos avisos que la base de upstream/main, ninguno en el código nuevo) · cargo fmt --all -- --check (limpio en todo el crate, el gate nuevo de chore(tooling): adoptar rustfmt con gate de CI de formato (#184) #185) · node scripts/i18n-inventory.mjs --check (verde, con el formato estable de chore(i18n): inventario estable, contrato de CI estructural y mensajes de drift (#160) #186) · evidencia app-faithful en KDE/X11 (webview real) por borde, con WM_NORMAL_HINTS = min == max == tamaño de la tira en los cuatro:

    borde geometría X11 WM_NORMAL_HINTS ¿toca el borde del área de trabajo?
    top 181x61+870+30 min = max = 181×61 y = 30 ✔
    bottom 181x61+870+1019 min = max = 181×61 y = 1019 = 30 + 1050 − 61 ✔
    left 61x181+0+465 min = max = 61×181 x = 0 ✔
    right 61x181+1859+465 min = max = 61×181 x = 1859 = 1920 − 61 ✔

    (área de trabajo 0, 30, 1920, 1050; reproducible con xprop -root _NET_WORKAREA, xwininfo -id <id> y xprop -id <id> WM_NORMAL_HINTS) · sondas con webview real: un XResizeWindow(200,200) externo y un ConfigureRequest(200,200) sintético quedan clampados a 61×181 · sesión manual en KDE/X11 sobre este mismo binario: la tira se movió por los bordes, se acopló al borde más cercano con el mismo tamaño, sin parpadeo ni rectángulo 320×260, y al intentar redimensionarla no se observó cursor de redimensionado, arrastre de borde ni cambio de tamaño · escaneo del mismo binario durante 30 s de arrastre (288 muestras cada 100 ms): sólo los dos tamaños esperados (181×61 y 61×181), ninguna aparición del tamaño natural 320×260, WM_NORMAL_HINTS siempre acordes con el tamaño actual, ningún fotograma sin mapear y el recorrido siempre dentro del área de trabajo (x 0…1859, y 30…1019).

  • Checks not run and why / Comprobaciones no ejecutadas y motivo: el gesto manual de resize del gestor (lo medido es que el gestor recibe min == max y que un resize externo queda clampado; el gesto no se ejercitó) · GNOME/XWayland (donde la ventana se pintaba como una caja translúcida mayor que la tira; con la ventana al tamaño de la tira debería desaparecer, y queda por comprobar en ese entorno) · la escala HiDPI real en un monitor 1,5/2 (la regla de conversión está medida con GDK_SCALE=2 y probada por unidad, no en pantalla) · la ausencia de destello: en la sesión manual no se vio parpadeo, pero medido el resize previo al mapeo se pierde en ~1 de cada 9 carreras, así que puede haber un parpadeo breve del tamaño por defecto (acotado por el reintento con la ventana ya visible); no se afirma ausencia de flash porque no hay evidencia en pantalla de esa carrera.

  • Remaining blockers / Bloqueos pendientes: ninguno técnico; el tamaño del diff queda sujeto a tu aceptación (o a tu preferencia por partirlo en dos PRs). El merge es decisión del mantenedor.

An open PR does not mean integration. Release, secrets and signing operations require separate authorization.
Una PR abierta no implica integración. Release, secretos y firma requieren autorización separada.

Rafael Deliners added 3 commits October 29, 2026 11:20
)

The widget window is created with `resizable: false`, so under X11 the window
manager discards every programmatic resize and the strip keeps the 320x260
default, leaving the bottom of the work area unreachable.

Open the programmatic resize path and pin the window with equal size hints
(min == max == the strip size): the manager cannot offer another size and GTK
clips any reconfiguration, so a manual resize stays impossible while the
programmatic one is applied. The placement cycle asks for size and position,
maps the window, reads back what the compositor granted (GDK absolute client
origin plus size), repositions with the effective geometry and reads back
again. The placement report is now the source of truth for the frontend
cache, and a structured reason code is reported when the manager does not
grant the requested geometry.

The reason labels live in the ES/EN catalogs (`widget.reason*`), like the rest
of the strip UI after upstream egdev6#195.

Native Wayland and the Windows/macOS paths keep their current behaviour.
Regenerate the text inventory over the committed tree, with the stable format
from egdev6#186: the only new user-facing texts are the `widget.reason*` labels
(four of them reachable in the UI; the `superseded` code is reported by the
backend but the widget treats a superseded placement as "no reason").
ODD artifact for egdev6#179: the approved scope, the measured design decisions, the
four-edge evidence and the human accreditations still pending.
@egdev6

egdev6 commented Oct 29, 2026 •

Copy link
Copy Markdown
Owner

@Rafaeldelinares gracias por la PR y por la evidencia de #179: el A/B, la tabla de los cuatro bordes con WM_NORMAL_HINTS y la sección de lo que no está acreditado son un trabajo de primera. He revisado el diff completo (c9fcd31) y el código de tao 0.37.1 que interviene.

Lo que está bien

  • CI 5/5 en verde en el head; en local, los 15 tests de widget_placement y los 54 de widget-placement/auto-hide pasan.
  • Dependencias limpias: gtk 0.18 y webkit2gtk (dev) ya estaban en el lock, sin paquetes nuevos y limitadas a Linux.
  • Las llamadas GTK pasan por run_on_main_thread, sin unsafe; Wayland nativo conserva su ruta (bug(linux/wayland): la ventana no se puede posicionar: tira centrada, arrastre y cambio de monitor inoperativos #180 sigue separado).
  • Ciclo acotado y cancelable por generación. Mantener una sola PR me parece bien: partirla dejaría un estado intermedio sin valor.

Cambios necesarios

  1. D1 reintroduce el resize manual (punto 2 del alcance). En una ventana sin decoración con is_resizable() activo, tao (platform_impl/linux/event_loop.rs:494-557) muestra el cursor de redimensionar a 5 px × escala de cada borde y, al hacer clic, llama a begin_resize_drag. Las pistas min == max hacen que la mayoría de gestores rechacen el tamaño, pero el clic cerca del borde se convierte en un grab de resize y un gestor que ignore las pistas lo aplicaría. La prueba en vivo no lo cubre porque usa un gtk::Window simple, sin los handlers de tao. Propuesta: activar resizable solo alrededor del resize programático (o una alternativa equivalente) y acreditar en la app real que al pasar el ratón por los bordes de la tira no aparece cursor de resize ni se inicia ningún arrastre.
  2. tao restablece resizable en el primer configure (platform_impl/linux/window.rs:116-123: handler de un solo uso que llama a set_resizable(attributes.resizable)). Como el widget nace con visible:false, ese evento puede llegar después de que el ciclo informe Applied. Verifica el tamaño efectivo tras ese primer evento.
  3. map_if_needed → show_all() mapea la ventana durante la colocación: se adelanta al recorte que aplica el frontend antes de show() y podría volver a mostrar un widget que el usuario ocultó desde la bandeja si llega una colocación mientras está oculto. Quítalo del ciclo o condiciónalo a la visibilidad que decide el usuario.
  4. La señal de «visible» no es válida. El ciclo usa el primer valor de gdk_window.origin(), pero en GDK3 ese retorno está documentado como «not meaningful, ignore» (Gdk-3.0.gir, gdk_window_get_origin). Con eso, not_viewable no puede producirse como se describe y una lectura previa a que el gestor coloque la ventana puede contarse como buena. Sustitúyela por una señal real (estado mapeado / is_viewable() más el configure correspondiente) y ajusta el doble de los tests, que hoy modela «sin lectura hasta mapear», algo que la implementación no garantiza.
  5. La tolerancia de ±1 px afecta a todas las plataformas. Widget.tsx sustituye samePhysical por samePhysicalWithin también en Windows/macOS, donde un cambio de 1 px de la tira dejaría de aplicarse. La justificación (el gestor redondea) es de X11: aplica la tolerancia solo a la geometría devuelta por el read-back de X11.

Opcional

  • Timeout en la espera (recv) de on_ui.
  • Comprobar la generación dentro del closure de UI para cerrar el hueco entre el chequeo y el move_.
  • Tests de drive para los anclajes Top/Bottom/Left y con dos generaciones reales.
  • El test de widget-placement.test.ts:92-97 se llama «never caches the request as verified…» pero comprueba que la solicitud sí se cachea; renómbralo.

Con los puntos 1, 3, 4 y 5 resueltos, la verificación del punto 2 y la prueba del cursor en la app real, vuelvo a revisar.

Rafael Deliners added 4 commits October 29, 2026 18:47
)

The widget window is created with `resizable: false`, so under X11 the window
manager discards every programmatic resize and the strip keeps the 320x260
default, leaving the bottom of the work area unreachable.

Open the programmatic resize path and pin the window with equal size hints
(min == max == the strip size): the manager cannot offer another size and GTK
clips any reconfiguration, so a manual resize stays impossible while the
programmatic one is applied. The placement cycle asks for size and position,
reads back what the compositor granted (GDK absolute client origin plus size),
repositions with the effective geometry and reads back again. The placement
report is now the source of truth for the frontend cache, and a structured
reason code is reported when the manager does not grant the requested
geometry.

The resize permission is opened and closed inside one native call, and the
size is re-verified after that withdrawal, so the report always describes
what is on screen and the manual resize interaction stays closed outside the
programmatic resize.

The reason labels live in the ES/EN catalogs (`widget.reason*`), like the rest
of the strip UI after upstream egdev6#195.

Native Wayland and the Windows/macOS paths keep their current behaviour.
La review pidió cambios que reescribieron la secuencia nativa del ciclo de
colocación (permiso de resize cerrado salvo en el resize programático,
read-back del tamaño tras la retirada, señal de visibilidad real) y una
auditoría ciega encontró además seis defectos menores. El work se rehízo sobre
el main actual (0.8.0) en tres commits (8c3ee37 implementación, a7bab32
inventario i18n, 42c62d0 artefacto ODD) y este merge lo integra en la rama ya
publicada sin reescribir la historia: el diff de la PR egdev6#205 pasa a ser el árbol
final verificado y el hilo de la review se conserva.
Rafael Deliners added 2 commits October 29, 2026 21:18
Integra los tres commits nuevos de main (firma de macOS egdev6#209/egdev6#211 y logo de
Copilot egdev6#208), que no tocan el mecanismo de colocación; el inventario i18n se
regenera encima en el commit siguiente.
@Rafaeldelinares

Copy link
Copy Markdown
Contributor Author

@egdev6 gracias por la review: los cinco puntos eran buenos y dos de ellos no estaban realmente cerrados por nuestra primera corrección. Van resueltos, con las cuatro opcionales también, y con el mecanismo corregido donde nuestra primera respuesta describía mal lo que ocurría.

Punto 1 — el permiso de resize. Ahora se abre y se cierra dentro de una sola llamada nativa (apply_size: petición de tamaño + pistas min == max + resizable(true) + resize + resizable(false)), y la llamada confirma su propio resultado (!is_resizable()). Al ser una sola llamada al hilo de UI, sin iterar el main loop en medio, ningún evento de puntero o teclado puede despacharse mientras el permiso está abierto: no hay ventana en la que tao ofrezca cursor de redimensionado ni begin_resize_drag. Y si una llamada nativa no se puede confirmar (hilo de UI ocupado más de 2 s), no se da por buena: se reintenta dentro de un presupuesto acotado y, si tampoco, la colocación se informa como no verificada con read_failed en vez de fingir que se aplicó.

Medido (GTK3 + WebKit2 real, X11): con la petición de tamaño puesta, retirar el permiso no cambia el tamaño ni genera configure (contador 4 → 6 → 6) y el 61×181 concedido sobrevive con su posición; sin la petición de tamaño, el mismo retiro devuelve la ventana a 320×260. Son dos escenarios distintos y los medimos por separado. Lo que conserva el granted size es la petición de tamaño del widget (gtk_widget_set_size_request), porque es el retiro del permiso lo que hace que GTK recalcule la configuración.

Punto 2 — el tamaño efectivo tras ese evento. El read-back final vuelve a leer tamaño y posición después de la retirada, y el informe usa esa geometría: si tras el retiro el compositor cambió el tamaño, no se informa applied. Antes leíamos el tamaño antes de retirar y refrescábamos sólo el origen, así que podíamos informar applied con un tamaño que el compositor ya había cambiado.

Una precisión sobre el mecanismo, porque nuestra primera respuesta lo describía mal: el handler de un solo uso de tao (platform_impl/linux/window.rs:113-126) restaura el valor de creación de la ventana, y este widget se crea con resizable: false (tauri.conf.json:23), así que restaura false y no puede reabrir el permiso. La consecuencia real de ese evento es el mismo recálculo de set_resizable(false) que puede cambiar el granted size — que es exactamente lo que no verificábamos y que ahora sí verificamos.

Punto 3 — el mapeo en el ciclo. Fuera: el backend no mapea ni muestra la ventana en ningún camino (el único show_all que queda está en la prueba en vivo, con un gtk::Window pelado). El frontend es el dueño de show(), y ahora sabe si la ocultó el usuario (widget-hidden/widget-shown): no volvemos a mostrar una tira que el usuario ocultó desde la bandeja, tampoco por la red de seguridad de 2,5 s. Si el backend no pudo leer la ventana todavía (not_viewable), se vuelve a pedir la colocación en cada caso, no sólo en el primer show().

Punto 4 — la señal de «visible». La real: gdk_window_is_viewable() más la coincidencia del tamaño. Quitamos el contador de configure como puerta: no puede distinguir el configure del gestor del eco de nuestra propia petición al servidor X, así que mantenerlo habría sido afirmar una garantía que el código no sostiene; lo que afirmamos ahora es exactamente lo que se puede probar. El origen sale de gdk_window_get_origin (coordenadas de la ventana raíz) y, si esa llamada falla, no hay lectura: preferimos informar el fallo antes que devolver una geometría de otro espacio de coordenadas. La conversión de nativo a físico usa el factor de la ventana, no el de la petición.

Punto 5 — la tolerancia de ±1 px. El informe lleva read_back y el frontend aplica la tolerancia sólo a la geometría que vino del read-back de X11; Windows, macOS y Wayland nativo comparan exacto. La justificación es el redondeo del gestor de píxeles lógicos a píxeles de dispositivo, y por eso aplica a tamaño y posición (el redondeo ocurre en ambos ejes).

Opcionales. Timeout de 2 s en la espera de on_ui, con confirmación de cada llamada nativa y reintento acotado · la generación se comprueba dentro del closure de UI, también al fijar las restricciones de tamaño · tests de drive para los cuatro anclajes (con ambos movimientos) y de dos generaciones que cancelan más tarde · y el test engañoso, renombrado.

Además, una auditoría ciega independiente del diff encontró seis cosas más que también están cerradas: la cabecera del módulo describía el diseño anterior · la fijación de restricciones no comprobaba la generación (una colocación cancelada podía pisar las pistas de la sucesora) · la red de seguridad de 2,5 s podía volver a mostrar una tira oculta · el origen tenía un fallback en coordenadas relativas al padre (otro espacio de coordenadas) · scale no se validaba · y las cifras y afirmaciones del artefacto ODD no reproducían. El árbol final es 42c62d0.

Evidencia (binario de este head). KDE Plasma 6.7.5, app en X11/XWayland, área de trabajo 0, 30, 1920, 1050, una carrera por borde:

borde geometría X11 WM_NORMAL_HINTS ¿toca el borde?
top 181x61+870+30 min = max = 181×61 y = 30 ✔
bottom 181x61+870+1019 min = max = 181×61 y = 1019 = 30 + 1050 − 61 ✔
left 61x181+0+465 min = max = 61×181 x = 0 ✔
right 61x181+1859+465 min = max = 61×181 x = 1859 = 1920 − 61 ✔

Comprobado también con sondas: un XResizeWindow(200,200) externo y un ConfigureRequest(200,200) sintético quedan clampados a 61×181. Sesión manual sobre este mismo binario: la tira se movió por los bordes, se acopló al más cercano con el mismo tamaño, sin parpadeo ni rectángulo 320×260, y al intentar redimensionarla no se observó cursor de redimensionado, arrastre de borde ni cambio de tamaño.

Comprobaciones. CI 5/5 · cargo test --locked 354 ok / 0 fallos / 3 ignorados · npm test 751 vitest + 240 node · npm run typecheck limpio · cargo fmt --all -- --check limpio (gate de #185) · cargo clippy --all-targets con los mismos avisos que la base y ninguno en widget_placement* · node scripts/i18n-inventory.mjs --check verde, con el inventario regenerado en commit aparte.

Lo que sigue sin acreditar, dicho explícitamente: el gesto manual de resize del gestor (lo medido es que el gestor recibe min == max y que un resize externo queda clampado; el gesto no se ejercitó) · GNOME/XWayland —donde @SrStamm vio la ventana pintarse como una caja translúcida mayor que la tira, que con la ventana al tamaño de la tira debería desaparecer—, la escala HiDPI real en un monitor 1,5/2 · el traslado entre monitores con distinto factor (la petición usa el factor del monitor destino y la lectura el de la ventana: mientras no coinciden, la colocación termina de forma conservadora en limited/size_refused en vez de informar un tamaño equivocado) · y la percepción del destello en la carrera (~1 de cada 9) en que el resize previo al mapeo se pierde.

Tamaño del diff: +2617 / -29 (código y tests +2352: 1274 tests, 1078 producción; más el artefacto ODD y el inventario regenerado). El crecimiento respecto de lo que viste son estas correcciones y sus tests; seguimos en una sola PR, como sugeriste.

Sobre la forma de la rama: la reconstruimos sobre el main actual (0.8.0) y la integramos en la rama ya publicada con un merge, sin reescribir la historia publicada: el diff de esta PR es el árbol final (8c3ee37 implementación, a7bab32 inventario, 42c62d0 artefacto ODD, más el merge), y tu hilo de review queda en esta misma PR.

@egdev6
egdev6 merged commit f71039b into egdev6:main Oct 29, 2026
5 checks passed
@egdev6

egdev6 commented Oct 29, 2026

Copy link
Copy Markdown
Owner

@Rafaeldelinares gracias por la segunda vuelta. He revisado el árbol final (42c62d0 + main) y los cinco cambios pedidos están resueltos:

  1. El permiso de resize se abre y se cierra dentro del mismo closure del hilo de UI, sin eventos de por medio, y en la app real no hay cursor de redimensionado.
  2. La lectura final de tamaño y posición se hace después de retirar el permiso, y la precisión sobre el handler de tao (restaura resizable: false) es correcta.
  3. El backend ya no muestra la ventana, y hiddenByUser se respeta también en la red de seguridad.
  4. La visibilidad sale de is_viewable() más la coincidencia de tamaño.
  5. La tolerancia de ±1 px solo se aplica con read_back; Windows y macOS vuelven a comparar exacto.

Las opcionales también están, y la auditoría adicional suma. Queda integrada.

Dos notas que no bloqueaban, para un seguimiento:

  • Comentarios que prometen más de lo que el código garantiza. rooted_origin(ox, oy, rooted != 0) vuelve a apoyarse en el retorno de gdk_window_get_origin, que GDK3 documenta como «not meaningful» (en la práctica solo es 0 con la ventana destruida, no detecta un fallo de traducción de coordenadas). Y apply_size devuelve !is_resizable() justo después de set_resizable(false), que no puede fallar. El comportamiento es correcto; lo que pediría es ajustar los comentarios para que no afirmen una confirmación que no existe.
  • El presupuesto de lecturas es justo. READS × SETTLE son unos 60 ms por fase. Si en el primer show() el gestor tarda más en mapear (GNOME/Mutter con animaciones es buen candidato), el segundo intento devuelve not_viewable y la tira muestra el reintento con un fallo que no es real. Propongo que not_viewable tras el show() se reintente en silencio en lugar de marcar placementFailed. Encaja con el smoke de GNOME pendiente en feat(linux): hacer funcional el escritorio y widget nativos #107.

Lo que declaráis sin acreditar (GNOME/XWayland, HiDPI real, monitores con distinto factor) queda en #107, no en esta PR.

egdev6 added a commit that referenced this pull request Oct 31, 2026
…and (#215) (#224)

* fix(linux/x11): ajustar la ventana del detail al contenido bajo XWayland (#215)

La ventana detail nace resizable(false) y GTK retiene un piso PMinSize sticky,
así que el resize externo es rechazado en el ancho y el aviso de API key muestra
la caja del contenedor (~16 px de banda). El detalle pasa por el mismo ciclo X11
de la tira (#205): una sola llamada al hilo de UI hace la pista de tamaño, las
pistas min == max == pedido, el permiso de resize abierto sólo alrededor del
resize y confirmado cerrado; luego la posición explícita y un read-back de GDK
con presupuesto acotado. Generación de cancelación propia del detail (no
comparte la del widget), sin anclaje y con informe {status, bounds, reason,
read_back} espejo de set_widget_bounds; Windows, macOS y Wayland nativo quedan
en su ruta anterior (unverified, bounds=null).

* fix(linux/x11): el panel del detail consume el informe de colocación (#215)

DetallePanel cachea sólo la geometría confirmada por read-back (nunca la pedida),
repide en silencio tras show() ante not_viewable con el presupuesto acotado de
#221 (readPlacementWithRetries reutilizado) y deja intacto el flujo de Wayland
nativo/Windows/macOS (unverified sin read-back, comparación exacta). La apertura
no se bloquea: place → clip → positioned → show conserva su orden.

* docs(i18n): regenerar el inventario de textos (#215)

Secciones nuevas de detail_placement.rs y detail_placement_test.rs (cuerpos de
error y nombres de test, nada de interfaz de usuario) tras los commits 9a6622d
y 15aea55; el frontend no añade textos.

* test(linux/x11): pedir posición dentro del área en la prueba en vivo (#215)

Ejecutar la prueba en vivo contra el XWayland real marcó el hallazgo del
fixture: movía la ventana 314×379 a (1859,899), fuera de pantalla, y el gestor
la reubicaba (1606,701) para que quepa. La petición ahora es (100,100) y el
resto de la secuencia queda intacto: tamaño exacto concedido, permiso cerrado y
tamaño sobreviviente a la retirada.

* docs(odd): artefacto de la colocación nativa del detail en X11 (#215)

Ciclo, decisiones (D1–D3), TDD por unidad, hallazgo del fixture de la prueba
en vivo y evidencia de sesión GNOME/XWayland (geometría 314×379, pistas
min == max, sin caja translúcica) según los criterios de aceptación (#215).

* docs(i18n): regenerar el inventario de textos

* fix(linux/x11): mover el detail a coordenadas lógicas en HiDPI (#215)

request_position pasaba la petición física tal cual a gtk.move_, que
mide en píxeles lógicos: a escala ×2 el panel quedaba desplazado
aproximadamente al doble (P2 del review de PR #224, diagnosticado por
el mantenedor y contrastado con el recorrido del código).

Se introduce el seam testeable del propio archivo (Positioner +
GtkPositioner + position_call, espejo de Sizable/GtkSizable): la
conversión con to_logical cubre los dos ejes antes del movimiento, el
mismo mapeo que el adaptador del widget. La cobertura nueva (escala
×2: (200,300) físico -> (100,150) lógico) falla contra el código
anterior, así que la omisión queda atrapada por test.

Verificación: cargo test 402 + 4 ignoradas (0 fallos), fmt limpio,
clippy sin avisos nuevos en detail_placement*; verificador
independiente en PASS sobre los 5 checks.

* docs(odd): bitácora L5 del fix HiDPI del review de PR #224 (#215)

* docs(i18n): regenerar el inventario de textos (#215)

* docs(odd): cierre L5 con el inventario i18n regenerado (#215)

---------

Co-authored-by: Quique <egdev6@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants