Repository navigation
Description
Pre-flight checks
- I searched existing issues and found no duplicate.
- I understand that this report enters review before implementation.
What happened?
Qué pasa
En Windows, la tira y el panel de detalle de cada proveedor se ven cortados por el borde derecho, como si tuvieran un ancho fijo. En el panel quedan cortados los porcentajes, las barras y la esquina redondeada, que se ve como un borde recto. En la tira se corta el último anillo y los iconos de acciones.
Pasa desde la instalación. No es una regresión reciente.
Causa
La app pasa los tamaños de píxeles CSS a píxeles físicos usando el scaleFactor del monitor, que solo tiene en cuenta la escala de pantalla. WebView2, en cambio, pinta el contenido con devicePixelRatio, que combina la escala de pantalla y el tamaño de texto de Accesibilidad. Si el tamaño de texto es mayor que 100 %, cada ventana nativa queda más pequeña que su contenido en esa misma proporción.
En mi caso:
- escala personalizada 102 %, así que
scaleFactor= 1,02 - tamaño de texto 106 %, así que
devicePixelRatio≈ 1,02 × 1,06 ≈ 1,081 - la ventana queda un ~6 % más estrecha que su contenido, unos 20 px en un panel de ~330 px
Lo comprobé así: con el tamaño de texto al 100 % (y pulsando «Aplicar»), las dos superficies se ven bien.
Dónde ocurre:
src/Widget.tsxgeoOf:sf = m.scaleFactor.- Tira:
src/Widget.tsxmide la tira en CSS px (ResizeObserver) y la convierte conphysicalPanelBounds(win, geo.sf)antes deset_widget_bounds. - Detalle:
src/DetailPanel.tsxconvierte conphysicalPanelBounds(detailBounds(data, next), data.scaleFactor)antes deset_detail_bounds. El recorte de superficie también escala condata.scaleFactor. src/lib/detail-panel.tsphysicalPanelBounds: multiplica posición y tamaño por el mismo factor.src-tauri/src/detail_geometry.rs:SetWindowPosaplica esos píxeles tal cual.
Contexto upstream: Tauri todavía no expone el factor de texto (tauri-apps/tauri#14711, tauri-apps/tao#1168 abierta). La documentación de WebView2 (ICoreWebView2Controller3::get_RasterizationScale) lo confirma: «The rasterization scale is the combination of the monitor DPI scale and text scaling set by the user».
Propuesta de solución
Separar las dos conversiones:
- Posiciones con
scaleFactor, como ahora, porque el área de trabajo viene en unidades del monitor. - Tamaños con el
window.devicePixelRatioreal de la ventana que contiene el contenido: el de la ventana del widget para la tira y el de la ventana de detalle para el panel. Lo mismo para la escala del recorte de superficie. - Tests unitarios de la conversión con
devicePixelRatio ≠ scaleFactor(por ejemplo 1,02 / 1,0812 y 1,25 / 1,5). Hoy ningún test cubre ese caso. Tests de no regresión paradevicePixelRatio == scaleFactor(100 %, 125 %, 200 %), que debe dar el mismo resultado que ahora. - Escuchar los cambios de
devicePixelRatio(matchMedia('(resolution: …dppx)')) para recolocar si el usuario cambia el tamaño de texto con la app abierta.
Alternativa que descarto: pasar --force-text-scale-factor=1 a WebView2 en los argumentos del navegador. Haría que Quorb ignore el tamaño de texto del usuario, que es justo un ajuste de accesibilidad, y no he comprobado que funcione.
Preguntas para cerrar la spec:
- ¿Os parece bien el enfoque de separar posiciones y tamaños, o preferís esperar a que Tauri exponga el factor de texto?
- En Linux y macOS,
devicePixelRatioyscaleFactordeberían coincidir, así que el cambio no tendría efecto. ¿Lo limitamos a Windows o lo dejamos general?
Steps to reproduce
- En Windows 11, ir a Configuración > Accesibilidad > Tamaño de texto, subirlo por encima de 100 % (por ejemplo 110 %) y pulsar «Aplicar».
- Abrir Quorb, o cerrarlo desde la bandeja y volver a abrirlo.
- Mirar la tira y abrir el panel de detalle de un proveedor.
Expected behavior
Que la tira y el panel se vean enteros, con el contenido y las esquinas redondeadas dentro de la ventana, sea cual sea el tamaño de texto de Windows.
Environment
Windows 11
Quorb: v0.8.0 (pasaba también en versiones anteriores, desde la instalación)
Escala de pantalla: personalizada 102 %
Tamaño de texto (Accesibilidad): 106 %
Reproducido con un monitor (en casa); en el trabajo uso 3 monitores, no medido con esta configuraciónAffected area
Application behavior
Activity
egdev6 commentedon Nov 1, 2026
Gracias por el reporte tan detallado, @matraket. Lo revisé contra
mainy el diagnóstico encaja con el código:src/Widget.tsx:62tomasf = m.scaleFactor, y tanto la tira (Widget.tsx:524) como el detalle (DetailPanel.tsx:122) convierten medidas CSS con ese factor. Si WebView2 pinta con undevicePixelRatioque incluye el tamaño de texto, la ventana queda más pequeña que su contenido, como describe la documentación que citas.Sobre la propuesta, separar "posiciones con
scaleFactor" y "tamaños condevicePixelRatio" no es suficiente, porque la posición depende del tamaño.stripRect(Widget.tsx:519) calcula, por ejemplo, una tira anclada a la derecha como borde menos ancho, usando el ancho medido en CSS. Si después el tamaño se convierte con otro factor, la ventana crece pero su posición no se corrige, y la tira se saldría del borde en la misma proporción en que hoy se corta.Propongo normalizar antes de calcular la geometría:
css × devicePixelRatio / scaleFactor) al medir, usando eldevicePixelRatiode la ventana que contiene ese contenido.scaleFactor, como ahora.devicePixelRatio(en el detalle,zoom × devicePixelRatio).Hay otro punto afectado que conviene incluir: el arrastre (
Widget.tsx:1065-1066) multiplica el desplazamiento del puntero porsf, así que con el texto por encima del 100 % la tira probablemente se quede atrás del cursor en esa misma proporción.Sobre tus preguntas:
devicePixelRatioreal ya se puede leer en cada ventana.devicePixelRatio == scaleFactorel resultado es idéntico al actual, y los tests de no regresión que propones lo garantizan sin añadir ramas por plataforma. Escuchar los cambios dedevicePixelRatiotambién cubre mover la ventana a otro monitor.Los tests que propones me parecen bien, añadiendo casos de tira anclada a derecha e inferior para comprobar que el borde se conserva. No lo he podido reproducir porque no tengo un equipo con Windows a mano, así que la validación manual con tus valores (102 % / 106 %) será importante en la PR.
matraket commentedon Nov 1, 2026
Gracias por la revisión, @egdev6. Tienes razón en lo central: si la posición se calcula como
borde - tamañocon el tamaño en CSS, convertir solo el tamaño desplaza la tira. Adopto tu enfoque de normalizar antes de calcular la geometría. Al revisarlo con detalle encontré un ajuste en el factor y varios puntos más que también hay que cubrir. Propongo esta spec para cerrarla.1. El factor correcto es la escala de texto,
t, nodpr / geo.sfcss × devicePixelRatio / scaleFactorsolo es correcto si las dos cifras son del mismo monitor, y el código mezcla monitores:geo.sfes el del monitor destino (geoOf/resolveGeo).devicePixelRatioes el del monitor donde está ahora la ventana.Hay tres casos en que no coinciden: al cambiar el monitor en Ajustes (el efecto de colocación corre con la ventana todavía en A y
geoya en B), al arrancar condockMonitor= B mientras la ventana nace en el principal, y en la recuperación tras desconectar un monitor. Con monitores de escala distinta, la conversión saldría mal justo ahí.El factor que no depende del monitor es la escala de texto, que en Windows es global:
Las dos lecturas son de la misma ventana y en el mismo momento. Cuando la ventana cambia de monitor,
dprysfcambian perotse mantiene. Lo cacheamos y lo refrescamos cuando cambiadevicePixelRatio.2. Invariante
Un módulo puro nuevo,
src/lib/text-scale.ts, contextScale(dpr, sf)(con guardas: valores no finitos o ≤ 0 dan1, y redondeo a 4 decimales para que el ruido de coma flotante no dispare recolocaciones),toMonitor(css, t)ytoCss(monitor, t). Así ningún sitio hace la cuenta a mano.3. Sitios que cambian
Tira (
src/Widget.tsx)stripRect(geo.area, …, stripSize.w, stripSize.h)toMonitor(stripSize, t)peekRect: constantes48/24(auto-hide.ts).peekenstyles.css)× t(recibet)setPos({ strip: s - win })left/topCSStoCss(…, t), sigue en CSSanchorReldel orbe al abrir el detalleanchordel payloadtoMonitormeasureSurfaceClip(…, geo.sf)sft × geo.sf(no eldprvivo, porque la ventana aún puede estar en el otro monitor)scale = geo.sfsfdevicePixelRatioactual (ahí la ventana sí está donde está)d.sf = geo.sf, delta(screenX - sx) × d.sf(move y fallback del drop)sfgeo.sf × tcapturado en pointerdowngrip.left + w/2(CSS)× d.sfsfdel destino antiguodevicePixelRatioactual en el drop (la ventana puede estar ya en otro monitor)Detalle (
src/DetailPanel.tsx,src/lib/detail-panel.ts)fitPanelestá afectado aunque nadie lo había mencionado: comparalayoutHeight × base(CSS) conmaxHeight(unidades de monitor). Cont > 1el panel empieza a encogerse tarde, y la ventana queda corta también en altura, no solo en ancho. Propuesta:t, medido en su ventana, no elscaleFactordel payload: la ventana del detalle puede estar oculta en otro monitor.stripyanchorya llegan normalizados desde la tira, así quepanelHeightLimitydetailBounds(panelRect) salen correctos sin tocarlos. Con bordebottom/rightse conservay + h = strip.y - 8yx + w = strip.x - 8.next.zoom × t × data.scaleFactor.Recolocación al cambiar
ten caliente. Hoy nada reacciona: cambiar el tamaño de texto no altera las medidas CSS, así que elResizeObserverno se dispara y no hay listener de resolución. HookuseTextScale()conmatchMedia('(resolution: Xdppx)')(rearmándolo en cada cambio) másonScaleChanged.tentra en las dependencias de la colocación de la tira y del layout del detalle. En la tira lo guardo en un ref o en un estado declarado después de los existentes, porque el harness deauto-hide.test.tsmockeauseStatepor índice de slot.4. Lo que NO cambia (y por qué)
physicalPanelBounds: es la única conversión final, consf.Placement.scale=geo.sf: en X11, Rust lo usa para pasar a lógico GTK, y debe ser el factor con el que se calculó el rectángulo.widget_placement,detail_placement,detail_geometry,window_clip): trabaja en físico, sin suponercss × sf.window_clip::resizedreescala porscale/old_scale, ytse cancela en ese cociente.dockOffset: es una fracción entre 0 y 1 sin unidades, así que losconfig.jsonguardados siguen siendo válidos.devicePixelRatiode forma coherente y ocupa la pantalla completa.clientX, umbral de 6 px): todo en CSS, consistente.En Linux y macOS,
t = 1(el text-scaling de GNOME cambia la fuente, no eldevicePixelRatio), así que el resultado es idéntico al actual sin ramas por plataforma, como pedías.5. Tests
Todos sobre funciones puras, sin DOM:
textScale: casos normales, guardas (0,NaN,Infinity→1) y el redondeo.stripRect+peekRectcont: tira anclada a derecha e inferior conservando el borde exacto (lo que pediste), y el peek escalado.fitPanelcont: umbral de encogimiento desplazado,width/heightcont, y el caso escalado devolviendomaxHeightliteral (sin píxel fantasma).detailBoundsbottom/right:y + h = strip.y - 8,x + w = strip.x - 8.zoom × t × sf.auto-hide.test.ts: delta físico= delta CSS × sf × t.t = 1ysf ∈ {1, 1,25, 1,5, 2}que da exactamente lo mismo que ahora. Los mocks existentes (sindevicePixelRationiscaleFactor) caen ent = 1, así que los tests actuales no cambian.Pares de
(dpr, sf)en los casos cont ≠ 1:(1,0812, 1,02)(el mío),(1,25, 1),(1,5, 1,25)y(2,25, 1,5).6. Validación manual en Windows (en la PR)
Ya que tú no tienes Windows a mano, la hago yo y adjunto capturas y valores:
devicePixelRatio,scaleFactor()y un delta descreenXarrastrando. Así confirmo que bajo escala de textoscreenXva en px CSS (físico / dpr). Es la única suposición del arrastre que no he podido comprobar leyendo código.main.7. Dos decisiones para cerrar
area.w. Con untgrande (por ejemplo 150 % de texto en una pantalla pequeña) el panel podría salirse del área de trabajo. Propongo dejarlo fuera de esta issue como follow-up, porque no aparece en los casos reales que tenemos. ¿Te parece bien, o lo incluimos?Si estás de acuerdo, me asigno la issue y preparo la PR.
egdev6 commentedon Nov 1, 2026
Gracias por el trabajo, @matraket. Tienes razón en el factor: mi fórmula mezclaba el
scaleFactordel monitor destino con eldevicePixelRatiodel monitor donde está la ventana, y fallaría justo en los cambios de monitor que describes. Calculart = devicePixelRatio / scaleFactor()sobre la misma ventana es mejor, porque la escala de texto de Windows es global.Verifiqué en
mainlos sitios nuevos que señalas y los confirmo:fitPanelcompara la altura en CSS conmaxHeighten unidades de monitor, las constantes48/24depeekRectestán en CSS,setPosusa un desplazamiento en unidades de monitor como CSS yanchorsuma un valor CSS a una posición en unidades de monitor.Un único añadido a la spec:
devicePixelRatiose lee de forma síncrona yscaleFactor()es asíncrono, así que durante un cambio de monitor podrían leerse en momentos distintos y producir unttransitorio incorrecto. Conviene recalcularlo cuando el cambio de escala se haya asentado y descartar valores fuera del rango posible de Windows (100–225 %), conservando el último válido.Sobre las dos decisiones:
Apruebo la spec con ese añadido y te asigno la issue. Adelante con la PR, incluida la validación manual en Windows que propones. ¡Gracias!
docs(odd): PR borrador egdev6#231 de egdev6#228
test(widget): límites del rango y ruido float32 de la escala de texto (…
docs(i18n): regenerar el inventario de textos (egdev6#228)
fix(widget): la tira y el detalle respetan la escala de texto de Wind…
merge: integrar main del upstream (widget egdev6#214/egdev6#227/egdev…
merge: integrar main del upstream (widget #214/#227/#228, X11 #215, m…