尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
Aplicativo de finanças pessoais e familiares, pensado primeiro para o celular. A Íris mostra para onde o dinheiro vai: você anota entradas e gastos em segundos e enxerga o mês inteiro, sem planilhas, sem jargão e sem julgamento. • Next.js 16 (App Router) + React 19 + TypeScript • Tailwind CSS 4 • Supabase (Postgres + Auth), rodando localmente via Docker durante o desenvolvimento • Zod para validação • Vitest (regras de negócio, componentes, banco) e Playwright (ponta a ponta)查看完整 README ↗
Aplicativo de finanças pessoais e familiares, pensado primeiro para o celular. A Íris mostra para onde o dinheiro vai: você anota entradas e gastos em segundos e enxerga o mês inteiro, sem planilhas, sem jargão e sem julgamento.
O ambiente local usa uma instância do Supabase rodando em Docker — nenhum dado de desenvolvimento fica em um projeto na nuvem.
npm installnpx supabase start — sobe Postgres, Auth e demais serviços localmente.npx supabase status -o env mostra as credenciais locais. Copie para .env.local (baseado em .env.example) mapeando:
API_URL → NEXT_PUBLIC_SUPABASE_URLPUBLISHABLE_KEY (ou ANON_KEY) → NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEYSECRET_KEY (ou SERVICE_ROLE_KEY) → SUPABASE_SECRET_KEYNEXT_PUBLIC_SITE_URL=http://localhost:3000npx supabase db reset — aplica as migrações e a seed no banco local.npm run dev → http://localhost:3000SUPABASE_SECRET_KEY só é usada pelos testes de banco e end-to-end (para criar/apagar usuários de teste) e pelo script local scripts/configurar-tarefa.mjs. Ela nunca é usada no código do app, nunca vai para a Netlify e nunca recebe o prefixo NEXT_PUBLIC_. As variáveis do Plano 8 (push, e-mail, tarefa) são opcionais: sem elas, a parte correspondente fica desligada e o app funciona igual (veja "PWA e notificações (local)").
O login por e-mail funciona sem nenhuma configuração extra. Já "Continuar com Google" só funciona depois de configurar [auth.external.google] em supabase/config.toml com as credenciais OAuth do Google (client_id e secret) via variáveis de ambiente — nunca comitadas.
Tudo funciona sem conta em nenhum serviço. Cada parte liga com variáveis em .env.local (lista comentada em .env.example); sem elas, a parte fica desligada.
npm run dev ele só registra com NEXT_PUBLIC_REGISTER_SW=1 (o Playwright já liga sozinho). Ele não guarda nenhuma tela, dado ou arquivo do app: só a página "Sem conexão" e um ícone. Para ver o aviso à mão: npm run build && npm start e desligue a rede no navegador.npx web-push generate-vapid-keys. Copie a chave pública para NEXT_PUBLIC_VAPID_PUBLIC_KEY (a única com esse prefixo, porque vai para o navegador), a privada para VAPID_PRIVATE_KEY e defina VAPID_SUBJECT=mailto:seu-email. Em Configurações → Lembretes, "Ativar lembretes". O endereço de push de cada aparelho é dado pessoal: nada o lê pela API, e o app só envia para serviços de push conhecidos (Google, Mozilla, Apple e Microsoft).SMTP_HOST=127.0.0.1, SMTP_PORT=54325, MAIL_FROM=Íris <[email protected]>, sem usuário e senha. Os e-mails aparecem na caixa local, em http://127.0.0.1:54324, e não saem do computador. A recuperação de senha também passa por essa caixa (o modelo está em supabase/templates/recovery.html; é o Supabase que envia)./api/jobs/notificacoes) que o banco chama; ela só abre com um código de disparo.
node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))". Coloque o valor em JOB_SECRET no .env.local.npm run job:configurar registra no banco local só o resumo (SHA-256) do segredo e mostra dois comandos do Vault (endereço da rota e código de disparo). Cole-os no SQL Editor, em http://127.0.0.1:54323. O script nunca mostra nem grava o segredo.npx supabase db reset (o Vault e os resumos são esvaziados).npm run job:notificacoes -- manha (enfileira os lembretes da manhã e entrega), , (gera as contas do dia) ou sem argumento (só entrega o que está na fila). Esse atalho usa a chave publicável e o segredo; é só para a máquina local.Segurança da tarefa, em poucas palavras: o banco guarda só o SHA-256 do segredo; quem chama a rota (pg_net) leva um código derivado do segredo, do qual não se volta ao segredo e que só pede à rota para entregar um lote; a rota usa a chave publicável e o segredo do próprio ambiente, nunca a chave de serviço. O código de disparo continua valendo até o segredo ser trocado. Não ligue log_statement = 'all' nem pgaudit para os papéis da API (os parâmetros das chamadas iriam para o registro).
npm test — regras de dinheiro, datas, formulários e componentes (Vitest); no Plano 8 também tipos de aviso, textos e destinos, envio de push e de e-mail (simulados), rota da tarefa, service worker, manifest e página "Sem conexão"; no Plano 9 também as células do CSV, os textos jurídicos, as ações de excluir o cadastro e trocar o e-mail e a rota de download; no Plano 10 também os textos da landing, a regra da seção Confiança, robots e sitemap, as colunas do desktop, o pular para o conteúdo e a conferência de textos (scripts/conferir-textos/core.test.mjs)npm run test:db — privacidade e regras no banco local (usa SUPABASE_SECRET_KEY; requer npx supabase start); o Plano 8 acrescenta preferências, inscrições e fila de avisos, tarefa diária das contas, convite por e-mail e o segredo da tarefa; o Plano 9 acrescenta plano9 (entrada recente, exclusão completa tabela por tabela, isolamento, família e limites de convite que sobrevivem à exclusão). Durante a execução o agendador local (pg_cron) fica pausado; se uma execução for interrompida antes do fim, rode select public.job_set_paused(false); no SQL Editornpm run test:e2e — fluxo completo no navegador, celular e desktop (Playwright; requer npx supabase start e .env.local preenchidos, e sobe o npm run dev automaticamente, já com NEXT_PUBLIC_REGISTER_SW=1; se um npm run dev já estiver rodando, reinicie-o). Os testes de push, da tarefa e do convite por e-mail do Plano 8 rodam quando NEXT_PUBLIC_VAPID_PUBLIC_KEY, JOB_SECRET e SMTP_HOST estão em .env.local; sem eles ficam marcados como pulados. Os testes da tarefa e do convite por e-mail só rodam contra o Supabase local e a caixa de e-mail local (NEXT_PUBLIC_SUPABASE_URL e SMTP_HOST em localhost ou 127.0.0.1): com um banco hospedado ou um servidor de e-mail de verdade em .env.local eles ficam pulados, para nenhum aviso ou e-mail de teste chegar a pessoas de verdade. A caixa de e-mail é lida em http://127.0.0.1:54324 (mude com E2E_MAILBOX_URL). O Plano 9 acrescenta plano9.spec.ts: páginas públicas de Termos e Privacidade, download dos dados, exclusão do cadastro, sessão antiga e troca de e-mail; o Plano 10 acrescenta plano10.spec.ts (landing, SEO, / para quem entrou, colunas a 1440 e 800 px, 375 px) e acessibilidade.spec.ts; a troca de e-mail só roda com o banco local (NEXT_PUBLIC_SUPABASE_URL em localhost ou 127.0.0.1) e o teste da sessão antiga também precisa do no contêiner do banco localnpx playwright test tests/e2e/acessibilidade.spec.ts roda o axe (WCAG 2.0, 2.1 e 2.2, níveis A e AA) nas páginas públicas e nas principais telas do app, no celular e no desktop, e confere que nada rola na horizontal. As telas do app criam um cadastro de teste, então precisam do Docker e do .env.local. Sem Docker: npx playwright test --grep @publico roda só os testes públicos (landing, Entrar, Criar cadastro, Termos e Privacidade, SEO, pular para o conteúdo e 375 px); os que precisam de banco ficam de fora ou pulados. A execução completa é npx supabase db reset && npm run test:e2e. Uma violação aparece com a regra, o impacto e o elemento; a correção é sempre na tela, nunca desligando a regra.
npm run textos compara todo texto visível do app (src/, public/sw.js, public/sem-conexao.html, supabase/templates/) com a cópia da copy e com as listas de textos (docs/etapa-2-requisitos.md, docs/etapa-5-ui.md, docs/decisoes-para-revisao.md e a seção "Textos novos" de cada plano). Termina com erro e lista arquivo:linha "texto" quando um texto não está em nenhuma delas; no fim mostra quantos textos conferiu, quantos casaram com a copy, com as listas ou com o ignorar.json, e quantos ficaram de fora. Código 0: tudo coberto; 1: há textos fora das listas; 2: falta a cópia da copy.
docs/copy/documento-base.md, mantendo a primeira linha com a revisão e a data. A pasta docs/copy/ está no .gitignore: o arquivo nunca vai para o git, e os testes usam uma cópia fictícia. Quando a copy mudar, exporte de novo.docs/decisoes-para-revisao.md (para a sua aprovação); se não aparece para ninguém (identificador, formato, nome técnico), coloque em scripts/conferir-textos/ignorar.json com o motivo. Uma entrada sem "motivo", ou que já não casa com nada, também faz o comando falhar.A publicação só acontece quando a pessoa responsável pelo projeto decidir — não faz parte do fluxo automático de desenvolvimento.
npx supabase link para associar o repositório a esse projeto.npx supabase db push para aplicar as migrações no banco de produção.netlify.toml para build e publicação).NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY e NEXT_PUBLIC_SITE_URL (com a URL pública do site). SUPABASE_SECRET_KEY não é necessária em produção — ela só é usada pelos testes locais de banco e end-to-end.NEXT_PUBLIC_SITE_URL de produção.Na Vercel (hospedagem atual): as variáveis são as mesmas do passo 5. O servidor do site precisa rodar na mesma região do banco: cada tela faz várias consultas em sequência, e cada uma atravessa a distância entre os dois. O vercel.json fixa a região em gru1 (São Paulo), a mesma do projeto Supabase atual (sa-east-1), perto de quem usa o app. Se o banco mudar de região, troque também a região no vercel.json (Oregon/us-west-2 é pdx1; Virgínia/us-east-1 é iad1).
Nada disto é necessário para desenvolver e testar localmente. Reúne o que ficou para você nos Planos 8, 9 e 10: contas e chaves que só você pode criar (itens 1 a 11, todas com plano gratuito), decisões de custo e de hospedagem (12, 13, 17), configurações do Supabase hospedado (5, 8, 14, 15), a revisão jurídica (16), a primeira execução com Docker (18) e as aprovações (11, 19 e 22), a liberação da landing (20) e o endereço dela (21). A SUPABASE_SECRET_KEY não entra nesta lista: ela não vai para a Netlify, nunca.
SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASS e MAIL_FROM na Netlify e as mesmas credenciais no Supabase, em Authentication → SMTP (é o que envia a recuperação de senha). Sem isso, convite e resumo por e-mail ficam desligados (o convite por link continua) e a recuperação de senha tem limite baixo de envios.supabase/templates/recovery.html e o assunto "Crie uma nova senha na Íris". O arquivo do repositório só vale no ambiente local.npx web-push generate-vapid-keys (outro par, diferente do local) para NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY e VAPID_SUBJECT na Netlify. Trocar as chaves depois desliga os lembretes de quem já ativou (a pessoa ativa de novo).JOB_SECRET (comando acima) e coloque na Netlify. Depois, uma vez, rode npm run job:configurar -- producao https://SEU-DOMINIO na sua máquina (com o mesmo JOB_SECRET no .env.local) e cole os três comandos que ele mostra no SQL Editor do Supabase hospedado. Nenhum deles contém o segredo.pg_cron, pg_net e o Vault antes de npx supabase db push. O Postgres do projeto precisa ser 17 ou mais novo (o local usa 17). O papel anon tem limite curto de tempo por comando: a produção depende do pg_cron (que chama as funções direto), não do atalho job_trigger do script local.BUDGET_MS em src/features/notificacoes/deliver.ts) e que os trechos que rodam depois da resposta (after(): avisos de planejado e de meta) de fato rodam lá. Se não rodarem, é preciso entregá-los por waitUntil.fcm.googleapis.com, *.push.services.mozilla.com, *.notify.windows.com, *.push.apple.com) é rígida e, se um navegador usar outro endereço, o lembrete não é salvo. Confira com um endereço real de cada navegador (o Chrome pode usar um endereço diferente de ). A lista está em e, igual, na migração.| Documento | Conteúdo |
|---|---|
docs/etapa-2-requisitos.md | Regras de negócio e requisitos (aprovados) |
docs/etapa-3-arquitetura.md | Modelo de dados, privacidade, páginas e fluxos (aprovada) |
docs/etapa-5-ui.md | Identidade visual e tokens (aprovada) |
docs/etapa-7-roteiro.md | Roteiro de desenvolvimento em 10 planos |
docs/superpowers/plans/ | Planos de implementação detalhados |
-- noite-- ocorrenciasnpm run icons gera os PNG a partir do logo provisório.docker execnpm run typecheck — checagem de tipos (TypeScript)npm run lint — checagem de estilo e boas práticas (ESLint)npm run textos — conferência de textos (abaixo)fcm.googleapis.comsrc/features/notificacoes/endpoint.tsnext mais longo (use o curinga …/**).scripts/gerar-icones.mjs e rodar npm run icons. Hoje os ícones usam o logo provisório.docs/decisoes-para-revisao.md).NEXT_PUBLIC_SITE_URL, o "Site URL" e os endereços de retorno em Supabase → Authentication → URL Configuration, e o retorno do login com o Google.NEXT_PUBLIC_SITE_URL de produção. O link do e-mail de troca usa {{ .SiteURL }}.supabase/templates/email_change.html, com o assunto "Confirme a troca de e-mail na Íris". O link precisa ser exatamente {{ .SiteURL }}/confirmar-email?token_hash={{ .TokenHash }}. Se decidir ligar "Confirm email" no cadastro, colar também supabase/templates/confirmation.html em "Confirm signup" (assunto "Confirme seu e-mail na Íris") e testar antes o caminho do convite da família (pendência em docs/progresso.md).src/features/legal/content.ts são um rascunho fiel ao que o app faz. Antes de lançar: (a) preencher em src/features/legal/controller.ts quem é o responsável (você ou a sua empresa), o e-mail de contato para assuntos de dados (que também é o contato do encarregado), o nome do provedor de e-mail e a data da revisão — enquanto um deles estiver vazio, as páginas mostram "Rascunho em revisão", marcam o que falta com "[a definir antes do lançamento]" e não são indexadas; nunca invente nomes ou números ali; (b) decidir os pontos marcados em docs/decisoes-para-revisao.md (Plano 9, "Para a revisão jurídica"): base legal, servidores da Netlify fora do Brasil, idade mínima, limite de responsabilidade, aviso em caso de incidente, prazos das cópias de segurança, lei aplicável e foro, gratuidade; (c) confirmar se os e-mails de confirmação e de nova senha (enviados pelo Supabase Auth) usam o mesmo provedor de e-mail do item 1 — a política diz que isso ainda está a confirmar.npx supabase db reset && npm run test:db && npm run test:e2e. Esperado do Plano 9: banco plano9 com 19 testes; ponta a ponta plano9.spec.ts com 6 testes (12 entradas em --list); os testes dos Planos 7 e 8 continuam passando (em especial plano7-familia, plano7-saida e plano8-convite, que passam pelas mudanças do Plano 9). Conferências que nenhum teste automático faz de ponta a ponta:
20261003000001_seus_dados.sql: se parar no primeiro bloco, o papel que aplica as migrações não pode apagar em auth.users (ou ler auth.sessions, ou tem a segurança por linha ligada nas tabelas de auth). Nesse caso não publique e não use a chave de serviço no app: fale comigo. No banco hospedado, npx supabase db push para nesse ponto com uma mensagem clara.plano9.spec.ts envelhece a sessão pelo psql do contêiner do banco local (supabase_db_Planilha_financeira; mude com E2E_DB_CONTAINER) e fica marcado como pulado se o contêiner não puder ser alcançado. Sem ele, à mão: update auth.sessions set created_at = now() - interval '16 minutes' where user_id = 'ID';, depois a tela Excluir meu cadastro deve pedir para sair e entrar de novo, e delete_my_account chamada com o token dessa pessoa deve recusar com "Entrada recente necessária." e deixar o cadastro intacto.plano9.spec.ts baixa o arquivo com uma sessão de verdade. Confira também num deploy de prévia da Netlify, com um cadastro grande, e o que acontece se a conexão cair no meio do envio (o download deve falhar, não terminar "completo"). Confira ainda, no mesmo deploy, que os trechos after() rodam (item 6).auth.jwt() ->> 'session_id' vem preenchido; verifyOtp({ type: 'email_change', token_hash }) só devolve sessão na segunda confirmação; cada endereço recebe um {{ .TokenHash }} diferente.authenticated tem limite de tempo por comando, 8 segundos no Supabase hospedado; o risco de estourar está no tamanho do registro de acessos de autenticação, auth.audit_log_entries, varrido sem índice; se estourar, a exclusão volta atrás inteira).select * from public.account_leftovers('ID-DO-CADASTRO', 'e-mail') deve voltar vazio. Compare também select tablename from pg_tables where schemaname = 'auth' com o que o papel consegue ler: uma tabela sem acesso não é conferida.docs/decisoes-para-revisao.md, seção "Plano 9").DATA_RIGHTS_RELEASED em src/features/landing/release.ts, depois dos itens 16, 17 e 18 (e com os campos de src/features/legal/controller.ts preenchidos — sem eles o item não aparece, mesmo ligado). Antes de ligar, aprove a frase nova "Você pode baixar o que registrou ou excluir seu cadastro quando quiser." (decisão 160).NEXT_PUBLIC_SITE_URL define o endereço canônico, o sitemap.xml e a imagem de compartilhamento. Termos e Privacidade só entram no sitemap.xml quando deixam de ser rascunho (item 16). O logo definitivo (item 10) também troca a imagem de compartilhamento (npm run icons).docs/decisoes-para-revisao.md, seção "Plano 10"), e a landing e os layouts de desktop vistos por você no celular e no computador (a landing não tinha protótipo; confira também as colunas a exatamente 1024 px). Rode npm run textos com a sua cópia da copy (acima) e resolva o que ele listar.src/features/cartoes/brand-mark.tsx só para identificar o cartão da própria pessoa. Antes do lançamento, confira o manual de marca de cada bandeira (cores, área de respiro, uso permitido) ou troque pelos arquivos oficiais. Aprove também os textos novos da bandeira (docs/decisoes-para-revisao.md, "Bandeira do cartão").src/domain/importacao/dicionario.ts) e a leitura de cada formato com o que aparecer. Faça a revisão jurídica da frase nova da Política de privacidade ("Se você importar o extrato do seu banco, o arquivo é lido no seu próprio aparelho e não é enviado nem guardado pela Íris…") e aprove os textos de "Importar extrato" (docs/decisoes-para-revisao.md, seção "Importar extrato (2026-10-10)"). Rode npm run textos com a sua cópia da copy.