6.3 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.
Deterministic test support
APP_FROZEN_NOW configures CarbonImmutable::setTestNow() through AppServiceProvider::freezeClockWhenConfigured() outside production. VisualContentSeeder provides deterministic image/content fixtures for tests that explicitly need them. Neither setting is a browser-CI global default.
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.