Skip to content

fix(desktop): corrige flicker e salto lateral no morph da ilha - #18

Merged
RenanMev merged 2 commits into
mainfrom
fix/floating-island-morph-flicker
Aug 20, 2026
Merged

fix(desktop): corrige flicker e salto lateral no morph da ilha#18
RenanMev merged 2 commits into
mainfrom
fix/floating-island-morph-flicker

Conversation

@RenanMev

@RenanMev RenanMev commented Aug 19, 2026

Copy link
Copy Markdown
Owner

Problema

Abrir e fechar o chat piscava mostrando o desktop, e a pílula saltava ~131px para o lado ao abrir.

Diagnóstico

Adicionei instrumentação (island-debug.ts, opt-in via window.__islandDebug = true) para separar três relógios que se confundiam: quando o SetWindowPos retorna, quando o WebView2 entrega o viewport novo, e quando o CSS anima. Os números descartaram a hipótese inicial (resize assíncrono do WebView2 — o viewport-timeline mostrou frames=1, sem dessincronia) e revelaram três causas reais:

1. Movimento lateral junto com o resize. Centralizar o painel exigia mover a janela 132px físicos no mesmo SetWindowPos. A ilha só ficava parada porque o CSS cancelava esse deslocamento via left: calc(% - px) — compensação que depende de o WebView refazer o layout no mesmo frame. Quando ele apresentava um frame com o layout antigo já na posição nova, a pílula saltava.

2. Tempo morto no fechamento. ~240ms com a janela ainda expandida e transparente, já com a pílula desenhada:

morph antes depois
collapse #1 411ms 243ms
collapse #2 435ms 236ms
collapse #3 419ms 203ms

3. Faixa branca em volta da pílula. A região era montada a partir do retângulo visual cru (168×34), ignorando o recuo de 1px das superfícies, e o Rust ainda somava +1. Em DPI 1,25: região cobria 133→344 físicos, CSS pintava 133,75→341,25. Os até 3px de sobra expunham o fundo padrão branco do WebView2.

Solução

  • Largura de janela fixa (ISLAND_WINDOW_WIDTH) em todos os modos — abrir mexe só na altura. Sem movimento, não há o que compensar, e a classe inteira do artefato desaparece.
  • set_window_region (novo comando Rust, SetWindowRgn) recorta a área pintada e clicável, para as faixas transparentes não engolirem cliques do desktop. Nunca fica sem região: durante o morph usa uma arredondada do tamanho da transição, porque a janela sem recorte vira um retângulo cru e o Windows desenha moldura em volta.
  • Caminho crítico limpo: setMinSize/setResizable movidos para o prepare; barreiras de requestAnimationFrame reduzidas a um frame; recorte aplicado antes do encolhimento, com a escala já lida.
  • Borda como anel inset em vez de border, para não cair sobre a linha de corte.
  • onScaleChanged reaplica a região — sem isso, arrastar entre monitores de DPI diferente recorta a pílula.
  • Duração do morph: 260ms → 170ms.

Verificação

tsc --noEmit limpo. Validado manualmente no Windows 11 (build 26200).

⚠️ Não executei os testes nem o cargo check — ambos falham no WSL deste ambiente por motivos alheios a estas mudanças (bug de optional deps do rollup; libspa-sys exigindo libpipewire). As expectativas de posição afetadas em window-transition.test.ts, floating-quick-menu-mode.test.ts e floating-checklist-mode.test.ts foram atualizadas para o novo comportamento (origem preservada em vez de centralização), mas precisam rodar no Windows antes do merge.

Notas para revisão

  • island-debug.ts é instrumentação permanente, opt-in e sem custo quando desligada. Se preferirem fora do repositório, é só remover o arquivo e as chamadas islandLog/sampleViewportFrames.
  • resizeAroundCenter virou resizeFromOrigin: o painel agora abre alinhado pela borda esquerda da pílula. Expand e collapse continuam inversas exatas, então a pílula volta no pixel de onde saiu.

🤖 Generated with Claude Code

RenanMev and others added 2 commits August 19, 2026 17:21
A abertura e o fechamento do chat piscavam mostrando o desktop, e a
pílula saltava ~131px para o lado ao abrir. Instrumentando a transição
(island-debug) apareceram três causas distintas, todas medidas:

1. Movimento lateral no mesmo SetWindowPos do resize. Centralizar o
   painel exigia mover a janela 132px físicos, e a ilha só ficava parada
   porque o CSS cancelava esse deslocamento via `left: calc(% - px)`. A
   compensação depende de o WebView refazer o layout no mesmo frame do
   resize; quando ele apresentava um frame com o layout antigo já na
   posição nova, a pílula saltava.

   A janela passa a ter largura fixa (ISLAND_WINDOW_WIDTH) em todos os
   modos: abrir mexe só na altura. Sem movimento não há o que compensar.
   As formas mais estreitas são centralizadas por dentro e o excedente é
   recortado da janela por SetWindowRgn, para as faixas transparentes não
   captarem cliques do desktop.

2. Tempo morto entre o fim da animação e o commit nativo. No fechamento
   havia ~240ms com a janela ainda expandida e transparente, já com a
   pílula desenhada — desktop visível em volta. Vinha de setMinSize e
   setResizable (invisíveis, ~150ms de IPC) sentados no caminho crítico,
   mais dois requestAnimationFrame por barreira. Os IPCs foram para o
   prepare e as barreiras caíram para um frame.

3. Faixa branca em volta da pílula. A região era montada a partir do
   retângulo visual cru, ignorando o recuo de 1px com que as superfícies
   são desenhadas, e o Rust ainda somava +1 — sobravam até 3px que a
   janela incluía e o CSS nunca pintava, expondo o fundo padrão branco do
   WebView2. A região agora sai do retângulo já recuado, arredondando
   para dentro, e a borda virou anel inset para não cair sobre a linha de
   corte.

Também: reaplica a região em onScaleChanged, senão arrastar entre
monitores de DPI diferente recorta a pílula; e a duração do morph caiu de
260ms para 170ms.

Verificação: `tsc --noEmit` limpo. Os testes (vitest) e o `cargo check`
não rodam no WSL deste ambiente — bug de optional deps do rollup e
dependência de libpipewire, ambos alheios a estas mudanças. As
expectativas de posição afetadas foram atualizadas, mas não executadas;
o app foi validado manualmente no Windows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Consegui rodar a suíte pelo lado Windows (`cmd.exe /c npx vitest run`) —
o node_modules tem os binários win32 do rollup, que é o motivo de ela
falhar no WSL. Com isso as falhas do CI ficaram visíveis: de 32 para 5.

- `islandDebugEnabled` assumia `window`. A lane `unit` roda em Node e
  chama as funções de janela com os mocks do Tauri, então a
  instrumentação derrubava o teste inteiro. Agora tem guarda.
- `onScaleChanged` faltava no mock do Tauri, e o efeito novo do BarApp
  quebrava na montagem.
- O teste de `transitionend` checava o commit na hora, mas ele passou a
  ser adiado um frame; agora espera.
- Expectativas de posição em `floating-compact-bounds` e
  `enter-floating-mode` ainda descreviam centralização e janela de 168px.

Restam 5 falhas em `BarApp.test.tsx`, todas do mesmo tipo: asserções
sobre `set_window_bounds`/`animate_window_bounds` que agora também veem
chamadas de `set_window_region`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@RenanMev
RenanMev merged commit db7445a into main Aug 20, 2026
1 check failed
@RenanMev
RenanMev deleted the fix/floating-island-morph-flicker branch August 20, 2026 13:29
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.

1 participant