6cc469109403194d21403dfcfd244562f88c1a10
8 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| 1e215ac3d2 |
docs: alinhar remotes e registry para Gitea
Origin e deploy passam a documentar git.hellomanoel.com; GitHub/GHCR ficam como legado/backup. Co-authored-by: Cursor <cursoragent@cursor.com> |
|||
| 2e43fdeb04 |
chore: usar America/Sao_Paulo como timezone da aplicação (#42)
A cidade de atuação é São Paulo, garantida por teste em quatro lugares e exigida por openspec/specs/site-settings/spec.md. O identificador de timezone, porém, era America/Fortaleza. O identificador passa a acompanhar o negócio. A mudança não altera comportamento. America/Sao_Paulo e America/Fortaleza são UTC-3 o ano inteiro desde que o horário de verão brasileiro foi extinto — verificado para janeiro, março e dezembro de 2026, idênticos ao segundo. Nada renderizado muda, o relógio congelado dos testes visuais usa offset absoluto (-03:00) e os baselines seguem válidos. O motivo de mexer é outro: a divergência entre o timezone e a cidade custou tempo real. Uma sessão anterior a interpretou como drift e "corrigiu" a SPEC no sentido errado, mudando o documento normativo para Fortaleza em vez de olhar o que o negócio é. Com os dois valores dizendo São Paulo, não há mais o que interpretar. Escopo: config/app.php, .env.example, os dois pontos do ci.yml, SPEC.md (§0, §13.5, §15.4), README.md, docs/deployment/dokploy.md, CLAUDE.md, openspec/config.yaml, openspec/specs/visual-regression/spec.md e o withTimezone do VisualRegressionTest. Intocados de propósito: as asserções que garantem que Fortaleza não aparece como cidade de operação, em PublicPagesTest, SiteSettingsTest, ContentSeederProductionGatingTest e openspec/specs/site-settings. Essas tratam de cidade, não de fuso, e continuam corretas. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code Co-authored-by: manoel.neto <manoel.neto@creditas.com> |
|||
| 7e68c0e37f |
chore: fechar portões de qualidade da Fase 0 (MAN-120) (#39)
* feat: gates de qualidade da Fase 0 (auth, npm audit, cobertura, compose local) Implementa os itens pendentes de SPEC.md §18 mapeados pela mudança OpenSpec complete-foundation-parity (grupos 1, 2 e 3 de tasks.md): - MustVerifyEmail em User, com canAccessPanel exigindo is_active E hasVerifiedEmail(). Seeds de admin/assistant local passam a marcar email_verified_at. Verificação de e-mail permanece administrada apenas pelo admin (seed/edição) — não há rota de auto-verificação registrada (->emailVerification() não foi chamado) — decisão deliberada, já que usuários são criados pelo admin, não se autocadastram. - Reset de senha seguro: página customizada App\Filament\Pages\Auth\RequestPasswordReset substitui a página padrão do Filament, que vaza a existência de contas via notificação de erro distinguível em dois casos — Password::INVALID_USER (e-mail inexistente) e Password::RESET_THROTTLED (só ocorre para usuário existente, quando um token foi criado há pouco). Ambos os casos agora respondem com a mesma notificação de sucesso. - declare(strict_types=1) adicionado aos 15 arquivos de app/ que ainda não tinham: AdminPanelProvider, Controller e 13 páginas de Filament Resources. - npm audit adicionado ao script `quality` do composer e ao job `static` do CI, com --audit-level=high (documentado inline no workflow). O gate é hoje vazio na prática — package.json não tem `dependencies` de runtime — mas protege contra regressões futuras. - Gate de cobertura Domain/Application >= 80% no job `unit` do CI. Escopo via phpunit.coverage.xml dedicado (o phpunit.xml global continua cobrindo todo app/ para os demais usos). O job `unit` ganhou serviço de postgres e passou a rodar Unit+Architecture+Feature juntas, porque as classes de App\Application\Queries\Marketing só são exercitadas por testes Feature hoje — um gate baseado só em Unit ficaria bem abaixo de 80%. Medido localmente com pcov: 98,1% (nenhum teste novo foi necessário para atingir o limiar). - Serviço `app` (FrankenPHP) no docker-compose.yml para paridade local com staging/produção, dependente de postgres saudável, com DB_HOST/DB_PORT sobrescritos para resolver o serviço postgres pelo nome (o padrão 127.0.0.1 do .env só funciona para processos no host). Validado com um smoke isolado (projeto/portas dedicados via overlay `!override`, sem tocar o container amare-postgres compartilhado por outra worktree): depends_on/healthcheck funcionou, `php artisan migrate` rodou dentro do container via DB_HOST=postgres, e /up respondeu 200. README atualizado (PHP 8.4 canônico, composer.json mantém ^8.3 por design). Fora do escopo: deploy hello-world em staging (Dokploy com falha, item de infra não relacionado a este change) — L2353 e o critério de saída da Fase 0 continuam sem marcar. Também ficam pendentes, por dependerem de uma execução real de CI/PR (task 3.4, 6.1, 6.2 do OpenSpec): prova de que o CI falha com quebra intencional de audit/cobertura, e evidência de deploy/ rollback em staging. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * fix: fecha lockout de admin-criado e documenta pcov/compose no auth de usuários Correção de acompanhamento ao commit anterior (gates de qualidade da Fase 0), achada por revisão adicional após o primeiro commit: - CreateUser::handleRecordCreation agora define email_verified_at ao criar um usuário pelo painel. Sem isso, UserForm não expõe esse campo (nem está no #[Fillable] de User — de propósito, para nunca virar mass-assignable via formulário), então todo usuário criado pelo admin nascia com email_verified_at nulo e, com canAccessPanel() agora exigindo e-mail verificado, ficava trancado para sempre — sem rota de auto-verificação e sem recuperação via reset de senha (o callback do Filament pula o envio para quem falha canAccessPanel(), mas ainda reporta sucesso). Usa forceFill() (não atribuição direta, que o PHPStan rejeita pelo tipo Carbon/string do cast) para contornar o guard de mass assignment só nesse ponto. Regressão coberta por teste que cria um usuário via Livewire::test(CreateUser::class) — não pela factory, que não passa pelo mesmo caminho — e confirma login em seguida; teste falha sem a correção (verificado manualmente revertendo e rodando de novo). - Confirmado que DatabaseSeeder::run() (commit anterior) já funciona sem ajuste: User::query()->updateOrCreate() pareceria sofrer o mesmo guard de #[Fillable], mas `php artisan db:seed` executa dentro de Model::unguarded() (Illuminate\Database\Console\Seeds\SeedCommand), o que levanta o guard durante o seed inteiro. Adicionado teste de regressão que roda via $this->seed(DatabaseSeeder::class) (o mesmo caminho de `composer setup`) para travar esse comportamento — chamar a mesma lógica fora do comando db:seed reproduz o guard bloqueando o campo, confirmando que a dependência do unguard() é real, não coincidência. - README: nota de que `docker compose up -d postgres` agora exige o `.env` do passo 1, porque o serviço `app` referencia `.env` via `env_file` e o Compose valida o arquivo inteiro antes de subir qualquer serviço. - AGENTS.md: documentado `composer test:coverage` (usado pelo job `unit` do CI) e que `docker compose up -d` sem argumento também sobe o serviço `app` agora, não só o Postgres. Nota para quem for escrever o próximo teste de recurso Filament neste repo: Livewire::test(...)->fillForm([...]) é um no-op silencioso aqui — app()->runningUnitTests() retorna false no setup de testes deste projeto, então o guard de Filament\Schemas\Concerns\InteractsWithSchemas:: fillFormDataForTesting() nunca aplica o estado, e o teste falha depois com erros de validação "required" que parecem um bug no formulário, não no teste. Use ->set('data.campo', valor) (como os testes de Login já fazem) até isso ser investigado — fora do escopo deste change. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * fix(auth): verificar usuários existentes ao adotar MustVerifyEmail O `canAccessPanel` passou a exigir `hasVerifiedEmail()`, mas nenhuma migration preenchia `email_verified_at` para contas que já existem. Toda conta criada antes desta mudança tem o campo nulo e ficaria trancada fora do /admin no instante do deploy. E não haveria volta pelo aplicativo: nenhuma rota de verificação self-service é registrada, e o fluxo de reset de senha deliberadamente não notifica quem falha no `canAccessPanel` — ainda reportando sucesso. Recuperar exigiria shell no contêiner. Verificar essas contas é o padrão correto, não um atalho. Não existe cadastro público: toda conta existente foi criada por um admin, pelo painel ou pelo snippet de tinker do runbook de deploy. Esse ato é a verificação — exatamente o raciocínio que o CreateUser aplica às contas criadas de agora em diante. A verificação é datada pelo `created_at` da conta, não pelo momento em que a migration roda, para não inventar um histórico. O `down()` é um no-op documentado: anular as colunas recriaria justamente a falha que esta migration existe para evitar, e a divisão original entre nulos e não-nulos não está registrada em lugar nenhum. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code --------- Co-authored-by: manoel.neto <manoel.neto@creditas.com> |
|||
| 4001cd0e5d |
docs: adicionar runbook de atualização de conteúdo (MAN-106 AC4) (#33)
* docs: adicionar runbook de atualização de conteúdo (MAN-106 AC4) A dona do negócio precisa editar textos, fotos, casos de portfólio, depoimentos e dados de contato pelo painel admin sem depender de engenharia. Documenta as regras que mais confundem (publicação não é agendamento; home exige publicado + destaque, exceto depoimentos), os campos editáveis por tela, requisitos de imagem/alt text e o que não deve ser mexido sem apoio técnico. Também esclarece o status do depoimentos.md: é material bruto histórico que nunca foi lido por código (confirmado por busca no repo), então recebe uma nota de status no topo do arquivo em vez de ser movido ou apagado, já que ainda é citado por documentos técnicos antigos como origem do conteúdo. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: reforçar avisos sobre resumo institucional e publicação em Configurações Ajusta o runbook de atualização de conteúdo: deixa explícito que o resumo institucional pode ser salvo em branco sem erro (produzindo um parágrafo vazio silencioso na página Sobre) e que a tela Configurações não tem passo de publicação — toda alteração entra no ar assim que é salva. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: corrigir regra de imagem opcional para capa de caso e galeria A regra geral "sem imagem, texto alternativo pode ficar em branco" (seção Imagens) estava sendo lida como "a imagem em si é opcional" para todo campo do painel. Isso é falso para a capa de um caso de portfólio e para a imagem de um item de galeria: as colunas cover_image_path, cover_image_alt (portfolio_cases) e path, alt_text (portfolio_images) são NOT NULL no banco, mas o formulário do Filament nunca chama ->required() nesses uploads. Quem seguisse a doc e tentasse salvar sem imagem passaria pela validação do formulário e só então bateria num erro de banco não tratado, em vez da mensagem amigável de campo obrigatório usada para título, resumo etc. Confirmado em database/migrations/2026_07_28_230932_create_portfolio_cases_table.php, database/migrations/2026_07_28_230933_create_portfolio_images_table.php e app/Support/PublicImageUploadRules.php — os demais campos de imagem do painel (logo, foto de depoimento, imagem da página Sobre, imagem de SEO, capa de serviço) são de fato nullable no banco, então a ressalva fica restrita a portfólio. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: avisar que Excluir apaga registros para sempre, sem lixeira O runbook só ensinava publicar/despublicar (preencher ou apagar "Publicado em") como forma de mostrar/esconder conteúdo em Portfólio, Depoimentos e Serviços, e nunca mencionava que cada linha dessas tabelas também tem um botão "Excluir" (e ação de exclusão em massa) bem ao lado do de editar. Nenhum model usa SoftDeletes (confirmado em app/Models) e as tabelas de Portfólio, Depoimentos e Serviços usam DeleteAction/DeleteBulkAction (app/Filament/Resources/{PortfolioCases, Testimonials,Services}/Tables/*.php e ImagesRelationManager.php), ou seja, um clique em Excluir apaga o registro para sempre, sem possibilidade de recuperação — inclusive apagando em cascata a galeria inteira de um caso de portfólio (cascadeOnDelete em portfolio_images.portfolio_case_id). Quem seguisse só a instrução de "apagar o Publicado em" não tinha como saber que existia esse outro controle, irreversível, na mesma linha. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: explicar como converter foto HEIC do iPhone para o formato aceito A instrução para formato de imagem recusado dizia só "converta a imagem antes de enviar", sem dizer como. HEIC é o formato padrão de fotos do iPhone e está listado no próprio texto como um dos formatos recusados — é exatamente o erro que uma pessoa não técnica enviando foto do celular vai encontrar primeiro, e o guia não pressupõe em nenhum outro lugar que ela saiba converter formato de imagem. Adiciona duas instruções concretas e não técnicas: ajustar o iPhone para salvar fotos novas em JPEG, e usar o WhatsApp para converter fotos que já estão em HEIC. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: avisar que o e-mail de Contato recebe os briefings do site O runbook descrevia o campo E-mail de Configurações → Contato só como algo que vira link clicável no rodapé e na página /contato. Não mencionava que esse mesmo endereço é o destino de todo envio do formulário de contato: ContactController::dispatchEmails() (app/Http/ Controllers/PublicSite/ContactController.php) chama Mail::to($settings->email)->send(new ContactBriefing(...)) para cada briefing enviado por um visitante. Alguém lendo só esta doc poderia trocar esse valor por um e-mail apenas decorativo e parar silenciosamente de receber notificações de leads, sem nenhum erro visível no admin. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * docs: documentar a tela Usuários para quem realmente pode acessá-la O runbook só citava "Usuários" na negativa ("se você não é Administradora, peça a uma Administradora"), mas toda política de autorização do painel (PortfolioCasePolicy, ServicePolicy, TestimonialPolicy, SiteSettingPolicy, UserPolicy — todas checando $user->isAdmin()) garante que quem consegue seguir qualquer outra seção deste guia é, por construção, uma Administradora com acesso total a Usuários. A doc nunca explicava os campos da tela (Nome, E-mail, Papel, Ativo, Senha) nem como usá-la para dar acesso a alguém, apesar de seções anteriores (Como entrar no painel) já dependerem de a leitora saber fazer exatamente isso. Adiciona uma seção "Usuários" ligando os campos ao comportamento já descrito em outras seções (Papel Assistente remove todo acesso; Ativo desligado bloqueia login via User::canAccessPanel()) e avisa sobre um risco real e não coberto: como UserPolicy não impede autoedição, uma Administradora pode trocar o próprio Papel ou desligar o próprio Ativo e ficar travada fora do painel sem nenhuma forma de recuperação self-service. Também reescreve o bullet de Usuários em "O que NÃO mexer" para não instruir a mesma leitora a evitar uma tela que, pela própria lógica de acesso do sistema, é dela. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code --------- Co-authored-by: manoel.neto <manoel.neto@creditas.com> |
|||
| 7f4ea01f4b |
feat: prepare Dokploy staging and production deploy pipeline (#6)
Publish immutable FrankenPHP images to GHCR, auto-deploy staging after CI, and promote the same digest to production with smoke, backup, and rollback docs. Co-authored-by: Cursor <cursoragent@cursor.com> |
|||
| cafb1167ac |
feat: wire Resend mail and Cloudflare R2 storage (#4)
Add production provider deps/config so transactional email and CMS media can use Resend and a dedicated r2 disk with custom-domain URLs. Co-authored-by: Cursor <cursoragent@cursor.com> |
|||
| 8da8d19504 |
feat: CMS de conteúdo do site (WEB-02/03/04/06)
Implementa gestão Filament de configurações, serviços, portfólio e depoimentos com policies admin-only, upload validado e seed determinístico. Co-authored-by: Cursor <cursoragent@cursor.com> |
|||
| 236a7d3ea9 |
feat: Fase 0 — Fundação (setup-foundation) (#1)
* docs(openspec): propose setup-foundation change Add OpenSpec change for SPEC Phase 0 — Foundation with proposal, design, capability specs, and implementation tasks. Populate openspec/config.yaml with project context and artifact rules. Co-authored-by: Cursor <cursoragent@cursor.com> * feat: bootstrap Laravel 13 foundation (Fase 0) Implement OpenSpec change setup-foundation: Laravel 13 + Filament 5 admin panel, Livewire 4 public site shell, PostgreSQL via Compose, design tokens, role-based auth, quality gates (Pint/Larastan/Pest), FrankenPHP Dockerfile, and GitHub Actions CI with five blocking jobs. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(ci): use valid APP_KEY for encryption in tests Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |