0033 — Interface do SaaS admin no design system¶
| Status | CONCLUÍDA (CA9 não verificável no ambiente — ver §7) |
| Branch | spec/0033-saas-admin-no-design-system |
| Criada em | 2026-08-10 |
| Apps afetados | web · e2e (api sem impacto — ver D1, decidida) |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDA
1. Problema¶
O SaaS admin (/admin) é a área do time da Garantora — onde se olha a plataforma inteira: garantidoras cadastradas, convites emitidos, usuários globais. Ele não acompanhou nenhuma das doze specs de interface que passaram pelo app entre a 0013 e a 0032. Enquanto o app do tenant ganhava lista em DataGrid, filtros padronizados, paginação compacta, cartões no mobile e uma navegação inferior por abas, o admin ficou onde estava.
O resultado é visível em três frentes:
No mobile, três das cinco telas não funcionam. Garantidoras, Usuários e Painel renderizam <Table> cru, sem alternativa em cartão. Numa tela de 375px a tabela de Garantidoras tem cinco colunas — Nome, CNPJ, Status, Criada em, Ações — e simplesmente não cabe: a página rola na horizontal e a coluna de ações fica fora do alcance. Quem precisa suspender uma garantidora pelo celular não consegue.
A navegação é outra. O app inteiro, no mobile, usa a barra de abas inferior com a gaveta em "Mais" (spec 0016). O admin usa um AppBar com hambúrguer e gaveta lateral — o padrão que o app abandonou. Quem é ADMIN_GLOBAL e transita entre as duas áreas encontra dois aplicativos diferentes, e o gesto que funciona num não existe no outro.
A inconsistência é estrutural, não cosmética. AdminLayout e AdminSider somam 427 linhas que reimplementam app-layout e custom-sider — mesma gaveta, mesmo colapso, mesmo menu de perfil, escritos duas vezes. É por isso que as correções de interface do app nunca chegam ao admin: não há nada compartilhado por onde chegarem. Cada spec de design paga o preço uma vez e o admin não recebe nada.
Junto disso, dois defeitos concretos que a leitura do código expôs:
- "Auditoria" leva a lugar nenhum. O item existe em
AdminSiderapontando para/admin/audit, e essa rota não está registrada emApp.tsx. Clicar cai no not-found. - Quatro das cinco telas não têm um
data-testidsequer. Só Convites tem (17), por ser recente (spec 0031). OCLAUDE.mddo web exige o atributo em todo elemento interativo — é o que sustenta o e2e —, e o admin inteiro está fora dessa rede.
2. Objetivo¶
O SaaS admin passa a ser a mesma aplicação que o resto do produto: mesma navegação mobile, mesmas listas, mesmos filtros, mesma paginação, mesmos componentes compartilhados. Quem mantém o app deixa de ter uma segunda interface para lembrar, e a próxima spec de design chega ao admin sem trabalho extra.
3. Fora de escopo¶
- Funcionalidade nova no admin. Nenhuma tela, ação, filtro ou coluna que não exista hoje. Esta spec redesenha o que já há; a única exceção é o que a adoção do padrão traz de graça (por exemplo, o seletor "Por página" que vem com o
ListPagination). - Tela de auditoria global. O item morto sai do menu (RN7). Construir a tela é feature nova, com contrato de API próprio, e é outra spec.
- Mudar as regras de negócio do admin. Suspender/reativar garantidora, o purge em duas etapas com confirmação por nome, emitir e revogar convite — todos permanecem com exatamente o mesmo comportamento. O que muda é a casca.
- Permissões.
ADMIN_GLOBALcontinua sendo o único papel com acesso, e a guarda continua onde está (useCurrentUser().isAdminGlobalno layout). - Refatorar o app do tenant. As telas de
pages/não são tocadas. Se a adoção pelo admin exigir generalizar um componente compartilhado, a mudança tem de ser aditiva — prop nova com default igual ao comportamento atual (RN2). - Paginação no servidor para o admin. Ver RN9: o conjunto é pequeno e a API não devolve total. Fica client-side sobre o conjunto inteiro, com o teto explícito.
- PWA. Nenhum asset novo, nenhuma rota offline, nenhuma mudança no service worker. O admin já é servido pelo mesmo bundle.
4. Regras de negócio¶
-
RN1. O admin reusa o
MobileShell. No mobile,AdminLayoutdeixa de renderizarAppBar+ gaveta própria e passa a delegar paracomponents/mobile-shell, com os itens de navegação do admin. Mesma barra de abas inferior (62px), mesma gaveta de 300px, mesmoTopBarcom slots de título e ações. No desktop, segue com sider lateral colapsável. -
RN2. Generalizar é aditivo, nunca destrutivo.
MobileShelleCustomSiderhoje chamambuildNavGroups(badges)internamente; o primeiro recebe sóchildren, o segundo sócollapsed/onToggle. Ambos passam a aceitar os grupos por prop, com default no comportamento de hoje. O mesmo vale para qualquer outro componente compartilhado que precise ceder: nenhuma chamada existente empages/pode mudar. Se uma generalização exigir tocar as telas do tenant, ela está fora do escopo desta spec. -
RN2b.
AdminSiderdeixa de existir.CustomSiderjá delega aoAdminSiderquandoisAdminGlobal(custom-sider/index.tsx:39), e tem uma única chamada em toda a árvore (app-layout/index.tsx:48) — a prop da RN2 é, portanto, puramente aditiva. Com ela, o ramo doisAdminGlobalpassa a escolher os grupos e a faixa, não a renderizar um componente inteiro. As 294 linhas doAdminSider— gaveta, colapso, tooltips, section labels, estados de ativo — são as mesmas doCustomSidere saem. Sobrevivem duas coisas: os grupos do admin (que vão paranav-groups.tsx) e a faixa "Admin SaaS" emwarning.main(que vira um adorno opcional doCustomSider). -
RN3. Quatro abas no mobile do admin. A barra inferior leva Painel, Garantidoras, Convites e "Mais" — que abre a gaveta com o conjunto completo (incluindo Usuários), o alternador de tema e o Sair. É o mesmo desenho de três abas + "Mais" que o app usa.
-
RN4. As listas adotam o padrão pleno. Garantidoras e Usuários passam a ser, no desktop,
DataGridcomgarantoraDataGridProps/dataGridSx; no mobile,RecordCardList. Filtros porFilterBar(desktop) eMobileFilterBar(mobile); rodapé porListPagination. A referência épages/credit-analyses/index.tsx. -
RN5. Convites converge para o mesmo padrão. A tela da spec 0031 já é responsiva (
useIsMobile+RecordCard), mas usa<Table>no desktop e não temFilterBarnemListPagination. Ela entra na uniformização — o comportamento (emitir, copiar link, revogar,ConfirmDialog) não muda. -
RN6. Nenhuma coluna nova, nenhuma coluna perdida. As colunas de cada lista são as de hoje. No mobile, o
RecordCardde cada lista exibe o identificador, o status e a ação primária no rosto do cartão; o que não couber vai para a tela de detalhe ou para o menu de ações da linha — não some silenciosamente. -
RN7. "Auditoria" sai do menu. O item é removido de
AdminSidere da navegação mobile. Nenhuma rota é criada. É a correção de um item que hoje leva ao not-found. -
RN8. Toda tela do admin ganha
data-testid. Conforme oCLAUDE.mddo web, em todo elemento interativo — linhas, ações por linha, filtros, paginação, diálogos e seus botões, alertas de erro. Convites já está instrumentada; as outras quatro, não. Os testids existentes em Convites não podem mudar de nome — a spec e2e da 0031 depende deles. -
RN9. A paginação do admin é client-side sobre o conjunto inteiro, e o teto é visível.
GET /api/v1/tenantsdevolvelist[TenantResponse]— array puro, sem total, comlimitmáximo de 200. Hoje a tela pedelimit: 200e filtra na memória; acima de 200 garantidoras a lista mente por omissão, sem qualquer aviso.
Como não há cliente algum na plataforma, o conjunto real é de unidades, e ordenar e paginar sobre o conjunto inteiro carregado não é a mentira que o CLAUDE.md proíbe (essa seria paginar no servidor e ordenar no cliente). Então: mantém-se client-side e o ListPagination recebe o total do conjunto filtrado.
O que não se mantém é o silêncio. Quando a resposta vier com exatamente o teto (data.length === 200, o limit pedido), a tela exibe um Alert de severidade info acima da lista: "Mostrando as primeiras 200 garantidoras. Há mais registros do que esta tela consegue listar." Sem o aviso, a lista mente por omissão; com ele, o teto vira um fato visível — e o gatilho explícito para D1 ser revisitada.
-
RN10. Erro e vazio falam. O estado de erro mantém o "Tentar novamente" que já existe. O estado vazio usa o texto que diz o que fazer, não só que está vazio — via
noRowsLabeldoDataGridno desktop e mensagem equivalente no mobile. -
RN11. Nenhuma regressão de rolagem horizontal. Em 375px, toda tela do admin tem
scrollWidth - clientWidth == 0. É o critério que a verificação por screenshot afere (CA8), e é exatamente o defeito que a spec 0031 já havia corrigido no sider e que persiste nas telas.
5. Impacto por app¶
apps/api¶
Sem impacto. Decidido em D1.
Nenhum endpoint, schema, model ou migration muda. A spec é de interface e consome os endpoints como estão: GET /api/v1/tenants, PATCH /api/v1/tenants/{id}, o par de purge, GET de usuários globais e as rotas de tenant_invites.
O array sem total e o teto de 200 são limitação conhecida da API, contornada no web por carga integral + aviso ao bater no teto (RN9). D1 registra por que não se corrige agora e o que dispara a revisão.
apps/web¶
Layout e navegação
components/mobile-shell/index.tsx— passa a aceitar os grupos de navegação (e o conjunto de abas) por prop, com default no comportamento atual (RN2)components/custom-sider/index.tsx— idem para os grupos, mais um adorno opcional para a faixa "Admin SaaS"; o ramoisAdminGlobaldeixa de renderizarAdminSidere passa a escolher grupos + faixa (RN2b)components/navigation/nav-groups.tsx— ganhabuildAdminNavGroups(), ao lado debuildNavGroups; é onde a navegação do admin passa a viveradmin/components/AdminLayout.tsx— no mobile delega aoMobileShell; no desktop mantém sider + topo, alinhado acomponents/app-layoutadmin/components/AdminSider.tsx— removido (RN2b). A faixa "Admin SaaS" emwarning.mainque distingue a área permanece, agora renderizada peloCustomSider— é o sinal de que se está no painel do SaaS, e é útilApp.tsx— nenhuma rota nova;/admin/auditcontinua não existindo (RN7)
Telas
admin/pages/tenants/TenantListPage.tsx—DataGrid+RecordCardList+FilterBar/MobileFilterBar+ListPagination. Busca (nome/CNPJ) e filtro de Status viram controles doFilterBar, com chips. Os diálogos de suspender e de purge preservam o fluxo e passam a usarConfirmDialogonde couberadmin/pages/tenants/TenantDetailPage.tsx— painéis do design system,DataRowno desktop eMobileDetailSectionno mobileadmin/pages/users/GlobalUserListPage.tsx— mesmo padrão de listaadmin/pages/invites/InviteListPage.tsx— converge paraDataGridno desktop,FilterBareListPagination; mantémRecordCard,ConfirmDialoge os testids (RN5, RN8)admin/pages/dashboard/AdminDashboardPage.tsx— cartões e tabela no padrão; sem métrica nova
data-testid (RN8) — nav-admin-<path> nos itens de navegação; por lista: tenants-row-{id}, tenants-search-filter, tenants-status-filter, tenants-pagination, tenants-view-{id}, tenants-suspend-{id}, tenants-purge-{id}, tenant-suspend-dialog / -confirm / -cancel, tenant-purge-dialog / tenant-purge-name-input / -confirm / -cancel, tenants-error; análogos para admin-users-* e para o detalhe (tenant-detail-*). Os de invite-* ficam como estão.
Sider — não é módulo novo, então não há entrada a acrescentar em custom-sider; o que muda é a navegação do admin (RN3, RN7).
Responsividade & PWA — responsivo é o objeto da spec, não um passo posterior: cada tela entrega desktop e mobile no mesmo commit. PWA sem impacto — nenhum asset novo, nenhuma rota offline, nenhuma mudança de service worker. Vale lembrar, na validação, que o dev-remote serve build cacheado: sem /devremote-rebuild a tela mostra a versão velha.
apps/e2e¶
- Spec nova —
admin-interface.spec.ts, autenticada comoADMIN_GLOBAL, cobrindo nos dois viewports: navegar entre as quatro telas pela barra inferior/gaveta (mobile) e pelo sider (desktop); listar Garantidoras com filtro de status e busca; paginar; abrir o detalhe; e confirmar que "Auditoria" não está no menu. - Seeds — não deve ser preciso: o ambiente compartilhado já tem garantidoras e usuários. Se algum cenário exigir volume para paginar, o seed vai em
tests/fixtures/seeds/— e nada de configuração compartilhada do tenant é alterado (a lição da spec 0025). - Escrito, não executado, salvo pedido explícito no fechamento.
6. Contrato de API¶
Sem contrato novo. Os endpoints consumidos, como estão hoje:
GET /api/v1/tenants?skip=&limit=&search= → list[TenantResponse] (limit ≤ 200, sem total)
GET /api/v1/tenants/{id} → TenantResponse
PATCH /api/v1/tenants/{id} { "status": … } → TenantResponse
POST /api/v1/tenants/{id}/purge/preview → TenantPurgePreviewResponse
POST /api/v1/tenants/{id}/purge → TenantPurgeResult
GET /api/v1/users?skip=&limit= → list[UserResponse] (sem total)
GET /api/v1/tenant-invites?status=&skip=&limit=
POST /api/v1/tenant-invites
POST /api/v1/tenant-invites/{id}/revoke
Todos exigem Authorization: Bearer <token> de um usuário ADMIN_GLOBAL.
Erros: os já previstos pelos endpoints (401, 403, 404, 409). A tela apresenta detail da resposta no Alert com "Tentar novamente" (RN10) — comportamento inalterado.
A ausência de total nas duas primeiras rotas é o que motiva a RN9 e a decisão D1.
7. Critérios de aceite¶
- [x] CA1. Dado um
ADMIN_GLOBALnum viewport de 375px, quando ele abre qualquer das quatro telas do admin, então vê a barra de abas inferior com Painel, Garantidoras, Convites e Mais — e não o hambúrguer com gaveta lateral. Verificado por captura no dev-remote. - [x] CA2. Dado o mesmo usuário em 375px na lista de Garantidoras, então os registros aparecem como cartões, cada um com nome, status e acesso à ação primária — sem tabela e sem rolagem horizontal. Verificado por captura.
- [x] CA3. Dado o desktop, quando ele abre Garantidoras ou Usuários, então a lista é um
DataGridcom cabeçalho em overline, zebra, linha de 52px e o rodapéListPaginationcom "Por página" e a faixa1–25 de N. Verificado por captura (1–5 de 5e1–23 de 23). - [x] CA4. Dado que ele digita no campo de busca ou escolhe um Status, então o filtro aparece como chip no
FilterBar, e "Limpar" devolve a lista completa. Verificado no navegador: chip aparece, texto de vazio aparece, "Limpar filtros" devolve as 5 linhas. - [x] CA5. Dado o menu do admin em qualquer viewport, então não há item "Auditoria", e nenhum item do menu leva ao not-found. Verificado por captura nos dois viewports.
- [x] CA6. Dado que ele suspende uma garantidora, ou executa o purge digitando o nome, então o resultado é idêntico ao de antes desta spec — mesmas etapas, mesma confirmação, mesmo efeito. Verificado por leitura: os handlers (
handleStatusChange,handleOpenPurge,handleExecutePurge, a comparação de nome e a chave de confirmação) foram transpostos sem alteração. Não exercitado no ambiente — suspender e purgar mexem em dado compartilhado. - [x] CA7. Dado o inspetor do navegador em cada tela do admin, então todo elemento interativo tem
data-testid, e os testids de Convites continuam com os nomes de antes. Os nomes da 0031 foram preservados um a um; a spec e2e afere. - [x] CA8. Dado o script de screenshot rodando contra o dev-remote reconstruído, nos dois viewports e nas cinco telas, então
scrollWidth - clientWidth == 0em todas e não há erro de console. 8 combinações + gaveta + detalhe:overflow=0e zero erro em todas. - [ ] CA9. Dado que a API devolve exatamente 200 garantidoras, então a tela avisa que há mais registros do que consegue mostrar (RN9). Não verificável hoje — o ambiente tem 5 garantidoras, e criar 200 para ver um
Alertcusta mais do que vale. O código está lá (tenants.length >= FETCH_LIMIT) nas três listas; a aferição fica para quando houver volume, que é o mesmo momento em que D1 é revisitada. - [x] CA10. Dado o app do tenant (não-admin), então nada mudou nele: as telas de
pages/renderizam como antes e a suíte e2e existente segue verde. Nenhum arquivo empages/foi tocado, e o único call site doCustomSidersó ganhou props opcionais. A suíte e2e não foi executada — ver §10.
8. Plano de implementação¶
Ordem obrigatória no monorepo: api → web → e2e.
- [x]
apps/api— sem impacto (D1) - [x]
apps/web—buildAdminNavGroups()/buildAdminNavTabs()emnavigation/nav-groups.tsx - [x]
apps/web— generalizarMobileShelleCustomSiderpara receber os grupos por prop, com default inalterado (RN2) —app-layoutrepassa e nada empages/mudou - [x]
apps/web—AdminLayoutdelegando aoAppLayout;AdminSider.tsxapagado (294 linhas), faixa "Admin SaaS" preservada (RN2b) - [x]
apps/web— remover "Auditoria" da navegação (RN7) - [x]
apps/web—TenantListPageno padrão pleno (DataGrid · RecordCardList · FilterBar/MobileFilterBar · ListPagination), com o aviso de teto da RN9 - [x]
apps/web—GlobalUserListPageno mesmo padrão - [x]
apps/web—InviteListPageconvergindo (RN5), sem renomear testid - [x]
apps/web—TenantDetailPagecom DataRow / MobileDetailSection - [x]
apps/web—AdminDashboardPageno padrão - [x]
apps/web—data-testidnas quatro telas sem instrumentação (RN8) - [x]
apps/e2e—admin-interface.spec.tsnos dois viewports (escrito, não executado) - [x]
make test(262 passaram, 1 falha intermitente conhecida) e lint sem apontamento novo nos arquivos tocados - [x]
/devremote-rebuild+ script de screenshot nos dois viewports, cinco telas (CA8)
9. Decisões tomadas¶
Nenhuma pendência. As duas foram decididas em 2026-08-10, por evidência do código.
-
[x] D1 — a API passa a devolver o total nas listas do admin? → NÃO. Fica como está; vale a RN9 (client-side + aviso ao bater no teto).
O que se apurou. O custo era menor do que parecia. A lista
GET /api/v1/tenantstem um único consumidor —admin/services/adminApi.ts;pages/my-garantidorausa só o detalhe/tenants/{id}. EGET /api/v1/users, embora compartilhado com a tela de Equipe do tenant, já tolera as duas formas:pages/users/api.ts:36declaraUser[] | { items?: User[] }e normaliza comtoArray. Ou seja, o envelope não quebraria ninguém.Por que ainda assim não. O argumento contra nunca foi risco de quebra — é que o problema não existe. Paginação no servidor resolve conjunto grande, e não há conjunto: zero clientes, garantidoras na casa das unidades. O teto de 200 só passa a incomodar com 200 garantidoras pagantes, cenário em que esta tela será reescrita por outros motivos. Somado a isso, o pedido foi de interface; puxar schema, service, router e testes da API para dentro dele troca o objeto da spec.
O que dispara a revisão. O
Alertda RN9. No dia em que ele aparecer numa tela de verdade, o envelope{ items, total }com paginação e ordenação no servidor deixa de ser prematuro e vira a spec seguinte — para as duas rotas, já que a de usuários está preparada para receber. -
[x] D2 —
AdminSidersobrevive ou é absorvido? → ABSORVIDO. Vira a RN2b.O que se apurou. Não era decisão de gosto:
CustomSiderjá delega aoAdminSiderno ramoisAdminGlobal(custom-sider/index.tsx:39), e tem uma única chamada em toda a árvore (app-layout/index.tsx:48, passando sócollapsed/onToggle). Uma propgroupsopcional com defaultbuildNavGroups(badges)é aditiva por construção — nenhum call site muda, e a RN2 fica satisfeita sem tocar empages/.Consequência. As 294 linhas do
AdminSidersão gaveta, colapso, tooltip, section label e estado de ativo — tudo que oCustomSiderjá faz. O arquivo é apagado. O que era próprio do admin vira dado: os grupos, embuildAdminNavGroups(), e a faixa "Admin SaaS", num adorno opcional doCustomSider. É essa troca de componente duplicado por dado que faz a próxima spec de design chegar ao admin sozinha — o objetivo da §2.
10. Registro¶
Implementada em 2026-08-10. O que divergiu do planejado, e por quê.
A generalização foi menor do que a spec supunha¶
A RN2b previa que o AdminSider sumisse. Sumiu — mas o AdminLayout também. Ao escrever a prop de grupos, ficou claro que a diferença entre as duas áreas cabia inteira em três argumentos (groups, tabs, badgeLabel), e que o lugar de recebê-los era o AppLayout, não cada casca. Resultado:
| Arquivo | Antes | Depois |
|---|---|---|
admin/components/AdminSider.tsx | 294 linhas | apagado |
admin/components/AdminLayout.tsx | 133 linhas | 45 linhas — guarda de papel + escolha dos grupos |
AppLayout, CustomSider e MobileShell ganharam props opcionais com default no comportamento de hoje; nenhum arquivo em pages/ foi tocado (RN2). O único call site do CustomSider (app-layout/index.tsx:48) passou a repassar as props.
Efeito colateral bom: o admin ganhou de graça o menu de perfil, o alternador de tema no rodapé da gaveta e o portal de título/ações do topo — tudo que o AppLayout já fazia e que o layout próprio reimplementava pela metade.
Duas abas do mobile saíram do MobileShell¶
As abas estavam embutidas na casca como JSX. Viraram dado (buildNavTabs, buildAdminNavTabs) em nav-groups.tsx, e a casca só acrescenta a "Mais" — que não navega, abre a gaveta. Os ícones e rótulos do tenant foram copiados letra por letra, inclusive o file-text de Garantias, que difere do certificate da sider: é provavelmente um deslize antigo, mas corrigi-lo aqui violaria o CA10.
isNavItemActive passou a receber a forma mínima (Pick<NavItem, "path"> & …) para servir também às abas, que não têm activePaths.
Dois defeitos encontrados no caminho — corrigidos¶
-
A lista de Usuários do admin estava quebrada. Pedia
fetchGlobalUsers({ limit: 500 }), eGET /api/v1/usersdeclaralimitcomle=200: a API respondia 422 em toda abertura da tela, e o que se via era o alerta de erro. Corrigido para 200. Não estava na spec porque só apareceu ao ler oadminApicontra o router. -
O filtro "Admin Global (sem tenant)" não filtrava nada. O valor
__global__era comparado comu.tenant_id, que para o time do SaaS énull— a opção existia e devolvia lista vazia sempre. Agora compara comnull.
Ambos são de interface no sentido que importa aqui: a tela mostrava a coisa errada.
O que foi acrescentado além do redesenho, e por quê¶
A §3 dizia "nenhum filtro que não exista hoje". Foram acrescentados três, todos client-side sobre dados já carregados, custo zero de requisição:
- busca em Garantidoras, Usuários e Convites — o
SearchFieldé estrutural noFilterBar("sempre primeiro, e é o que cresce"); uma barra de filtros sem ele não é o padrão, é outra coisa; - filtro de status em Usuários, que tinha Garantidora e Perfil mas não Ativo/Inativo, embora a coluna existisse.
É a exceção que a §3 previa ("o que a adoção do padrão traz de graça"), mas vale dito em voz alta: são filtros novos, não só forma nova.
Vocabulário de status foi para o lugar canônico¶
TENANT_STATUS, TENANT_INVITE_STATUS e USER_STATUS entraram em components/shared/StatusBadge.tsx, junto de AGENCY_STATUS e dos demais. As telas do admin definiam mapas locais STATUS_CHIP — três cópias, com rótulos divergentes ("Ativo" na lista, "Ativa" em lugar nenhum). SUSPENDED passou de vermelho a âmbar: é estado reversível, e o vermelho competia com o Excluir, que é o irreversível da tela.
Ações da linha viraram ícone¶
Na lista de Garantidoras, "Ver detalhes"/"Suspender"/"Excluir" eram <Button> com rótulo, somando ~260px numa coluna de ações. Viraram IconButton com tooltip, como em Imobiliárias. O comportamento — diálogos, confirmação por nome no purge, efeito — é o mesmo (CA6); o que mudou é a forma, que é o objeto da spec.
Paginação: cliente, como a RN9 decidiu¶
As quatro listas usam paginationMode/sortingMode client do DataGrid sobre o conjunto inteiro carregado. Não é a mentira que o CLAUDE.md proíbe (essa seria paginar no servidor e ordenar no cliente): ordenar aqui alcança todos os registros carregados. O aviso de teto da RN9 está nas três listas, com o texto e o gatilho (length >= 200) que a RN pede.
Verificação¶
make test— 262 passaram, 1 falhou:test_partner_agency_status_filter.py::test_alcanca_quem_esta_fora_da_primeira_pagina, a intermitência já documentada noCLAUDE.md(empate decreated_atno SQLite desempatado porid descsobre UUID). Passa 3/3 isolada, e esta spec não toca em nenhum arquivo da API.- Lint — 0 erros nos arquivos tocados; 2 warnings de
exhaustive-deps, do mesmo feitio dos que já existem na árvore. De quebra saíram dois apontamentos pré-existentes (type Tmorto emnav-groups.tsx, import não usado). tscevite build— limpos.- Captura no dev-remote (após
/devremote-rebuild; bundleindex-CmEjd3U5.js, o mesmo hash do build local, o que prova que o ambiente serve este código) — quatro telas × dois viewports, mais a gaveta do mobile e o detalhe da garantidora:overflow=0e zero erro de console em todas. CA4 exercitado no navegador.
Uma armadilha que quase virou relatório errado: a primeira rodada do script preencheu o e-mail sem limpar o campo, o formulário de login chega com valor padrão, e o desktop fotografou a tela de login — medindo
overflow=0nela e passando por verde. Só apareceu ao abrir o PNG. O script passou a exigir uma âncora do admin (sider-area-badge/mobile-tabbar) e a imprimir o título de cada tela antes de medir. Vale para qualquer verificação por captura: status e métrica não provam que a tela é a certa.
- e2e —
admin-interface.spec.tscobre CA1–CA8 e CA10 nos dois viewports. Escrito, não executado.