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>
7.0 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
@AGENTS.md
If the import above did not load, read AGENTS.md at the repo root now — it is the primary contract.
AGENTS.md (imported above) is the primary contract: structure, commands, style, testing, husky hooks, commit/PR rules. This file records the cross-file architecture and environment facts that are not obvious from any single file.
Non-negotiables (repeated from AGENTS.md because breaking them is expensive)
- Never modify or commit from the primary working tree on
main. Create a worktree per branch:git worktree add -b <branch> <path> main. - Every project-owned PHP file starts with
declare(strict_types=1);immediately after<?php. - Browser tests are CI-only (they run against a FrankenPHP container built by
docker build, notartisan serve).
Environment note
PHP and Composer are not on PATH in this environment, and vendor/ and node_modules/ are absent. Every composer … / php artisan … command in AGENTS.md and README.md assumes a PHP 8.4+ runtime with Composer 2 installed. Verify the toolchain before promising a command ran.
Git remote auth — two GitHub accounts
origin is git@github.com:manoel-freitas/amore-site.git, owned by the manoel-freitas account. The machine's default SSH identity is a different account (manoel-freitas-neto) that cannot see this repo, so pushes fail with ERROR: Repository not found. — an access error that reads like a missing repo.
- Correct key:
~/.ssh/id_github_pessoal. Verify withssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes -T git@github.com→ should greetHi manoel-freitas!. - The repo has
core.sshCommand = ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yesset locally, so plaingit pushworks. If that config is lost, restore it instead of editing the remote URL. ghauthenticates separately, by token rather than SSH key. As of 2026-08-10 it is logged in asmanoel-freitas, sogh pr create/gh repo viewwork. Confirm withgh auth statusbefore assuming: if it reportsmanoel-freitas-neto, that account cannot see this repo and everyghcall fails on it. Recovering needs an interactivegh auth login(orgh auth switchwith both accounts added), so ask the user to run it.
Request spine for the public site
Adding or changing a public page follows one path — controllers never query models directly:
routes/web.php
→ App\Http\Controllers\PublicSite\*Controller (thin; injects a query object)
→ App\Application\Queries\Marketing\* (invokable, final; owns all Eloquent access)
→ App\Application\Data\* (DTO: HomeContent, PageMeta)
→ resources/views/pages/*.blade.php
HomeController + GetHomeContent together show the shape. Page-level SEO is built with PageMeta::forPage(canonical:, settings:, jsonLd:).
AppServiceProvider::boot() registers a View composer on layouts.public that auto-injects siteSettings and pageMeta when the view didn't supply them — new pages do not have to pass them manually.
Site-wide content is a singleton row reached via SiteSetting::instance(). Publication state comes from the HasPublication concern (->published() scope).
Architecture boundary — what is actually enforced
tests/Architecture/DomainBoundariesTest.php enforces only:
App\Domainuses strict types and neverdd/dump/die.App\Domainnever depends onApp\FilamentorApp\Livewire.
App\Application is not covered by that rule. app/Domain/ currently holds a single placeholder (DomainModule.php); business reads live in app/Application/Queries. Extend the arch test when you add a boundary.
Visual regression — read before touching baselines
- Baselines are committed
.snapfiles undertests/.pest/snapshots/Browser/VisualRegressionTest/. tests/Browser/Screenshots/is gitignored — it only holds diff output.- Regenerate with
composer visual:update. - Baselines are CI-parity artifacts. CI runs the browser suite against a
docker build-produced FrankenPHP container (see thebrowserjob in.github/workflows/ci.yml), so baselines regenerated on macOS against a local server will be rejected by CI. Commit4578457exists because of this.
Determinism relies on three cooperating pieces:
APP_FROZEN_NOW→CarbonImmutable::setTestNow()inAppServiceProvider::freezeClockWhenConfigured()(no-op in production).Database\Seeders\VisualContentSeeder::FROZEN_NOW— the value the browser tests and the CI job both pin to.Tests\Support\StableScreenshot— forces Arial, disables transitions/animations, scrolls the page to settle lazy images, and avoids the flakynetworkidlewait.
Other things that bite
- Livewire/Filament temp uploads are pinned to the
localdisk whenFILESYSTEM_DISK=r2, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands onr2viaApp\Support\PublicImageUploadRules. SetLIVEWIRE_TEMPORARY_FILE_UPLOAD_DISKexplicitly to override. - Contact form is rate limited: named limiter
contact-briefing, 5/min per IP, registered inAppServiceProviderand applied inroutes/web.php. - Filament 5 nested resource layout: resources are split into
app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}rather than a flat resource class. Follow the existing shape inResources/PortfolioCases/. - Everything user-facing is pt-BR: routes are
/servicos,/portfolio,/portfolio/{slug},/sobre,/privacidade,/contato.APP_LOCALE=pt_BR,APP_TIMEZONE=America/Sao_Paulo(config/app.php:68). - Design tokens live in
resources/css/tokens.css(Heritage Editorial; seeDESIGN.md).tests/Feature/PublicSite/HeritageEditorialTokensTest.phpreads that file and asserts the exact hex values,EB Garamond, zero border radii,--amare-container-max: 1120px, and the absence of shadow tokens — so any token edit is a deliberate test change too. Motion lives inresources/js/motion.jsand is asserted bytests/Feature/PublicSite/MotionMarkupTest.php+tests/Browser/MotionTest.php.
Navigating the normative docs
SPEC.mdis the product source of truth and is ~2600 lines. Never read it whole —grep -n '^## ' SPEC.mdand read the numbered section you need (e.g. 7 functional requirements, 8 domain model/DB, 9 technical architecture, 13 test strategy, 15 FrankenPHP deploy).openspec/is the channel for planned change:openspec/specs/<capability>/spec.mdfor current capabilities,openspec/changes/<change>/{proposal,design,tasks}.mdfor in-flight work.openspec/config.yamlholds the precedence rule (product owner >SPEC.md> ADRs > tests > conventions) and repo-wide constraints (YAGNI, money as BIGINT centavos, no generic repositories/BaseService).PRODUCT.mdfor positioning,DESIGN.mdfor the design system,docs/conventions/php-strict-types.md,docs/deployment/dokploy.mdfor the deploy runbook.