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>
82 lines
7.0 KiB
Markdown
82 lines
7.0 KiB
Markdown
# 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`, not `artisan 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 with `ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes -T git@github.com` → should greet `Hi manoel-freitas!`.
|
|
- The repo has `core.sshCommand = ssh -i ~/.ssh/id_github_pessoal -o IdentitiesOnly=yes` set locally, so plain `git push` works. If that config is lost, restore it instead of editing the remote URL.
|
|
- **`gh` authenticates separately**, by token rather than SSH key. As of 2026-08-10 it is logged in as `manoel-freitas`, so `gh pr create` / `gh repo view` work. Confirm with `gh auth status` before assuming: if it reports `manoel-freitas-neto`, that account cannot see this repo and every `gh` call fails on it. Recovering needs an interactive `gh auth login` (or `gh auth switch` with 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\Domain` uses strict types and never `dd`/`dump`/`die`.
|
|
- `App\Domain` never depends on `App\Filament` or `App\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 `.snap` files under `tests/.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 the `browser` job in `.github/workflows/ci.yml`), so baselines regenerated on macOS against a local server will be rejected by CI. Commit `4578457` exists because of this.
|
|
|
|
Determinism relies on three cooperating pieces:
|
|
|
|
- `APP_FROZEN_NOW` → `CarbonImmutable::setTestNow()` in `AppServiceProvider::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 flaky `networkidle` wait.
|
|
|
|
## Other things that bite
|
|
|
|
- **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override.
|
|
- **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/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 in `Resources/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; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads 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 in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`.
|
|
|
|
## Navigating the normative docs
|
|
|
|
- `SPEC.md` is the product source of truth and is ~2600 lines. **Never read it whole** — `grep -n '^## ' SPEC.md` and 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.md` for current capabilities, `openspec/changes/<change>/{proposal,design,tasks}.md` for in-flight work. `openspec/config.yaml` holds the precedence rule (product owner > `SPEC.md` > ADRs > tests > conventions) and repo-wide constraints (YAGNI, money as BIGINT centavos, no generic repositories/BaseService).
|
|
- `PRODUCT.md` for positioning, `DESIGN.md` for the design system, `docs/conventions/php-strict-types.md`, `docs/deployment/dokploy.md` for the deploy runbook.
|