0026 — Jornadas visuais por processo¶
| Status | CONCLUÍDA |
| Branch | spec/0026-jornadas-visuais-por-processo |
| Criada em | 2026-08-08 |
| Apps afetados | nenhum — é ferramental (.claude/skills/, journeys/, docs/journeys/) |
Status:
RASCUNHO→APROVADA→EM IMPLEMENTAÇÃO→CONCLUÍDA
1. Problema¶
Não existe uma forma de ver um processo inteiro do sistema. Para saber como está hoje a jornada da análise de crédito — da inclusão à aprovação, passando por pendência de documentos — é preciso abrir a aplicação, criar dado à mão em cada estado e navegar tela por tela, nos dois viewports. Leva dezenas de minutos e ninguém repete com frequência.
Três consequências concretas, todas já observadas neste repositório:
-
Defeito de layout só aparece quando alguém tropeça nele. As specs 0016, 0018, 0020, 0022 e 0024 nasceram de problemas de tela — título truncado, campo estourando, ação no lugar errado — encontrados por acaso, não por varredura.
-
A validação visual é refeita do zero a cada spec. Nas specs 0025 e anteriores o procedimento foi sempre o mesmo: escrever um script de Playwright descartável, montar dado pela API, tirar print nos dois viewports, olhar. O script morre no fim da spec e a próxima começa de novo — e uma das cópias chegou a poluir a configuração do tenant de e2e e derrubar três testes que nada tinham a ver com a mudança.
-
Mudança de fluxo não deixa rastro visível. A spec 0025 moveu o parecer do sétimo para o segundo bloco da página e levou as ações de decisão para o cabeçalho. O
git diffmostra isso como TSX; ninguém consegue olhar o antes e o depois da jornada.
2. Objetivo¶
Um comando que, para um processo escolhido, encena um caso verossímil, percorre a jornada inteira em desktop e mobile, e entrega uma página navegável com o caminho percorrido e os prints de cada passo — repetível, versionável no que importa e sem deixar sujeira no ambiente.
Verossímil não é detalhe de acabamento: é o que separa um material que se mostra a alguém de um material que só serve para conferir se a tela subiu.
3. Fora de escopo¶
- Substituir o e2e. A suíte Playwright afirma comportamento e falha quando ele quebra. Esta skill descreve e mostra; ela não é gate de nada e não roda em automação.
- Comparação automática de imagens (visual regression). Fica registrado como possibilidade futura — o mapa e os prints são a matéria-prima —, mas comparar pixel a pixel exige baseline, tolerância e triagem de falso positivo, e é spec própria.
- Mapear todos os processos de uma vez. A entrega exige um processo funcionando fim a fim (§9). Os demais entram depois, e cada um é só um mapa novo.
- Criar tela ou endpoint no produto.
apps/api,apps/webeapps/e2enão mudam. - Rodar contra a
infra/local. O alvo é o dev-remote, pela mesma razão do e2e (CLAUDE.md): o que se valida tem que ser o que vai para o ar. - Publicar a página em algum lugar. Ela é gerada no repositório e aberta no browser; hospedar é outra conversa.
4. Regras de negócio¶
Onde a coisa mora¶
- RN1. A skill vive no projeto, em
.claude/skills/journeys/, mas é escrita para ser portável:SKILL.mdescripts/não citam Garantora, rota, papel nemdata-testiddeste sistema. Tudo que é daqui vive emjourneys/config.jsone nos mapas. Levar a outro projeto é copiar a pasta da skill e escrever umconfig.json.
Foi decidido pôr tudo no projeto em vez de deixar a skill em ~/.claude. O custo assumido é manter cópias em sincronia quando ela for para um segundo projeto; a contrapartida é que a skill entra em PR, é revisada e versionada junto do que ela fotografa.
- RN2. A árvore:
.claude/skills/journeys/
SKILL.md genérico — o procedimento
scripts/capture.mjs genérico — executa um mapa e gera a página
journeys/
config.json do projeto — alvo, autenticação, papéis, viewports
dados.js do projeto — o pool de personas e valores verossímeis (RN8c)
<processo>.json do projeto — o mapa de cada jornada (versionado)
docs/journeys/
index.html a entrega (gerada)
<processo>/*.png os prints (gerados)
- RN3.
docs/journeys/não é versionado (entra no.gitignore). A verdade versionada é o mapa: dele a página se regenera inteira. Um diff de PNG é ilegível, e uma execução completa produz dezenas de imagens — versionar isso incharia o repositório sem dar em troca nada que o diff do mapa não diga melhor.
A configuração do projeto¶
- RN4.
journeys/config.jsondeclara o que a skill não pode adivinhar:
{
"baseUrl": "https://dev.garantora.com.br/app",
"apiUrl": "https://dev.garantora.com.br/api/v1",
"auth": {
"loginPath": "/auth/login",
"tokenField": "access_token",
"storageKey": "garantora-access-token"
},
"roles": { "gestor": "gestor@e2e.test", "analista": "analista@e2e.test" },
"password": "S21d2qu1",
"defaultRole": "gestor",
"viewports": [
{ "nome": "desktop", "width": 1440, "height": 900 },
{ "nome": "mobile", "width": 375, "height": 812 }
],
"beforeRun": "invocar /devremote-rebuild se o código mudou"
}
Os papéis e a senha são os mesmos do apps/e2e/fixtures/roles.ts — a skill lê essa convenção do config, não a reimplementa.
O mapa da jornada¶
- RN5. Cada processo tem um mapa versionado em
journeys/<processo>.json, com passos em ordem. Um passo declara: nome, rota (ou a ação de navegação que chega nela), setup opcional (chamadas de API que produzem o estado), ações opcionais (interações pordata-testidantes do print) e espera (odata-testidque precisa estar visível para o print valer).
{
"processo": "Análise de crédito",
"papel": "gestor",
"passos": [
{
"nome": "Rascunho criado",
"setup": [
{ "metodo": "POST", "rota": "/renters",
"corpo": { "name": "{{inquilino.nome}}", "cpf": "{{inquilino.cpf}}" },
"guardarComo": "inquilino" },
{ "metodo": "POST", "rota": "/credit-analyses", "corpo": "…", "guardarComo": "analise" }
],
"rota": "/credit-analyses/{{analise.id}}",
"espera": "credit-analysis-timeline"
},
{
"nome": "Enviada para análise",
"setup": [{ "metodo": "POST", "rota": "/credit-analyses/{{analise.id}}/submit" }],
"rota": "/credit-analyses/{{analise.id}}",
"espera": "credit-analysis-timeline"
}
]
}
-
RN6. A primeira execução de um processo analisa antes de navegar. O modelo lê as rotas (
apps/web/src/App.tsx), as páginas envolvidas, a máquina de estados correspondente na API e osdata-testiddisponíveis, e escreve o mapa. Só então executa. Sem mapa, a skill não navega — declarar o caminho antes é o que torna a execução repetível e o resultado revisável. -
RN7. Com o mapa existindo, a execução não reanalisa: lê e roda.
--reanalisarrefaz a análise e mostra o diff contra o mapa atual, pedindo confirmação antes de gravar. Sobrescrever em silêncio apagaria ajuste feito à mão, que a RN6 espera que aconteça.
A execução¶
-
RN8. O dado de cada passo é criado pela API, antes de navegar — a mesma técnica dos specs e2e, pelo mesmo motivo: um print que depende do que houver no banco mostra coisa diferente a cada execução, e pode simplesmente não achar o estado que precisa. A unicidade que o banco exige vem do documento gerado (RN8c), não de um sufixo no nome, então rodar duas vezes não colide e os nomes continuam bonitos.
-
RN8a. O dado é uma simulação verossímil, não um preenchimento técnico. O print é para ser mostrado — a alguém do time, a um cliente, numa revisão de fluxo —, e
Inquilino Shot 1786211919068não sobrevive a isso. O que o mapa monta tem que passar por um caso real:
| Não vale | Vale | |
|---|---|---|
| Nome | Inquilino Shot 1786… | Marina Albuquerque Rocha |
| Documento | vazio, ou 11111111111 | CPF/CNPJ com dígito verificador válido |
| Imobiliária | Imob shot 1786… | Vitória Imóveis · CNPJ válido |
| Endereço | Rua V, 1 | Rua Doutor Rafael de Barros, 209 · Paraíso · São Paulo/SP · 04003-042 |
| Valores | 2500 / 0 / 0 | aluguel 3.200 · condomínio 680 · IPTU 190 |
| Renda | número redondo qualquer | compatível com a profissão e com o desfecho da jornada |
| Parecer | Screenshot spec 0025 | texto que um analista escreveria |
| Vigência | ausente | 12 meses a partir do primeiro dia do mês seguinte |
Campo vazio aparece na tela como —, e uma jornada cheia de travessões não mostra o produto: mostra o formulário mal preenchido. Todo campo que a tela exibe é preenchido, a menos que a ausência seja justamente o que o passo quer demonstrar.
-
RN8b. Os valores servem ao desfecho que o passo demonstra. A jornada da análise de crédito termina em aprovação, então a renda declarada tem que ficar dentro da política (≥ 3× o Pacote Locatício, spec 0025/RN7) — senão o print mostra "FORA" numa jornada que aprova, e o material fica incoerente. Um mapa que queira demonstrar o caso reprovado declara isso e escolhe a renda para caber nele. O mapa diz qual cenário encena; os números obedecem.
-
RN8c. Os dados vêm de um pool do projeto, não do núcleo da skill.
journeys/dados.js(lado do projeto, RN1) exporta umgerar()que devolve personas, imobiliárias, imóveis e valores coerentes — com CPF/CNPJ gerados com dígito verificador válido e CEP de logradouro existente. O mapa referencia por token ({{inquilino.nome}},{{imovel.cep}}); o núcleo só substitui. Assim a skill continua portável: outro projeto escreve o próprio pool, com os documentos do próprio país.
A unicidade que o banco exige — CNPJ de imobiliária — vem do documento gerado, não de um sufixo no nome. É o que permite nome bonito e execução repetível ao mesmo tempo.
-
RN8d. Os registros da skill são identificáveis no banco e invisíveis no print. Com dado verossímil, quem abrir o dev-remote não distingue mais o que é da skill do que é de alguém testando. A marca vai num campo que a tela do processo não exibe — um sufixo convencionado no e-mail de contato da imobiliária (
+journeys) —, o suficiente para varrer e limpar depois sem sujar nenhuma captura. -
RN9. O que for configuração compartilhada, a skill desfaz. Parametrização de documentos, ajuste de
TenantSettingse afins são do tenant, não da jornada — e o tenant é compartilhado com o e2e. Um passo que mexa nisso declara o desfazer, executado emfinallymesmo se a jornada falhar no meio. Esta regra existe porque já aconteceu: na spec 0025 um script de screenshot deixou um documento obrigatório parametrizado e derrubou três testes e2e que nada tinham a ver com a mudança. -
RN10. A autenticação é por injeção do token no
localStorage, não pela tela de login: é mais rápida e não quebra quando a tela de login muda. Uma jornada cujo objeto seja o login declara"auth": "pela-tela"e aí passa pelo formulário. -
RN10a. A captura estica o viewport até o conteúdo caber, em vez de usar
fullPage. Aplicação cujo layout rola por dentro — qualquer casca com barra fixa — não rola o documento, efullPage: truedevolve exatamente o viewport: a jornada mostraria só o topo de cada tela. Esticar a altura resolve sem seletor de projeto e mantém o cabeçalho na imagem. Teto configurável (alturaMaximaPrint), e a rolagem horizontal continua medida no viewport declarado — é a largura real do dispositivo que decide se há defeito. -
RN11. Cada passo é fotografado em todos os viewports do config — desktop e mobile por padrão. Em cada print a skill também afere
scrollWidth - clientWidth == 0e registra a falha no relatório sem interromper a jornada: encontrar rolagem horizontal é metade do valor da ferramenta, e abortar no primeiro defeito esconderia os seguintes. -
RN12. Erro de console (
console.error,pageerror) é coletado por passo e aparece no relatório. Mesmo tratamento: anota, não interrompe. -
RN13. O alvo é o dev-remote, e a skill avisa quando o código local está à frente da imagem (comparando o
HEADcom a data da imagem). Não sobe ambiente isolado — um ambiente montado à mão diverge no build, no service worker e no roteamento do Caddy, e o print deixaria de ser sobre o que vai para o ar. -
RN14. Cada jornada roda com um papel, declarado no mapa ou herdado do
defaultRole. Comparar o mesmo processo entre papéis (o que oAGENCY_USERvê e oANALYSTnão) é feito com dois mapas, não com um mapa multi-papel — cada jornada continua sendo uma sequência linear, que é o que a torna legível.
A entrega¶
-
RN15. A saída é uma página HTML navegável em
docs/journeys/index.html, com menu de processos à esquerda. Escolhido um processo, o corpo mostra os passos em ordem; cada passo traz nome, rota, o que o setup fez, e os prints dos dois viewports lado a lado. -
RN15a. O clique no print abre um visualizador com zoom, não uma aba nova. O print tem a página inteira (RN10a), então na miniatura nada é legível e o tamanho natural em aba nova não dá controle nenhum — o zoom é a única forma de ler um campo.
O visualizador abre ajustado à tela e oferece roda do mouse, arraste para mover, botões − / + / Ajustar / 100%, duplo clique alternando ajuste ↔ tamanho real, e teclado (Esc, +, −, 0, 1).
Fecha só pelo botão ou por Esc — clique nunca fecha. Há um × no canto e um Fechar na barra de controles. Não existe fechar-por-clique porque ele é incompatível com o arraste: setPointerCapture, que é o que mantém o pan inteiro quando o ponteiro sai da imagem, faz o browser redirecionar o click para o elemento que capturou — então um clique sobre a própria imagem chega indistinguível de um clique no fundo, e o visualizador fechava no meio da análise. Entre perder o arraste e perder o atalho de fechar, perde-se o atalho.
O arraste exige desligar o arraste nativo de imagem. <img> é arrastável por padrão: pressionar sobre o print inicia um drag-and-drop do browser, que dispara pointercancel e mata o pan depois de alguns pixels. São três desligamentos, e falta qualquer um deles para o defeito voltar: draggable="false" no elemento, -webkit-user-drag: none + user-select: none no CSS, e preventDefault() no pointerdown.
O zoom acontece sob o cursor. Com transform-origin: 0 0, um ponto p da imagem cai em s·p + t; manter parado o ponto q sob o cursor exige t' = q − (q − t)·(s'/s). Sem isso o zoom foge para o canto, e navegar numa captura de página inteira vira tentativa e erro. Continua sem CDN e sem dependência (RN16).
-
RN16. A página é autocontida e offline: CSS embutido, sem CDN, imagens como arquivos ao lado. Abre com um duplo-clique, sem servidor.
-
RN16a. No dev-remote ela também é servida em
/journeys, pelo Caddy da borda, via bind mount dedocs/journeys/— não entra em imagem nenhuma. Recapturar é rodar o skill: o Caddy passa a servir a versão nova sem rebuild e sem restart, que é o oposto do que vale parawebeapi.handle_pathremove o prefixo, senão toda imagem responderia 404.
A pasta é ignorada pelo git, então num clone novo o Docker cria o ponto de montagem vazio e /journeys responde 404 até a primeira captura — comportamento correto, e o motivo de a rota não depender de arquivo versionado.
Fica aberta, como o /mail. São capturas de um ambiente de desenvolvimento com dados fictícios, mas expõem a interface a quem souber a URL; o Caddyfile traz o basic_auth comentado para quando isso deixar de ser aceitável.
-
RN17. O cabeçalho de cada processo mostra quando foi capturado, contra que
baseUrl, em que commit (git rev-parse --short HEAD) e com que papel — um print sem essas quatro coisas não permite dizer se ainda vale. -
RN18. Os defeitos encontrados (RN11, RN12) aparecem no topo da página, com contagem e ligação para o passo. Uma execução limpa diz isso explicitamente, em vez de só não mostrar nada.
-
RN19. A skill não roda sozinha — nem em hook, nem em CI, nem por iniciativa do modelo dentro de outra tarefa. É invocada pelo usuário (
/journeys <processo>), porque monta dado no ambiente compartilhado e leva minutos. Sem argumento, lista os processos mapeados e os disponíveis para mapear.
5. Impacto por app¶
apps/api¶
Sem impacto. Nenhum endpoint, modelo ou migration. A skill consome a API pública com um token de usuário de e2e, como qualquer cliente.
apps/web¶
Sem impacto no código. A skill depende dos data-testid que o CLAUDE.md do web já torna obrigatórios — se um passo não encontra o testid de que precisa, isso é achado da skill (a tela não está instrumentada), não motivo para mudar a tela dentro desta spec.
apps/e2e¶
Sem impacto. A suíte não muda. A skill espelha convenções dela — usuários de fixtures/roles.ts, montar mundo pela API, alvo no dev-remote — mas não importa código: o config.json redeclara o que precisa, para a skill continuar portável (RN1).
Ferramental (o que realmente muda)¶
.claude/skills/journeys/SKILL.md— o procedimento: analisar, mapear, executar, relatar..claude/skills/journeys/scripts/capture.mjs— Node +playwright-core(já disponível emapps/e2e/node_modules), sem dependência nova. Lê um mapa, executa, grava PNGs e emite o JSON de resultado..claude/skills/journeys/scripts/render.mjs— o JSON de resultado viraindex.html.journeys/config.json— a configuração do Garantora.journeys/dados.js— o pool verossímil: personas com CPF válido, imobiliárias com CNPJ válido, imóveis com endereço e CEP reais, e valores coerentes entre si (RN8a–RN8c).journeys/analise-credito.json— o primeiro mapa (§9)..gitignore— acrescentadocs/journeys/.infra/dev-remote/—Caddyfile,Caddyfile.behind-edgeedocker-compose.yml: a rota/journeyse o bind mount da pasta de saída (RN16a).CLAUDE.mdda raiz — uma linha na seção de validação apontando a skill como o caminho para "ver a jornada", ao lado do script de screenshot pontual.
6. Contrato de API¶
Nenhum endpoint novo. A skill consome os existentes. O "contrato" que esta spec cria é o formato do mapa (RN5) e o do resultado que alimenta a página:
{
"processo": "Análise de crédito",
"capturadoEm": "2026-08-08T14:21:00Z",
"baseUrl": "https://dev.garantora.com.br/app",
"commit": "707416c",
"papel": "gestor",
"passos": [
{
"nome": "Enviada para análise",
"rota": "/credit-analyses/6f2a…",
"setup": ["POST /credit-analyses/6f2a…/submit"],
"prints": { "desktop": "analise-credito/02-desktop.png", "mobile": "analise-credito/02-mobile.png" },
"overflow": { "desktop": 0, "mobile": 0 },
"errosDeConsole": []
}
],
"defeitos": []
}
Erros previstos, todos relatados sem derrubar a execução: passo cujo espera não aparece no prazo (PASSO_NAO_CARREGOU), chamada de setup que falha (SETUP_FALHOU, e a jornada para ali porque os passos seguintes dependem dela), papel sem permissão para a rota (SEM_PERMISSAO).
7. Critérios de aceite¶
- [x] CA1.
/journeyssem argumento lista os processos com mapa e os disponíveis para mapear, sem navegar nem criar dado. - [x] CA2.
/journeys analise-creditonum repositório sem o mapa analisa primeiro, gravajourneys/analise-credito.jsone só então executa. - [x] CA3. Com o mapa existindo, a execução não reanalisa — o mapa não é reescrito.
- [ ] CA4.
/journeys analise-credito --reanalisarmostra o diff contra o mapa atual e só grava após confirmação. - [x] CA5. A execução cria o próprio dado: rodar duas vezes seguidas produz duas jornadas completas, sem colisão de registro.
- [x] CA6. Um passo que altere configuração de tenant a desfaz ao final — verificável rodando a suíte e2e depois da skill e obtendo o mesmo resultado de antes.
- [x] CA7. Cada passo tem print de desktop e de mobile, com as dimensões do config.
- [x] CA7a. Nenhum print da jornada exibe carimbo técnico — nome com número de
timestamp,lorem ipsum,teste,shotouspec. Verificável lendo os prints. - [x] CA7b. Os documentos gerados passam na validação de dígito verificador: o CPF do inquilino e o CNPJ da imobiliária são aceitos pela API e aparecem formatados na tela.
- [x] CA7c. Nenhum campo exibido pela tela do processo aparece como
—, salvo quando a ausência for o que o passo demonstra (RN8a). - [x] CA7d. A jornada da análise de crédito termina aprovada e o quadro de política mostra DENTRO em todos os passos em que aparece — os números servem ao desfecho (RN8b).
- [x] CA8. Uma tela com rolagem horizontal aparece como defeito no relatório, e a jornada continua até o último passo.
- [x] CA9. Erro de console num passo aparece no relatório atrelado àquele passo.
- [x] CA10.
docs/journeys/index.htmlabre por duplo-clique, sem servidor e sem rede, com todos os prints visíveis. - [x] CA19.
https://dev.garantora.com.br/journeysresponde com a página, e/journeyssem barra redireciona para/journeys/./appe/apiseguem intactos. - [x] CA20. Clicar num print abre o visualizador sem abrir aba nova; ele nasce ajustado à tela, amplia por roda, botão e teclado, move por arraste e fecha com
Esc(RN15a). - [x] CA22. Clicar na imagem, no fundo ou dar duplo clique não fecha o visualizador; ele fecha pelo
×, peloFecharda barra e porEsc. - [x] CA21. O arraste move a imagem 1:1 com o mouse — deslocar o ponteiro em (dx, dy) desloca a imagem exatamente em (dx, dy) —, não dispara
dragstartnempointercancel, não altera a escala, e um arraste que termina fora da imagem não fecha o visualizador. - [x] CA11. A página tem menu de processos; escolher um troca o corpo sem recarregar.
- [x] CA12. O cabeçalho de cada processo mostra data,
baseUrl, commit e papel. - [x] CA13. Uma execução sem defeitos diz isso explicitamente no topo.
- [x] CA14.
docs/journeys/está no.gitignoreegit statusfica limpo depois de uma execução (fora o mapa, quando for a primeira vez). - [x] CA15.
SKILL.mdescripts/não contêm nenhuma rota, papel,data-testid, URL ou nome próprio do Garantora — verificável porgrep. - [x] CA16. O processo "Análise de crédito" está mapeado fim a fim: rascunho, envio, início da análise, pendência de documentos, retomada, aprovação e contrato — com um caso verossímil do começo ao fim (RN8a–RN8c).
- [x] CA18. Os registros criados pela skill são localizáveis no banco pela marca da RN8d, e a marca não aparece em nenhum print.
- [x] CA17. A skill avisa quando o
HEADlocal é mais novo que a imagem do dev-remote, antes de capturar.
8. Plano de implementação¶
Não vale a ordem api → web → e2e desta vez: nenhum dos três é tocado. A ordem aqui é a da dependência — formato, executor, renderizador, mapa.
- [x]
apps/api— sem impacto - [x]
apps/web— sem impacto - [x]
apps/e2e— sem impacto - [x]
journeys/config.json— configuração do Garantora (RN4) - [x]
journeys/dados.js— pool verossímil, com geradores de CPF/CNPJ válidos (RN8a–RN8c) - [x]
.claude/skills/journeys/scripts/capture.mjs— executa um mapa, grava PNGs e o JSON de resultado (RN8–RN14) - [x]
.claude/skills/journeys/scripts/render.mjs— JSON →index.htmlcom menu (RN15–RN18) - [x]
.claude/skills/journeys/SKILL.md— o procedimento, incluindo a análise da primeira vez e o--reanalisar(RN6, RN7, RN19) - [x]
journeys/analise-credito.json— o primeiro mapa, derivado do código (CA16) - [x]
.gitignore—docs/journeys/ - [x]
CLAUDE.mdda raiz — a linha apontando a skill - [x] Execução real contra o dev-remote reconstruído, com a página aberta e conferida
- [x] Suíte e2e rodada depois da skill, para provar a RN9/CA6
9. Decisões em aberto¶
Nenhuma. A única que havia foi decidida:
- [x] Qual processo entra primeiro. Decidido pelo usuário em 2026-08-08: Análise de crédito (CA16) — entidade central do domínio, máquina de estados mais rica, e acabou de ser reorganizada pela spec 0025, então é onde a jornada visual paga mais rápido. Os candidatos seguintes, em ordem sugerida e fora do escopo desta spec: Onboarding da garantidora, Contrato e cobranças, Cadastro de inquilino e imóvel, Parametrização de documentos.
10. Registro¶
Implementada em 2026-08-08. A jornada da análise de crédito roda fim a fim contra o dev-remote: 7 passos, 14 prints, zero rolagem horizontal.
Achados da própria ferramenta¶
A skill existe para encontrar isto, e encontrou já na primeira execução. Nenhum deles é corrigido aqui — a spec declara apps/api e apps/web sem impacto, e mudar isso transformaria uma spec de ferramental numa spec de produto. Ficam registrados para virar /fix ou spec própria:
-
POST /decision/penddevolve 500 com motivo longo.Decision.decision_reasonéString(100)no banco e o schema não limita, então um motivo de 143 caracteres estoura comoStringDataRightTruncationError— HTTP 500, não 422. Vale parapend,rejecteapprove, que compartilham o campo. Correção provável:max_lengthnoAnalysisDecisionRequest.reason. -
A tela de detalhe registra um 404 no console em
DRAFTeSUBMITTED. É oGET /credit-analyses/{id}/decisionque a spec 0025 passou a chamar em todos os estados para alimentar o quadro de política. O 404 é resposta esperada — não há decisão nesses estados —, mas chega ao console como erro e polui qualquer monitoramento. São os 4 defeitos que a página relata. -
CNPJ aparece sem formatação em tela.
_validate_cnpj(app/schemas/common.py:34) recusa pontuação, então todo CNPJ existe cru no banco — e nenhuma tela o formata na exibição. O CPF não tem esse validador e por isso sai formatado. A incoerência entre os dois documentos fica visível lado a lado no painel da imobiliária. -
PENDING_DOCS → IN_ANALYSISé transição morta. Está emALLOWED_TRANSITIONS(credit_analysis_service.py:70), mas o único ponto que gravaIN_ANALYSISé a criação da decisão, que exigeSUBMITTED. Não há endpoint que a dirija. A retomada real do produto é anexar o documento e aprovar direto dePENDING_DOCS— que é como o mapa ficou, e funciona.
Divergências do planejado¶
-
RN10aé nova. O plano supunhafullPage: true. Medido no dev-remote, o<main>tem 2484px de conteúdo em 843px de altura e o documento não rola —fullPagedevolvia exatamente o viewport, e a jornada mostrava só o topo de cada tela. A captura passa a esticar a altura do viewport até o conteúdo caber. -
Três primitivas nasceram da necessidade, não do plano: seletor de lista no token (
{{tipos.items[name=X].id}}),tolerarErroearquivo(multipart). As três são genéricas — "garantir que existe" e "anexar um documento" são necessidades de qualquer jornada, não deste projeto. -
A etiqueta da RN8d mudou de campo. Ela estava no
emailda imobiliária, que é exibido no painel — apareceu no primeiro print, violando a própria regra. Foi paracontact_email, que nenhuma tela do processo mostra. -
CPF com máscara, CNPJ sem. Não é escolha estética: a API recusa CNPJ pontuado e aceita CPF de qualquer forma, e a tela exibe o que recebeu. Verossímil aqui é obedecer ao que cada campo aceita — ver o achado 3.
Verificação¶
- Duas execuções seguidas: 7 passos cada, sem colisão, mapa não reescrito.
- Estado compartilhado limpo depois de cada uma — inclusive depois da execução que falhou no meio (o 500 do achado 1), que é onde o
desfazerprova o que vale. index.htmlaberto porfile://: 7 passos, 14 imagens carregadas, metadados completos, zero erro de console na própria página.- Fronteira genérica conferida por
grep: nenhum termo do projeto emSKILL.mdouscripts/. -
Visualizador com zoom (RN15a, pedido do usuário depois da entrega): abre em 48% (ajuste),
+leva a 105%,100%a 100%, roda a 246%,Ajustarvolta a 48%,Escfecha. Zero abas novas e zero erro de console. Como a RN16a serve a pasta por bind mount, o modal entrou no ar sem rebuild e sem restart. -
O arraste saiu quebrado na primeira versão, e o teste não pegou. O
<img>mantinha o arraste nativo do browser, que cancela a sequência de ponteiro — no uso real o pan parava depois de alguns pixels. O teste passou mesmo assim porque só verificava "a transformação mudou", e porque evento sintético de automação não inicia drag-and-drop nativo. Corrigido nas três frentes (draggable, CSS,preventDefault) e o teste virou medida: o pan tem que ser 1:1 com o mouse. Aferido em três deslocamentos (−240,−160 · 300,220 · −90,410), todos exatos, semdragstartnempointercancel, com a escala intacta e sem o modal fechar por engano.
A lição vale além deste caso: asserção que só pergunta "mudou?" passa em qualquer implementação errada. A que pergunta "mudou quanto?" é a que pega. (Com tolerância: a comparação estrita reprovou um pan de -240.0000000000001 que estava correto.)
-
O fechar-por-clique caiu, e a causa é a mesma captura de ponteiro. Ele filtrava por
e.target === palcosupondo distinguir fundo de imagem — mas comsetPointerCaptureativo o browser redireciona oclickpara o elemento capturante, então todo clique chegava como clique no fundo e fechava o visualizador. Fechar passou a ser só×,FechareEsc(RN15a). As duas coisas não coexistem sem abrir mão do arraste. -
Crase em comentário dentro de template literal quebrou o
render.mjstrês vezes. O HTML inteiro é uma template string, e escrever`assim`num comentário fecha a string no meio. Todos os comentários dentro do template passaram a ser texto puro. -
Publicação no dev-remote (RN16a, pedido do usuário depois da entrega):
/journeys→ 302 para/journeys/;/journeys/→ 200; imagem → 200;/app/e/api/docsinalterados. Carregada pela URL pública: 7 passos, 14 imagens, nenhuma requisição com erro, nenhum erro de console. OCaddyfilee oCaddyfile.behind-edgereceberam a rota — o dev-remote roda o segundo (modo co-hospedado), e deixar os dois divergirem é como o modo standalone quebraria sem ninguém perceber. -
Suíte e2e depois da skill: 29 de 29 nos quatro specs de análise de crédito — os mesmos que a poluição da spec 0025 derrubou. É a CA6 fechada onde ela importa.
Não verificado¶
- CA4 (
--reanalisarmostra o diff e pede confirmação) é procedimento do modelo, descrito noSKILL.md, não comportamento de script — não há o que executar para provar. Fica por conferir no primeiro uso real do flag.