Skip to content

fix(ux): varredura de skeleton loading — 6 páginas do Financeiro + Iniciativas - #34

Merged
BarryBits merged 6 commits into
mainfrom
fix/skeleton-loading-visao-apuracao
Jul 27, 2026
Merged

fix(ux): varredura de skeleton loading — 6 páginas do Financeiro + Iniciativas#34
BarryBits merged 6 commits into
mainfrom
fix/skeleton-loading-visao-apuracao

Conversation

@BarryBits

Copy link
Copy Markdown
Owner

Varredura de carregamento página a página: o esqueleto passa a refletir a tela de forma fiel, e cada página carrega numa onda só. Seis páginas, validadas uma a uma no browser pelo Lucas.

O padrão que ficou

  1. O esqueleto espelha a moldura real — mesmos <Card>, mesmas grades, alturas medidas do DS — não blocos cinza genéricos.
  2. Texto estático entra escrito (títulos de seção, rótulos de alavanca, nomes das linhas da DRE, legendas). Pulsa só o que vem do banco.
  3. Leitura em TanStack Query: remontar/trocar de aba serve do cache em vez de refazer a tela.
  4. Um gate por página — nada de dois esqueletos em sequência nem de "meio carregado".
  5. Nada afirma antes de saber: sem "sem dados"/"nenhum cadastrado"/"—" durante o load.

Por página

Visão do Projeto (8c8cabe) — a flag RETRATO_EMPRESA nascia false no cliente e a árvore trocava de forma quando o override resolvia, desmontando a aba e refazendo a leitura inteira. Corrigido com featureDefault() isomórfico + slots fixos.

Apuração (d5c7824) — a DRE caía no estado vazio "Sem dados no recorte" (com CTA!) enquanto as transações vinham, e repetia a cada troca de mês. O shell carregava o grupo duas vezes por entrada (mes era dependência do próprio carregar).

Cockpit (d36ed57) — quatro ondas encadeadas, duas delas sem alimentar a tela: o Cockpit não usa mes nem a cobertura do grupo, e do overview usa só o runway (visível apenas na visão Análise). O card de drivers escrevia a narrativa da causa antes do ciclo chegar; o ponteiro do HTR entrava depois e empurrava a página. Herói prometia 560px para ~796 reais.

Endividamento (5b3af9b) — dois Loader2 girando dentro do conteúdo já pintado (contratos do cadastro e taxas de cartão), e a seção "Taxas de cartão" inteira faltava no esqueleto. Resolvido com query-options de chave compartilhada: a mãe dispara em paralelo e gateia; os filhos leem do mesmo cache e seguem donos do próprio refresh.

Simulador (f3824c6) — o esqueleto prometia uma fileira de 3 cards que não existe mais (o antigo tripé, absorvido pela "leitura da casa") e cobria ~1/3 da altura. Componente partido em porta + mesa para semear os defaults editáveis no useState inicial. Cenários salvos passaram a morar no cache — cópia local ficaria para trás e sair-e-voltar traria a lista velha.

Iniciativas (c7cb9f8) — o banner "extrato pendente, ciclo travado" só existia na Visão Geral: quem abrisse direto em ?tab=execucao não via aviso nenhum, e o tab está na URL. A Visão Geral também podia ficar presa no esqueleto para sempre se a leitura falhasse.

Fora de escopo

Caixa & Projeção, Resultado, Capital de giro e Pessoas ficaram de fora (páginas ainda não prontas). O spinner de auth do PageLayout fica como está — decisão do Lucas.

Gate

tsc 0 · eslint 0 novos (comparado contra HEAD nos mesmos arquivos, a cada commit) · next build ✓ · vitest 1323/1323 · zero migration.

🤖 Generated with Claude Code

BarryBits and others added 6 commits July 27, 2026 12:40
fix(visao): esqueleto fiel e um carregamento só na Visão do Projeto

O esqueleto não era só impreciso — ele escondia um carregamento duplicado.

1. A flag RETRATO_EMPRESA nascia `false` no cliente e só depois consultava o
   override no banco. Como ela está global ON, a árvore SEMPRE mudava de forma
   quando o override resolvia (<RaioXMesTab/> na raiz → <div><PremiumTabs/>
   <RaioXMesTab/></div>), o que desmontava a aba e refazia a leitura inteira: o
   esqueleto reaparecia do zero e a barra de abas caía na tela empurrando tudo.
   Corrigido com featureDefault() (isomórfico SSR↔cliente — não lê env, que não
   existe no bundle do browser) + slots fixos no shell.

2. A faixa "Composição & trajetória" tem ~1.100px reais (histórico 382 + entradas
   por forma ~254 + 2 gráficos de 422) e era representada por UM bloco de 288px.
   A página saltava quando o dado chegava.

3. A aba Retrato tinha DOIS esqueletos diferentes em sequência (intervalo de meses
   → seções).

As leituras foram para o TanStack Query (mesmo QueryProvider do layout de projeto):
trocar de aba passa a servir do cache em vez de refazer a tela, e trocar de
período/janela mantém os números anteriores atenuados via keepPreviousData.

Peças de esqueleto compartilhadas em components/visao/skeleton-pecas.tsx, com as
medidas do DS documentadas (Card com título+subtítulo = 74px de header + p-6 +
altura do gráfico; RetratoKpi = 112px; DreLinhaCard = 153px).

Gate: tsc 0 · eslint 0 · next build 0 · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@
fix(apuracao): DRE não afirma mais "sem dados" durante o load + grupo carrega 1x

A Apuração tinha mais problema de carregamento do que de esqueleto.

1. A DRE MENTIA. ResultsTab caía no estado vazio "Sem dados no recorte — o período
   selecionado não tem transações categorizadas" (com CTA "Ir para Categorização")
   sempre que dreMultiMensal fosse null — e ele é null durante todo o load, porque
   selectedPeriod começa null e só é escolhido por effect depois dos exercícios.
   Pior: repetia a CADA troca de mês, porque a query do recorte zerava na troca de
   chave. O controller agora expõe isCarregandoResultado, cobrindo a janela inteira
   de ignorância (exercícios → escolha do recorte → transações → snapshots e
   detalhamentos; sem os dois últimos um mês FECHADO era desenhado como "cru" e
   trocava de valor na tela). Com keepPreviousData, trocar de mês mantém a DRE
   anterior atenuada em vez de sumir.

2. O SHELL CARREGAVA O GRUPO DUAS VEZES em toda entrada: carregar() rodava com o
   mês vigente, descobria o último mês FECHADO, chamava setMes() — e `mes` era
   dependência do próprio carregar, então o effect refazia projeto + grupo +
   cobertura do zero, com setLoading(true) no meio. Como o mês vigente quase nunca
   está fechado, era em praticamente toda visita. Viraram duas leituras separadas
   (escopo 1x / cobertura por mês) com o mês default derivado, sem effect.

3. A CATEGORIZAÇÃO QUICAVA esqueleto → tabela vazia com 0% → esqueleto → tabela:
   os effects de ano e mês trocavam a queryKey depois do load. keepPreviousData +
   isCarregandoCategorizacao (que inclui o vão entre "exercícios chegaram" e "o
   effect escolheu o ano") + atenuar in-place na troca.

4. `aba` era setState-em-effect (estado derivado disfarçado de sincronização) — o
   lint só acusou depois que as deps viraram valores derivados de render. Virou
   derivação pura, removendo o abaTocada.

Fidelidade: DreSkeleton genérico → espelha PeriodPicker + faixa-veredito + cartão
da DRE Gerencial; PeriodPickerSkeleton é compartilhado pelas duas etapas do load
(shell → aba) pra a transição não trocar de desenho; chips de mês 48→60px; header
da categorização 64→70px; tiles da ingestão 138→146px; o trilho não promete mais o
card "Aguardando distribuição" (é condicional); o tile "Aguardando você" e a
identidade do cabeçalho pulsam em vez de mostrar 0/"Grupo" e corrigir depois.

Gate: tsc 0 · eslint 0 erros (as 9 warnings do ResultsTab são pré-existentes,
provadas contra o HEAD) · next build 0 · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@
…da tela

O Cockpit (home do Financeiro) carregava em QUATRO ondas encadeadas, cada uma
com o seu esqueleto: escopo+cobertura do grupo (Shell) → overview do Painel →
pacing/prontidão/tesouraria (view) → ABC+ciclo e iniciativas dentro do conteúdo.
As duas primeiras ondas nem alimentam a tela: o Cockpit não usa o `mes` nem a
cobertura, e do overview usa só o runway — que aparece na visão Análise.

- Shell: o corpo do Cockpit de empresa gateia só no escopo da URL (gvm). A
  identidade do cabeçalho segue pulsando sozinha (é dado). Falha de
  escopo/cobertura não derruba mais a home.
- Painel: Cockpit renderiza direto; o runway chega pulsando no seu tile
  (FinKpiCard ganhou `loading`) em vez de segurar a tela inteira.
- View: leitura única em TanStack Query (remontagem/volta serve do cache), com
  ABC+ciclo e iniciativas do HTR na mesma onda. Some o "meio carregado": o card
  de drivers escrevia a narrativa da causa ANTES do ciclo chegar (o texto mudava
  embaixo do olho do dono) e o ponteiro do HTR entrava depois, empurrando a
  página.
- Esqueleto fiel: espelha a moldura real (Card do herói + caixa do gráfico de
  440px + 4 tiles com spark + card dos drivers em 2 colunas), ~796px de herói no
  lugar de um bloco cinza de 560 — a página não cresce mais quando o dado chega.
  Conhece a visão (Rota|Análise) e não promete o ponteiro do HTR, que é
  condicional. Mesmo tratamento no cockpit de GRUPO.
- FrescorSelo reserva a pílula enquanto lê: ela dividia a linha com o toggle
  Rota|Análise e o empurrava ao aparecer.

Gate: tsc 0 · eslint 10 err (== baseline HEAD nos mesmos arquivos, 0 novos) ·
next build ✓ · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…a o esqueleto

A página tinha TRÊS ondas escondidas depois que o esqueleto saía: o snapshot do mês
(seção), os contratos do cadastro (RankingContratosTable, quando o mês não tem
ranking) e as taxas de cartão + transações (CardRatesSection) — as duas últimas com
**spinner girando dentro do conteúdo**, e a de cartão nem existia no esqueleto: a
página crescia uma seção inteira quando o dado chegava.

- Leituras centralizadas em `queries.ts` com chaves compartilhadas: a seção dispara as
  três em paralelo e só destrava a tela quando as três voltam; os filhos leem do mesmo
  cache (TanStack dedupa — uma requisição só) e seguem donos do próprio refresh depois
  de cadastrar/editar/remover.
- Esqueleto agora espelha a tela inteira, com as molduras reais (hero "Leitura do mês",
  os KPIs com faixa-bar e a régua "O que significa", tabela de contratos, taxas de
  cartão, sinais) — e os títulos de seção, a nota do grupo e a legenda dos semáforos
  entram JÁ ESCRITOS: são estáticos, não têm por que pulsar.
- Trocar de mês mantém a tela anterior (keepPreviousData) em vez de colapsar no
  esqueleto; o Shell já atenua o corpo enquanto o novo mês vem.
- Shell: o corpo do Endividamento não espera mais a cobertura do grupo — ele só precisa
  do escopo da URL e do mês âncora. Mesmo tratamento que o Cockpit ganhou.
- Guardas nos filhos: se um dia montarem sem o cache quente, pulsam em vez de afirmar
  "nenhum contrato/taxa cadastrada" — e se desmentir logo depois.

Gate: tsc 0 · eslint 0 err / 0 warn (== baseline HEAD nos mesmos arquivos) ·
next build ✓ · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
O dado do Simulador já vinha numa onda só (4 fontes em paralelo) — o problema era o
esqueleto: prometia uma fileira de 3 cards que NÃO EXISTE MAIS (o antigo tripé
DRE/Caixa/Meta, absorvido pela "leitura da casa") e parava no primeiro grid, cobrindo
cerca de um terço da altura real. O resto da página — conta inversa, retirada segura,
o caixa em barras, as 5 decisões prontas, cenários salvos — nascia de uma vez.

- Esqueleto espelha a tela inteira, com as molduras reais. Como quase tudo aqui é
  ESTÁTICO, entra escrito: os rótulos das alavancas (e no zero os valores são "como
  hoje" de qualquer jeito), os títulos das seções, o cabeçalho e os nomes das linhas da
  DRE (reusa `LINHAS_DRE`), os títulos das 5 decisões. Pulsa só o que vem do banco. O
  "Lado a lado" fica de fora — é condicional (≥2 colunas).
- Leitura em TanStack Query: voltar pra mesa serve do cache em vez de refazer as 4
  chamadas.
- Componente partido em porta (`SimuladorCasaTab`, carrega e destrava) + mesa
  (`MesaDeDecisao`, só monta com dado). Assim os defaults editáveis — custo/cabeça
  medido da folha, mês do estoque — são semeados no `useState` inicial: com um effect
  pós-render, o card "Contratar pessoas" piscaria custo 0 antes do valor real.
- Cenários salvos deixam de ser cópia local e passam a morar no cache (salvar/excluir
  escrevem lá): a lista local ficaria para trás e sair-e-voltar traria a lista velha,
  sem o cenário recém-salvo.
- Shell: o Simulador em escopo de empresa não espera mais a cobertura do grupo (só o
  escopo da URL) — e no agregador o esqueleto passa a ser o do picker de empresa, que é
  o que a tela realmente entrega ali, em vez da mesa inteira.

Gate: tsc 0 · eslint 0 err / 0 warn (== baseline HEAD nos mesmos arquivos) ·
next build ✓ · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eletos fiéis

O banner D1 — "extrato de <mês> pendente, o ciclo está travado" — é o recado mais
importante da tela, e só existia na Visão Geral: o painel que o alimenta só era
carregado quando `activeTabId === "overview"`. Quem abrisse a página direto em
?tab=execucao (link, bookmark, F5 na aba) não via aviso nenhum. E, mesmo na Visão
Geral, ele nascia depois do resto e empurrava a página pra baixo.

- O painel passa a carregar sempre (é do shell, não de uma aba), e a faixa do banner
  fica reservada enquanto vem — não empurra mais o conteúdo.
- As 4 leituras viram chaves compartilhadas em `queries.ts` (TanStack Query). A tela é
  feita de pular entre workspaces: antes, cada volta refazia a consulta e repintava o
  esqueleto. Cada aba segue dona do seu refresh — e agora invalida também o painel,
  porque aprovar/relatar/abortar move o funil da Visão Geral na hora.
- Esqueletos espelham a moldura real, com os títulos de seção e os 5 passos do funil
  escritos (são estáticos): Visão Geral (herói do laudo + funil + checklist do dia 1º),
  Ideação (3 seções), Execução (card de teste com escopo 2×2), Resultados (chips +
  dossiês). Antes eram 2-3 retângulos genéricos por aba.
- Visão Geral não fica mais presa no esqueleto se a leitura falhar: `!painel` agora diz
  que não deu, em vez de pulsar para sempre.
- Relato de execução: o texto já salvo volta a aparecer no campo (era semeado no estado
  local pelo fetch antigo; agora vem do dado, com o rascunho local por cima).

Gate: tsc 0 · eslint 0 err / 0 warn (== baseline HEAD nos mesmos arquivos) ·
next build ✓ · vitest 1323/1323.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 27, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
atr-os Building Building Preview, Comment Jul 27, 2026 7:15pm

@supabase

supabase Bot commented Jul 27, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project tdlxqqgechxhkygdmsxq because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BarryBits
BarryBits merged commit 0581118 into main Jul 27, 2026
3 of 4 checks passed
@BarryBits
BarryBits deleted the fix/skeleton-loading-visao-apuracao branch July 27, 2026 19:16
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