From ed1b8e05cad90dc0915fda908647d4986345ddae Mon Sep 17 00:00:00 2001 From: Phelipe Lorran <112786693+httpsphl@users.noreply.github.com> Date: Wed, 30 Sep 2026 22:20:16 -0300 Subject: [PATCH] perf(app): skip layout and paint of rows out of sight The chat and a crew's messages keep every loaded page in the DOM, and each new row or streamed draft re-laid them all out. Rows out of sight now use content-visibility: auto, with the size they last had, so the browser skips them until they scroll near. The 8 newest rows are left out, since cards arrive there with a glow that paint containment clips. --- app/src/features/chat/ChatView.tsx | 2 +- app/src/features/messages/Timeline.tsx | 2 +- app/src/index.css | 1 + app/src/offscreen.css | 9 +++++++++ docs/spec.md | 2 ++ 5 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 app/src/offscreen.css diff --git a/app/src/features/chat/ChatView.tsx b/app/src/features/chat/ChatView.tsx index 0f7399df..05fc6d2c 100644 --- a/app/src/features/chat/ChatView.tsx +++ b/app/src/features/chat/ChatView.tsx @@ -175,7 +175,7 @@ export const ChatView = memo(function ChatView({ bot, stopped }: { bot: Bot; sto )} -
    +
      {list}
    diff --git a/app/src/features/messages/Timeline.tsx b/app/src/features/messages/Timeline.tsx index 4851bfbe..9494aa59 100644 --- a/app/src/features/messages/Timeline.tsx +++ b/app/src/features/messages/Timeline.tsx @@ -69,7 +69,7 @@ export function Timeline({

    )} {count === 0 && !loading && !error &&

    {empty}

    } -
      +
        {messages.map((message) => ( ))} diff --git a/app/src/index.css b/app/src/index.css index 36aa5f29..27edc1c5 100644 --- a/app/src/index.css +++ b/app/src/index.css @@ -11,6 +11,7 @@ @import "./effort.css"; @import "./motion-less.css"; @import "./paused.css"; +@import "./offscreen.css"; /* Colors are tokens so the light and dark themes swap in one place (spec 15.3). State colors always go with an icon and a label. */ diff --git a/app/src/offscreen.css b/app/src/offscreen.css new file mode 100644 index 00000000..33a036c3 --- /dev/null +++ b/app/src/offscreen.css @@ -0,0 +1,9 @@ +/* Long lists (the chat, a crew's messages) keep every row in the page. + Rows out of sight skip layout and paint until they scroll near, and + keep the size they last had so the scrollbar stays still. The newest + rows are left out: that is where cards arrive with a glow that would + be clipped, and where the owner looks. */ +.offscreen-rows > li:not(:nth-last-child(-n + 8)) { + content-visibility: auto; + contain-intrinsic-size: auto 120px; +} diff --git a/docs/spec.md b/docs/spec.md index 265817d7..d5a03544 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -731,6 +731,8 @@ Componentes dependem só de `BotloftApi` e `Host`, nunca do cliente concreto nem O store recarrega crews, bots e `system.status` a cada (re)conexão e depois segue as notificações. `system.status` não tem notificação e é relido a cada 15 s, menos com a janela fora de vista (minimizada, escondida perto do relógio ou aberta escondida no login do Windows): aí para, e é relido quando ela volta. Fora de vista, as animações também param onde estão (`paused.css`) e continuam quando a janela volta. O Windows pode não avisar a página quando o próprio app esconde a janela, então o app marca isso ele mesmo (`shell/visibility.ts`), e a janela voltar à frente desmarca. +O chat e as mensagens de uma equipe guardam todas as páginas já carregadas. As linhas fora de vista não são diagramadas nem pintadas até chegarem perto da tela (`content-visibility: auto`, em `offscreen.css`), e guardam o tamanho que tiveram para a barra de rolagem não pular. As 8 mais novas ficam de fora: é onde os cartões chegam com o brilho de atenção, que a contenção cortaria. + - **Deliveries:** entram no store pela message (cada message tem uma). Vêm as 500 atualizadas mais recentemente e todas as mortas, depois cada `delivery.changed`. - **Tasks:** todas, depois cada `task.changed`. - **Chat e timeline** não ficam no store. Cada um carrega uma página (50) do que mostra, pede as anteriores sob demanda com `before` e acrescenta o que chega por notificação (`chat.item`, `chat.delta`, `message.created`).