Imported from lucasAlmeidaSilveira/farol (
AGENTS.md). Install upstream withnpx skills add lucasAlmeidaSilveira/farol. Copyright stays with the author.
Farol — instruções para agentes
App de finanças pessoais que responde uma pergunta: quanto eu posso gastar até o
fim do mês? O README.md é a fonte de verdade sobre problema, stack e
arquitetura — leia antes de propor qualquer mudança estrutural. Este arquivo tem
só o que não dá para inferir lendo o código: decisões fechadas, armadilhas
que falham em silêncio e o fluxo de trabalho.
Carregado em toda sessão. Se algo aqui já está óbvio no código, o lugar é o código, não aqui.
Comandos
| Comando | Quando | Detalhe que importa |
|---|---|---|
pnpm dev |
Sempre | Roda contra o farol-app-dev real, na nuvem. Não há emulador no dia a dia. |
pnpm typecheck |
Antes de qualquer entrega | Roda next typegen antes do tsc. Não troque por tsc --noEmit direto: LayoutProps/PageProps são gerados em .next/types e um clone limpo reprova código correto sem eles. |
pnpm lint |
Antes de qualquer entrega | Tem --fix. No CI é pnpm exec eslint, sem fix — lá o lint reprova, não conserta. |
pnpm test |
Mexeu em domain/ ou engine/ |
Vitest em ambiente node, sem jsdom e sem mock de Firebase. |
pnpm test:coverage |
Mexeu em domain/ ou engine/ |
100% de linhas, statements e functions. Abaixo disso, reprova. |
pnpm test:rules |
Mexeu em firestore.rules |
Sobe e derruba um emulador efêmero sozinho. Exige JDK 21+ — o script já prefixa o openjdk@21 do Homebrew no PATH. Não mexa no java global, outros projetos dependem do 17. |
pnpm build |
Antes de abrir PR | Gera o service worker junto. |
pnpm palette |
Mexeu em cor | Verifica o contraste de todos os pares. |
pnpm palette:write |
Mexeu em cor | Regenera os tokens no globals.css. Edite scripts/palette-source.mjs, nunca o CSS. |
O gate antes de abrir PR é pnpm typecheck && pnpm lint && pnpm test && pnpm build
— mais pnpm test:rules se as rules mudaram. Use /verificar para rodar tudo.
Não faça
Cada item já custou tempo uma vez.
- Não sugira emulador para o dia a dia. Decisão de 2026-08-16: o
desenvolvimento roda contra o
farol-app-devna nuvem, comNEXT_PUBLIC_USE_EMULATORS=false. A única exceção épnpm test:rules, porque@firebase/rules-unit-testingnão roda contra projeto de verdade. - Não rode
shadcn initnemshadcn add. Oinitreescreve oglobals.css, que tem uma paleta com contraste calculado par a par. Os primitivos emsrc/components/ui/são escritos à mão seguindo a forma do shadcn — copie o código do componente oficial e adapte aos tokens locais. - Não use
competencepara competência. É falso amigo. O termo éPeriod. - Não adicione
@rocketseat/eslint-config. Traz ESLint 8 e@typescript-eslintv6, que colidem com oeslint-config-next16. O estilo da casa vive no Prettier +simple-import-sort. - Não troque
@serwist/turbopackpor@serwist/next. O Turbopack é o bundler padrão do Next 16 e o plugin de webpack do@serwist/nextnão roda — falharia em silêncio, sem gerar service worker. - Não use
NEXT_PUBLIC_em nada que seja segredo. O Next inlina o valor no bundle de qualquer arquivo que referencie a variável. O MVP não tem segredo de servidor: a autorização inteira vive nas Security Rules. - Não importe
motiondireto — usem. OLazyMotionroda em modostrict, então o pacote completo reprova em desenvolvimento. É o que segura o bundle:m+domAnimationcusta ~17kb; omotioninteiro custa o dobro. - Não anime laço infinito com JavaScript. Feixe, halo e varredura vivem em
@utilitynoglobals.cssde propósito: laço infinito em JS disputa quadro com a rolagem sem entregar nada. A regra é curta — o que roda para sempre é CSS, o que reage à pessoa é Motion. - Não remova o
<noscript>do layout raiz. O Motion escreveopacity: 0no HTML do servidor; sem aquela regra, JavaScript quebrado é tela em branco. - Não anime o acordeão com o Motion. A altura de "aberto" só existe depois que o Radix mede o conteúdo, e ele entrega isso numa variável de CSS. Em JS, a mesma animação exigiria manter o conteúdo montado enquanto fecha — e colapsado na árvore de acessibilidade é lido como se estivesse aberto.
- Não dê
git push. Commit quando pedido; push é decisão do usuário. - Não faça deploy de rules junto com o merge.
pnpm rules:deploy:prodé manual e separado, de propósito, e exigepnpm test:rulesverde antes.
Invariantes de dinheiro
Quebrar qualquer uma destas é um bug de produção que aparece como centavo que
some. São a razão de domain/ e engine/ exigirem 100% de cobertura.
- Dinheiro é
Cents(inteiro), percentual éBasisPoints(inteiro). Nenhuma operação monetária tocafloatfora das funções auditadas desrc/domain/money.ts. Os branded types existem para impedir que reais entrem onde se espera centavos. - O rateio soma exatamente o total. Compromisso proporcional calcula com a
soma das alíquotas e só depois rateia por
allocateByWeights(maior resto). Aplicar 10% e 5% separadamente e somar pode divergir de 15% — e a diferença aparece na tela, ao lado de um detalhamento que não fecha. - A engine é pura e recebe
todayinjetado.todayIné a única fronteira do domínio com o relógio. Lernew Date()dentro da engine quebra o determinismo, e os testes junto. - Quitação (
settlement) NÃO desconta do disponível. O valor já foi reservado quando o mês foi calculado. Descontar de novo é contagem dupla — o erro mais fácil de cometer neste app, e há teste dedicado impedindo. availableToSpendCentspode ser negativo, e deve. Limitar em zero esconderia exatamente a informação que o app existe para dar.- Data é civil (
LocalDate,'YYYY-MM-DD'), nuncaDate. Um gasto às 23h de 31/08 em São Paulo é 02h de 01/09 em UTC — comDate, o mês fecha errado. - Mudou regra de cálculo? Incremente
ENGINE_VERSION. Meses fechados guardam a versão no snapshot e não mudam retroativamente.
Regra de negócio: Comunhão de Bens
O caso de uso que dá forma ao app. É uma contribuição religiosa mensal de
10% + 5% sobre toda a renda do mês — fixas e variáveis. Como a renda variável
entra ao longo do mês, o valor recalcula durante o mês: quitar com base só no
salário e receber um freela no dia 20 gera um alerta COMMITMENT_OUTSTANDING.
Está modelada como o preset covenant de ProportionalCommitment, com duas
parts (10% e 5%) e base incluindo fixas e variáveis. A engine não conhece o
nome — para ela é um proporcional como outro qualquer. Mantenha assim: preset é
configuração, não ramo de código.
Detalhes em .claude/skills/engine-financeira/SKILL.md.
Convenções
- Identificadores em inglês, UI em pt-BR. Decidido em 2026-08-15. Vale para tipos, funções, arquivos e chaves; textos visíveis e comentários em português.
- Mudou funcionalidade? A landing muda no mesmo commit.
/é a página pública esrc/content/landing.tsé todo o texto dela. Ela não quebra quando fica desatualizada — só passa a mentir para quem ainda não é usuário, que é o único público que ela tem. Como decidir se a mudança conta: skilllanding-farol. - Estilo: sem ponto e vírgula, aspas simples, trailing comma, 80 colunas.
Prettier +
simple-import-sortresolvem — não formate à mão. - Arquitetura
app → data → engine → domain, verificada pelo ESLint. Importar Firebase, React ou Next dentro dedomain//engine/falha o lint com a mensagem explicando o porquê. Se a regra te bloqueou, a resposta é inverter a dependência, não desligar a regra. - Movimento tem dono e régua. As durações da casa vivem em
src/components/motion/transitions.ts— 150ms reação, 250ms entrada, 300ms camada, 650ms só nas vitrines. Entrada, cascata e camadas usamReveal,StaggereAnimatePresence; a animação existe para EXPLICAR o que aconteceu, nunca para enfeitar. - Comentário explica por quê, nunca o quê. O código do projeto segue isso à risca; siga também, e no mesmo tom — direto, sem adjetivo de marketing.
- Commit no imperativo, explicando por quê. O o quê já está no diff.
mainé sempre publicável. Trabalho novo emfeat/…,fix/…,chore/….
Onde está o quê
src/domain/ Cents, BasisPoints, Period, LocalDate, schemas Zod. Só depende de zod.
src/engine/ computeMonth e amigos. Função pura estado → MonthSummary.
src/data/ ÚNICA camada que conhece Firebase. paths.ts tem todos os caminhos.
src/hooks/ Subscriptions (useFirestoreQuery) e mutations, por domínio.
src/components/ ui/ (primitivos) + motion/ (movimento) + por feature.
src/content/ Texto da landing. O que o Farol promete em público.
src/app/ Rotas do App Router. `/` é a landing; o app começa em `/hoje`.
scripts/ Paleta e ícones. palette-source.mjs é a fonte das cores.
tests/rules/ Security Rules contra o emulador.
Pontos de entrada quando estiver perdido: src/engine/compute.ts (o
orquestrador), src/domain/types.ts (o contrato entre as camadas),
src/data/paths.ts (o mapa do Firestore).
Duas rotas raiz, e a distinção importa: / é a landing pública, estática e
sem sessão, e /hoje é a primeira tela do app, atrás do SessionGate. Link
para "o início" dentro do app aponta para /hoje, nunca para /.
Armadilhas da camada de dados
invalidateQueriesdepois de mutação é anti-padrão aqui. OonSnapshotecoa a escrita de volta sozinho, localmente, antes de chegar no servidor. Invalidar dispara leitura extra — e leitura no Firestore é dinheiro.- Nunca escreva string de coleção à mão. Tudo passa por
src/data/paths.ts. - Nunca renderize
R$na mão. Só<MoneyValue>renderiza dinheiro: ele garantetabular-nums, o menos tipográfico (U+2212) e a leitura de tela. - Custo alto no Firestore é sintoma de bug, não de arquitetura. O suspeito é
sempre listener duplicado — veja o registry em
src/data/subscription.ts.
Skills e agentes deste repo
Carregue a skill antes de mexer na área correspondente:
| Skill | Para |
|---|---|
engine-financeira |
src/domain/, src/engine/ — dinheiro, ciclo, compromissos |
dados-firestore |
src/data/, src/hooks/, firestore.rules |
design-system-farol |
src/components/, globals.css, paleta |
landing-farol |
a página pública — e toda mudança de funcionalidade |
Subagentes: revisor-financeiro (auditar mudança em dinheiro), dev-ui-farol
(implementar tela ponta a ponta). Comandos: /verificar, /rules, /paleta.
This is NOT the Next.js you know
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in node_modules/next/dist/docs/ (resolved from this file's directory; in monorepos the next package may not be visible from the repo root) before writing any code. Heed deprecation notices.
This block is written and re-added by next dev — verify at node_modules/next/dist/server/lib/generate-agent-files.js. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.