Docs/fix timezone drift (#27)
* docs: add CLAUDE.md with repository architecture guidance Imports AGENTS.md as the primary contract and records the cross-file facts that are not discoverable from any single file: the public-site request spine (controller -> Application query -> DTO -> Blade), the layouts.public view composer, what the architecture test actually enforces, the CI-parity constraint on visual baselines, and the determinism trio behind them. Co-Authored-By: Claude <noreply@anthropic.com> AI-Assisted: yes AI-Tool: claude-code AI-Harness: creditas-agentic-harness AI-Harness-Command: /init * docs: corrigir timezone normativo para America/Fortaleza SPEC.md e openspec/config.yaml declaravam APP_TIMEZONE=America/Sao_Paulo, divergindo da implementação (config/app.php, .env.example), dos quatro jobs de CI e de openspec/specs/visual-regression/spec.md, que fixa America/Fortaleza e cita SPEC §13.5 como autoridade. As baselines visuais foram geradas sob America/Fortaleza. Cidade de atuação permanece São Paulo (capital) — é copy pública, não timezone de runtime. Co-Authored-By: Claude <noreply@anthropic.com> AI-Assisted: yes AI-Tool: claude-code AI-Harness: creditas-agentic-harness AI-Harness-Command: /init * docs: corrigir referências mortas no AGENTS.md - Design Context apontava para `.impeccable.md` na raiz, que não existe. O sistema de design vive em DESIGN.md/PRODUCT.md, tokens em resources/css/tokens.css, briefs por superfície em .impeccable/surfaces/ e a skill vendorizada em .github/skills/impeccable/SKILL.md. - Domain docs afirmava CONTEXT.md na raiz. Não existe: o domínio está em SPEC.md §8 e PRODUCT.md, capacidades em openspec/specs/, e docs/adr/README.md é apenas índice das ADRs decididas em SPEC.md §21. - Issue tracker: registra que o repo não traz .mcp.json, portanto o MCP do Linear precisa estar habilitado na sessão. Co-Authored-By: Claude <noreply@anthropic.com> AI-Assisted: yes AI-Tool: claude-code AI-Harness: creditas-agentic-harness AI-Harness-Command: /init * docs: registrar autenticação do remote no CLAUDE.md O repo é do account `manoel-freitas`, mas a identidade SSH padrão da máquina é outro account (`manoel-freitas-neto`) que não enxerga o repo — o push falha com `Repository not found`, que parece repo inexistente e é problema de acesso. A chave certa é `~/.ssh/id_github_pessoal`, já fixada em `core.sshCommand`. Registra também que o `gh` autentica por token, não por chave SSH, então `gh pr create` continua falhando mesmo com o push funcionando. Co-Authored-By: Claude <noreply@anthropic.com> AI-Assisted: yes AI-Tool: claude-code AI-Harness: creditas-agentic-harness AI-Harness-Command: /init --------- Co-authored-by: manoel.neto <manoel.neto@creditas.com> Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -44,13 +44,15 @@ Copy `.env.example`; never commit secrets or production credentials. Development
|
|||||||
|
|
||||||
## Design Context
|
## Design Context
|
||||||
|
|
||||||
Amare: refined, humane, precise — Heritage Editorial (see `.impeccable.md`). Trust-first, both private + corporate audiences. Never generic wedding decor (hearts/gold/script) or AI-slop. Real proof only. For design skills, read `.impeccable.md` at project root.
|
Amare: refined, humane, precise — Heritage Editorial. Trust-first, both private + corporate audiences. Never generic wedding decor (hearts/gold/script) or AI-slop. Real proof only.
|
||||||
|
|
||||||
|
The design system lives in `DESIGN.md` (palette, typography, layout, do's and don'ts) and positioning in `PRODUCT.md`; tokens are implemented in `resources/css/tokens.css` and asserted by `tests/Feature/PublicSite/HeritageEditorialTokensTest.php`. Per-surface briefs live in `.impeccable/surfaces/`. The Impeccable skill itself is vendored at `.github/skills/impeccable/SKILL.md` — its setup step reads `PRODUCT.md`, `DESIGN.md`, and the matching surface brief.
|
||||||
|
|
||||||
## Agent skills
|
## Agent skills
|
||||||
|
|
||||||
### Issue tracker
|
### Issue tracker
|
||||||
|
|
||||||
Issues live in Linear, driven through the Linear MCP tools. See `docs/agents/issue-tracker.md`.
|
Issues live in Linear, driven through the Linear MCP tools. See `docs/agents/issue-tracker.md` for workspace, team, and tool conventions. The repo ships no `.mcp.json`, so the Linear MCP has to be enabled for the session before those tools exist — if it isn't, report that instead of silently falling back to another tracker.
|
||||||
|
|
||||||
### Triage labels
|
### Triage labels
|
||||||
|
|
||||||
@@ -58,4 +60,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
|
|||||||
|
|
||||||
### Domain docs
|
### Domain docs
|
||||||
|
|
||||||
Single-context: `CONTEXT.md` at root + `docs/adr/`. See `docs/agents/domain.md`.
|
Single-context repo. There is no `CONTEXT.md` — the domain is documented in `SPEC.md` (§8 is the domain model and database schema) and `PRODUCT.md`, with current capabilities described per-capability under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21, and there are no standalone ADR files. `docs/agents/domain.md` describes the generic `CONTEXT.md`/`CONTEXT-MAP.md` layout that the engineering skills look for and instructs them to proceed silently when it's absent, which is the case here.
|
||||||
|
|||||||
81
CLAUDE.md
Normal file
81
CLAUDE.md
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
# 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` is a separate problem.** It authenticates by token, not SSH key, and is logged in as `manoel-freitas-neto`. So `gh pr create` / `gh repo view` fail on this repo even when `git push` succeeds. Opening a PR needs `gh auth login` as `manoel-freitas` (or `gh auth switch` with both accounts added) — ask the user to run it, since it's interactive.
|
||||||
|
|
||||||
|
## 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/Fortaleza` (`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.
|
||||||
6
SPEC.md
6
SPEC.md
@@ -14,7 +14,7 @@
|
|||||||
| Estágio | MVP |
|
| Estágio | MVP |
|
||||||
| Status da especificação | Aprovada para implementação |
|
| Status da especificação | Aprovada para implementação |
|
||||||
| Idioma da interface | Português do Brasil (`pt-BR`) |
|
| Idioma da interface | Português do Brasil (`pt-BR`) |
|
||||||
| Timezone padrão | `America/Sao_Paulo` |
|
| Timezone padrão | `America/Fortaleza` |
|
||||||
| Cidade de atuação | São Paulo (capital) |
|
| Cidade de atuação | São Paulo (capital) |
|
||||||
| Moeda | BRL, sem conversão entre moedas |
|
| Moeda | BRL, sem conversão entre moedas |
|
||||||
| Princípio principal | YAGNI — implementar somente o necessário para validar o produto |
|
| Princípio principal | YAGNI — implementar somente o necessário para validar o produto |
|
||||||
@@ -1994,7 +1994,7 @@ Determinismo obrigatório:
|
|||||||
|
|
||||||
- Chromium e imagem Linux fixos;
|
- Chromium e imagem Linux fixos;
|
||||||
- viewport fixo;
|
- viewport fixo;
|
||||||
- timezone `America/Sao_Paulo`;
|
- timezone `America/Fortaleza`;
|
||||||
- locale `pt-BR`;
|
- locale `pt-BR`;
|
||||||
- fontes instaladas na imagem;
|
- fontes instaladas na imagem;
|
||||||
- relógio congelado;
|
- relógio congelado;
|
||||||
@@ -2183,7 +2183,7 @@ APP_DEBUG=false
|
|||||||
APP_URL
|
APP_URL
|
||||||
APP_LOCALE=pt_BR
|
APP_LOCALE=pt_BR
|
||||||
APP_FALLBACK_LOCALE=pt_BR
|
APP_FALLBACK_LOCALE=pt_BR
|
||||||
APP_TIMEZONE=America/Sao_Paulo
|
APP_TIMEZONE=America/Fortaleza
|
||||||
|
|
||||||
DB_CONNECTION=pgsql
|
DB_CONNECTION=pgsql
|
||||||
DB_HOST
|
DB_HOST
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ schema: spec-driven
|
|||||||
|
|
||||||
context: |
|
context: |
|
||||||
Fonte de verdade: SPEC.md na raiz. Precedência: instrução do dono do produto > SPEC.md > ADRs > testes > convenções.
|
Fonte de verdade: SPEC.md na raiz. Precedência: instrução do dono do produto > SPEC.md > ADRs > testes > convenções.
|
||||||
Produto: plataforma de assessoria de eventos, single-tenant, MVP. UI em pt-BR, timezone America/Sao_Paulo, atuação em São Paulo (capital), BRL.
|
Produto: plataforma de assessoria de eventos, single-tenant, MVP. UI em pt-BR, timezone America/Fortaleza, atuação em São Paulo (capital), BRL.
|
||||||
Stack: Laravel 13, Filament 5 (/admin), Livewire 4 + Blade + Alpine + Tailwind (site público),
|
Stack: Laravel 13, Filament 5 (/admin), Livewire 4 + Blade + Alpine + Tailwind (site público),
|
||||||
PostgreSQL, FrankenPHP regular mode (sem worker mode), Vite, Pest 4 + Pest Browser, database queue.
|
PostgreSQL, FrankenPHP regular mode (sem worker mode), Vite, Pest 4 + Pest Browser, database queue.
|
||||||
Arquitetura: monólito modular. Interface -> Application (Actions/Queries) -> Domain (Enums/VOs) -> Infrastructure.
|
Arquitetura: monólito modular. Interface -> Application (Actions/Queries) -> Domain (Enums/VOs) -> Infrastructure.
|
||||||
|
|||||||
Reference in New Issue
Block a user