Compare commits

..

1 Commits

Author SHA1 Message Date
17060c054c docs: classify Amare MVP Linear issues vs Gitea main (MAN-135)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 13:40:09 -03:00
20 changed files with 204 additions and 404 deletions

View File

@@ -79,4 +79,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
### Domain docs ### Domain docs
Single-context repo. There is no `CONTEXT.md` on purpose — **`SPEC.md` §8.0 is the ubiquitous-language SSOT** (domain model/schema continue in §8.1+). `PRODUCT.md` covers positioning; capabilities live under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21. `docs/agents/domain.md` tells skills to use `SPEC.md` §8 instead of inventing a parallel `CONTEXT.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.

View File

@@ -67,7 +67,7 @@ Site-wide content is a singleton row reached via `SiteSetting::instance()`. Publ
- **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. - **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`. - **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/`. - **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`, `/pacotes/{slug}`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/briefing`, `/contato`, `/privacidade`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`). Glossário canônico: `SPEC.md` §8.0. - **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`. - **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 ## Navigating the normative docs

42
SPEC.md
View File

@@ -283,15 +283,11 @@ Uma funcionalidade fora do MVP só poderá entrar quando:
| Método | Rota | Nome sugerido | Finalidade | | Método | Rota | Nome sugerido | Finalidade |
|---|---|---|---| |---|---|---|---|
| GET | `/` | `home` | Home editorial | | GET | `/` | `home` | Home editorial |
| GET | `/servicos` | `services.index` | Lista de serviços / modalidades publicadas | | GET | `/servicos` | `services.index` | Lista de serviços publicados |
| GET | `/pacotes/{slug}` | `packages.show` | Detalhe de uma modalidade de casamento |
| GET | `/portfolio` | `portfolio.index` | Lista de casos publicados | | GET | `/portfolio` | `portfolio.index` | Lista de casos publicados |
| GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso | | GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso |
| GET | `/sobre` | `about` | História, método e credenciais | | GET | `/sobre` | `about` | História, método e credenciais |
| GET | `/briefing` | `briefing` | Briefing comercial (pedido de proposta) | | GET | `/contato` | `contact` | Briefing de contato |
| POST | `/briefing` | `briefing.store` | Envio do briefing comercial |
| GET | `/contato` | `contact` | Contato de parceiro / fornecedor (não é briefing) |
| POST | `/contato` | `contact.store` | Envio da consulta de parceiro |
| GET | `/privacidade` | `privacy` | Política de privacidade | | GET | `/privacidade` | `privacy` | Política de privacidade |
| GET | `/sitemap.xml` | `sitemap` | Sitemap público | | GET | `/sitemap.xml` | `sitemap` | Sitemap público |
| GET | `/robots.txt` | `robots` | Política de crawling | | GET | `/robots.txt` | `robots` | Política de crawling |
@@ -328,8 +324,7 @@ Administração
- Dashboard: `Filament Page` customizada com widgets orientados a exceção. - Dashboard: `Filament Page` customizada com widgets orientados a exceção.
- Detalhe do evento: página customizada do Resource com resumo operacional. - Detalhe do evento: página customizada do Resource com resumo operacional.
- Briefing comercial: Blade + Controller (`POST /briefing`), ver §11.2. - Briefing público: Blade + Controller (`POST /contato`), ver §11.2.
- Contato de parceiro: Blade + Controller (`POST /contato`) — canal separado do briefing.
- Home: Blade com componentes de design reutilizáveis. - Home: Blade com componentes de design reutilizáveis.
### 5.4 Decisões de UX YAGNI ### 5.4 Decisões de UX YAGNI
@@ -1248,35 +1243,6 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
## 8. Modelo de domínio e banco de dados ## 8. Modelo de domínio e banco de dados
Esta seção é a **fonte única de verdade** do vocabulário de domínio do produto. Issues Linear, OpenSpec, código e copy devem usar estes termos. Não criar glossário paralelo em `CONTEXT.md`.
### 8.0 Linguagem ubíqua (glossário)
**Vertente**:
Frente de marca da Amare — **Casamentos** ou **Corporate**.
_Avoid_: linha de negócio, vertical de produto (em copy pública), “área”
**Modalidade**:
Pacote/formato de acompanhamento de casamento gerenciado no CMS (`WeddingPackage`: Essenza, Conduzione, Grand Jour).
_Avoid_: pacote (em copy pública preferir “modalidade”), serviço genérico, plano
**Briefing**:
Pedido comercial de um potencial cliente descrevendo o evento e solicitando atendimento/proposta. Rota canônica: `/briefing`.
_Avoid_: contato (quando o sentido é pedido comercial), formulário de parceiro
**Contato parceiro**:
Consulta de fornecedor ou proposta de parceria. Rota canônica: `/contato`. Não mistura com Briefing.
_Avoid_: briefing, lead comercial
**CTA contextual**:
Link de conversão de uma Modalidade escolhida — WhatsApp com mensagem que nomeia a modalidade, ou fallback para `/briefing?servico_interesse=` quando não há número oficial válido.
**CTA orientação**:
Link de conversão para visitante que ainda não escolheu Modalidade — WhatsApp com mensagem de orientação humana (sem quiz), ou fallback para `/briefing` quando não há número oficial válido.
**Boutique**:
Conceito de posicionamento (cuidado, especialização, múltiplos serviços sob a mesma marca) — não é entidade de domínio nem rota.
### 8.1 Módulos ### 8.1 Módulos
| Módulo | Responsabilidade | | Módulo | Responsabilidade |
@@ -1829,7 +1795,7 @@ Não transformar seções estáticas em componentes Livewire. Usar Blade quando
### 11.2 Formulário de briefing ### 11.2 Formulário de briefing
> **Estado atual:** o formulário de **Briefing** é Blade + Controller (`GET/POST /briefing`, `ContactBriefingRequest`), conforme WEB-05. A rota `/contato` é o canal de **Contato parceiro** (consulta não persistente), separado do briefing. A criação de Lead comercial persistente segue para a Fase 2. > **Estado atual:** o formulário é implementado em Blade + Controller (`POST /contato`, `ContactBriefingRequest`), conforme WEB-05, e essa é a abordagem aceita — não um estágio provisório. Os requisitos abaixo valem independentemente da tecnologia; a criação de Lead segue para a Fase 2.
O formulário deve: O formulário deve:

View File

@@ -4,10 +4,7 @@ declare(strict_types=1);
namespace App\Application\Queries\Marketing; namespace App\Application\Queries\Marketing;
use App\Domain\Marketing\PortfolioVertical;
use App\Models\PortfolioCase; use App\Models\PortfolioCase;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
final class GetPublishedPortfolioCases final class GetPublishedPortfolioCases
@@ -15,37 +12,12 @@ final class GetPublishedPortfolioCases
/** /**
* @return Collection<int, PortfolioCase> * @return Collection<int, PortfolioCase>
*/ */
public function __invoke(?PortfolioVertical $vertical = null): Collection public function __invoke(): Collection
{ {
return $this->baseQuery($vertical)->get(); return PortfolioCase::query()
}
/**
* @return LengthAwarePaginator<int, PortfolioCase>
*/
public function paginate(?PortfolioVertical $vertical = null, int $perPage = 9): LengthAwarePaginator
{
return $this->baseQuery($vertical)->paginate($perPage)->withQueryString();
}
/**
* @return Builder<PortfolioCase>
*/
private function baseQuery(?PortfolioVertical $vertical): Builder
{
$query = PortfolioCase::query()
->published() ->published()
->with(['images']) ->with(['images'])
->orderBy('sort_order'); ->orderBy('sort_order')
->get();
if ($vertical !== null) {
$query->where(function (Builder $builder) use ($vertical): void {
foreach ($vertical->eventTypePatterns() as $pattern) {
$builder->orWhere('event_type', 'ilike', $pattern);
}
});
}
return $query;
} }
} }

View File

@@ -1,45 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Domain\Marketing;
/**
* Public portfolio verticals used to separate Casamentos and Corporate proof.
*
* Matching is intentionally tolerant of free-text `event_type` values already
* stored in the CMS (e.g. "Casamento", "Mini wedding", "Corporativo").
*/
enum PortfolioVertical: string
{
case Casamentos = 'casamentos';
case Corporate = 'corporate';
public function label(): string
{
return match ($this) {
self::Casamentos => 'Casamentos',
self::Corporate => 'Corporate',
};
}
/**
* @return list<string>
*/
public function eventTypePatterns(): array
{
return match ($this) {
self::Casamentos => ['%casamento%', '%wedding%', '%social%'],
self::Corporate => ['%corporat%', '%empresa%', '%business%'],
};
}
public static function tryFromQuery(?string $value): ?self
{
if ($value === null || $value === '') {
return null;
}
return self::tryFrom($value);
}
}

View File

@@ -6,35 +6,33 @@ namespace App\Http\Controllers\PublicSite;
use App\Application\Data\PageMeta; use App\Application\Data\PageMeta;
use App\Application\Queries\Marketing\FindPublishedPortfolioCaseBySlug; use App\Application\Queries\Marketing\FindPublishedPortfolioCaseBySlug;
use App\Application\Queries\Marketing\GetPublishedPortfolioCases;
use App\Domain\Marketing\PortfolioVertical;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\PortfolioCase;
use App\Models\SiteSetting; use App\Models\SiteSetting;
use Illuminate\Contracts\View\View; use Illuminate\Contracts\View\View;
use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
use Illuminate\Pagination\LengthAwarePaginator;
final class PortfolioController extends Controller final class PortfolioController extends Controller
{ {
public function index(Request $request, GetPublishedPortfolioCases $getPublishedPortfolioCases): View public function index(): View
{ {
$settings = SiteSetting::instance(); $settings = SiteSetting::instance();
$vertical = PortfolioVertical::tryFromQuery($request->query('vertente'));
$cases = $getPublishedPortfolioCases->paginate($vertical); /** @var LengthAwarePaginator<int, PortfolioCase> $cases */
$cases = PortfolioCase::query()
->published()
->with(['images'])
->orderBy('sort_order')
->paginate(9);
return view('pages.portfolio.index', [ return view('pages.portfolio.index', [
'cases' => $cases, 'cases' => $cases,
'vertical' => $vertical,
'siteSettings' => $settings, 'siteSettings' => $settings,
'pageMeta' => PageMeta::forPage( 'pageMeta' => PageMeta::forPage(
canonical: route('portfolio.index', array_filter([ canonical: route('portfolio.index'),
'vertente' => $vertical?->value,
])),
settings: $settings, settings: $settings,
title: PageMeta::withBrandSuffix( title: PageMeta::withBrandSuffix('Portfólio', $settings),
$vertical === null ? 'Portfólio' : 'Portfólio — '.$vertical->label(),
$settings,
),
description: 'Casos reais de eventos conduzidos pela '.$settings->brand_name.'.', description: 'Casos reais de eventos conduzidos pela '.$settings->brand_name.'.',
), ),
]); ]);

View File

@@ -8,45 +8,20 @@ use App\Models\SiteSetting;
final class PackageContactLink final class PackageContactLink
{ {
private const ORIENTATION_MESSAGE = 'Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.';
/** /**
* Resolve the contextual contact link for a wedding package modality. * Resolve the contextual contact link for a wedding package modality.
* *
* @return array{href: string, isWhatsapp: bool} * @return array{href: string, isWhatsapp: bool}
*/ */
public static function for(SiteSetting $settings, string $packageName): array public static function for(SiteSetting $settings, string $packageName): array
{
return self::resolve(
$settings,
'Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.',
['servico_interesse' => $packageName],
);
}
/**
* Resolve the orientation CTA for visitors who have not chosen a modality yet.
*
* @return array{href: string, isWhatsapp: bool}
*/
public static function forOrientation(SiteSetting $settings): array
{
return self::resolve($settings, self::ORIENTATION_MESSAGE, []);
}
/**
* @param array<string, string> $briefingQuery
* @return array{href: string, isWhatsapp: bool}
*/
private static function resolve(SiteSetting $settings, string $whatsappMessage, array $briefingQuery): array
{ {
$digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number); $digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number);
$isWhatsapp = strlen($digits) >= 10; $isWhatsapp = strlen($digits) >= 10;
return [ return [
'href' => $isWhatsapp 'href' => $isWhatsapp
? 'https://wa.me/'.$digits.'?text='.rawurlencode($whatsappMessage) ? 'https://wa.me/'.$digits.'?text='.rawurlencode('Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.')
: route('briefing', $briefingQuery), : route('briefing', ['servico_interesse' => $packageName]),
'isWhatsapp' => $isWhatsapp, 'isWhatsapp' => $isWhatsapp,
]; ];
} }

View File

@@ -1,28 +1,51 @@
# Domain Docs # Domain Docs
How engineering skills should consume this repo's domain documentation. How the engineering skills should consume this repo's domain documentation when exploring the codebase.
## Single source of truth ## Before exploring, read these
**`SPEC.md` §8** (especially **§8.0 Linguagem ubíqua**) is the glossary and domain model for Amare. There is no root `CONTEXT.md` on purpose — do not create one. Prefer `SPEC.md` over Linear copy or OpenSpec prose when terms conflict. - **`CONTEXT.md`** at the repo root, or
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
Also useful: If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
- **`PRODUCT.md`** — positioning and audience (not the glossary) ## File structure
- **`openspec/specs/`** — current capability contracts
- **`docs/adr/README.md`** — index only; ADR-001010 live in `SPEC.md` §21
## Before exploring Single-context repo (most repos):
1. Read `SPEC.md` §8.0 for vocabulary; skim §5.1 for public routes and §8.2+ for persistence shape when relevant. ```
2. If a skill expects `CONTEXT.md` / `CONTEXT-MAP.md` and finds neither, **proceed using `SPEC.md` §8** — do not invent a parallel glossary file. /
├── CONTEXT.md
├── docs/adr/
│ ├── 0001-event-sourced-orders.md
│ └── 0002-postgres-for-write-model.md
└── src/
```
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
```
/
├── CONTEXT-MAP.md
├── docs/adr/ ← system-wide decisions
└── src/
├── ordering/
│ ├── CONTEXT.md
│ └── docs/adr/ ← context-specific decisions
└── billing/
├── CONTEXT.md
└── docs/adr/
```
## Use the glossary's vocabulary ## Use the glossary's vocabulary
When output names a domain concept (issue title, refactor, hypothesis, test name), use the term as defined in `SPEC.md` §8.0. Don't drift to synonyms the glossary explicitly avoids (`_Avoid_`). When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
If the concept isn't in §8.0 yet, either you're inventing language the project doesn't use (reconsider) or there's a real gap — resolve it by updating `SPEC.md` §8.0 via `/grill-with-docs` / domain-modeling, not by adding `CONTEXT.md`. If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
## Flag ADR conflicts ## Flag ADR conflicts
If output contradicts an ADR in `SPEC.md` §21, surface it explicitly rather than silently overriding. If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_

View File

@@ -0,0 +1,118 @@
# MAN-135 — Classificar issues Amare MVP vs Gitea `main`
**Issue:** [Research: classificar issues Amare MVP vs Gitea main](https://linear.app/maneco-workspace/issue/MAN-135/research-classificar-issues-amare-mvp-vs-gitea-main)
**Mapa:** [MAN-134](https://linear.app/maneco-workspace/issue/MAN-134/mapa-classificar-amare-mvp-linear-gitea)
**Branch:** `research/man-135-classify-amare-mvp`
**HEAD canônico inventariado:** `origin/main` @ `93b680c` (`docs: require Linear issues and auto-ship PRs (#4)`)
**Data:** 2026-08-13
## Regras (de MAN-134)
| Regra | Aplicação |
| --- | --- |
| Code truth | Gitea `origin/main` (`manoel-freitas/amare`) |
| Done = shipped | commit presente em `origin/main` |
| GitHub | só pista (`amore-site`); não canônico |
| Pós-MVP | fora de escopo: MAN-111, 112, 113, 114, 115, 131 |
| Status Linear | **não** alterados neste research (exceto fechar MAN-135) |
## Fontes
| Fonte | Uso |
| --- | --- |
| Linear MCP `list_issues` / `get_issue` project **Amare — Lançamento MVP** | universo, status, labels, ACs |
| `git fetch origin` + `git log origin/main` | evidência shipped |
| `tea pr list --state all` (0 open; #1#4 merged) | PRs Gitea |
| `gh pr list` / `gh pr view` (GitHub mirror) | pistas #63, #64 |
| Branches `origin/*` | trabalho em voo |
| Cross-check | [MAN-136 orphans](https://linear.app/maneco-workspace/issue/MAN-136/research-inventariar-merges-orfaos-no-gitea), [MAN-137 MAN-126 state](https://linear.app/maneco-workspace/issue/MAN-137/research-estado-real-de-casamentos-corporate-man-126) (branches locais; **não** em `main` ainda) |
## Contagens (36 issues in-scope)
| Bucket | Count |
| --- | ---: |
| **shipped** | 14 |
| **em voo** | 5 |
| **não começou** | 12 |
| · cliente | 5 |
| · engenharia | 5 |
| · ops-lançamento | 2 |
| **Linear podre** | 5 |
| **Total classificado** | **36** |
| Excluídos Pós-MVP | 6 |
| Issues no projeto | 42 |
---
## Tabela canônica
| Issue | Linear status | Bucket | Evidência / nota |
| --- | --- | --- | --- |
| [Implementar Home e páginas institucionais do MVP](https://linear.app/maneco-workspace/issue/MAN-102/implementar-home-e-paginas-institucionais-do-mvp) | Done | **shipped** | Site público + home editorial em `main` (ex. `#53`, heros, `/sobre`, `/servicos`) |
| [Implementar portfólio e depoimentos](https://linear.app/maneco-workspace/issue/MAN-103/implementar-portfolio-e-depoimentos) | Done | **shipped** | Portfólio/depoimentos + seed fix `49fbc0f` (MAN-103) |
| [Implementar formulário de solicitação de proposta](https://linear.app/maneco-workspace/issue/MAN-104/implementar-formulario-de-solicitacao-de-proposta) | Done | **shipped** | Briefing/contato em `main` (ex. `eed8240`) |
| [Implementar tracking simples da origem dos leads](https://linear.app/maneco-workspace/issue/MAN-105/implementar-tracking-simples-da-origem-dos-leads) | Done | **shipped** | `37296f7` (MAN-105) / GH #37 |
| [Definir e implementar atualização simples de conteúdo](https://linear.app/maneco-workspace/issue/MAN-106/definir-e-implementar-atualizacao-simples-de-conteudo) | Done | **shipped** | `4001cd0` (MAN-106 AC4) / GH #33 |
| [Finalizar SEO técnico e metadados sociais](https://linear.app/maneco-workspace/issue/MAN-107/finalizar-seo-tecnico-e-metadados-sociais) | Done | **shipped** | SEO desde ship público (`3dc1f44` et al.) |
| [Rodar auditoria Lighthouse e corrigir performance do site público](https://linear.app/maneco-workspace/issue/MAN-109/rodar-auditoria-lighthouse-e-corrigir-performance-do-site-publico) | Done | **shipped** | `0ecae4c` (MAN-109) / GH #43 |
| [Auditar se Vue + Livewire faz sentido na arquitetura atual](https://linear.app/maneco-workspace/issue/MAN-117/auditar-se-vue-livewire-faz-sentido-na-arquitetura-atual) | Done | **shipped** | `2e28603` reconcilia stack declarada |
| [Portar identidade visual do site público para o admin em Filament](https://linear.app/maneco-workspace/issue/MAN-118/portar-identidade-visual-do-site-publico-para-o-admin-em-filament) | Done | **shipped** | `63c0269` (MAN-118) / GH #40 |
| [Estabilizar baselines de regressão visual no CI (paridade local×CI)](https://linear.app/maneco-workspace/issue/MAN-119/estabilizar-baselines-de-regressao-visual-no-ci-paridade-localci) | Done | **shipped** | Trabalho de baselines chegou em `main`; remoção posterior `#44` é resolução intencional, não “Done sem merge” |
| [Fechar portões de qualidade e deploy da Fase 0 antes do lançamento](https://linear.app/maneco-workspace/issue/MAN-120/fechar-portoes-de-qualidade-e-deploy-da-fase-0-antes-do-lancamento) | Done | **shipped** | `7e68c0e` (MAN-120) / GH #39 |
| [Reencodar imagens de marca e regerar baselines visuais](https://linear.app/maneco-workspace/issue/MAN-122/reencodar-imagens-de-marca-e-regerar-baselines-visuais) | Done | **shipped** | Era de baselines/imagem em `main` (série #24/#7 + follow-ups) |
| [Deploy de staging falha no Dokploy desde 08/08 — causa server-side, sem log acessível](https://linear.app/maneco-workspace/issue/MAN-123/deploy-de-staging-falha-no-dokploy-desde-0808-causa-server-side-sem) | Done | **shipped** | Pipeline/fixes Dokploy+staging em `main` (ex. `#6``#9`, `ac08b5f`, `26a68e1`) |
| [Reconciliar documentação e changes OpenSpec após os PRs #41#49](https://linear.app/maneco-workspace/issue/MAN-124/reconciliar-documentacao-e-changes-openspec-apos-os-prs-41-49) | Done | **shipped** | `213d684` (MAN-124) / GH #50 |
| [Reestruturar jornada pública em Amare Casamentos e Amare Corporate](https://linear.app/maneco-workspace/issue/MAN-126/reestruturar-jornada-publica-em-amare-casamentos-e-amare-corporate) | In Progress | **em voo** | `origin/feat/man-126-casamentos-corporate` (1 commit ahead, não em `main`); GH PR [#63](https://github.com/manoel-freitas/amore-site/pull/63) open; **0** PR Gitea. Ver MAN-137 |
| [Executar QA de responsividade e acessibilidade](https://linear.app/maneco-workspace/issue/MAN-108/executar-qa-de-responsividade-e-acessibilidade) | In Progress | **em voo** | Status coerente com QA aberto; PRs de suporte (#44#49) já em `main`, ACs manuais não fechados; sem PR Gitea aberto só desta issue |
| [Configurar domínio, ambiente de produção e checklist de lançamento](https://linear.app/maneco-workspace/issue/MAN-110/configurar-dominio-ambiente-de-producao-e-checklist-de-lancamento) | In Progress | **em voo** | Ops de lançamento em curso (staging/deploy em `main`; produção/DNS/checklist incompletos) |
| [Ajustar CTAs consultivos no WhatsApp e briefing comercial](https://linear.app/maneco-workspace/issue/MAN-127/ajustar-ctas-consultivos-no-whatsapp-e-briefing-comercial) | Todo | **em voo** | Fluxo 1 parcial em `main` via Gitea [#2](https://git.hellomanoel.com/manoel-freitas/amare/pulls/2) (`e2af1f6`); Fluxo 2 (indecisos) + fechamento de ACs ainda abertos; Linear Todo atrasa vs código parcial |
| [Research: classificar issues Amare MVP vs Gitea main](https://linear.app/maneco-workspace/issue/MAN-135/research-classificar-issues-amare-mvp-vs-gitea-main) | Backlog→Done* | **em voo** | Este arquivo / branch `research/man-135-classify-amare-mvp`**ainda não** em `origin/main` (*Done só no Linear após resolução; ver podre se Done sem merge) |
| [Exigir issue Linear em todo trabalho e PR](https://linear.app/maneco-workspace/issue/MAN-132/exigir-issue-linear-em-todo-trabalho-e-pr) | In Progress | **Linear podre** | Conteúdo em `main` via Gitea [#4](https://git.hellomanoel.com/manoel-freitas/amare/pulls/4) (`93b680c`); Linear ainda In Progress. GH [#64](https://github.com/manoel-freitas/amore-site/pull/64) open = mirror stale |
| [Adicionar PR template no Gitea e no GitHub](https://linear.app/maneco-workspace/issue/MAN-133/adicionar-pr-template-no-gitea-e-no-github) | In Progress | **Linear podre** | `.gitea/PULL_REQUEST_TEMPLATE.md` + `.github/pull_request_template.md` em `origin/main` (mesmo PR #4); Linear In Progress |
| [Fechar arquitetura e copy final do MVP](https://linear.app/maneco-workspace/issue/MAN-97/fechar-arquitetura-e-copy-final-do-mvp) | In Progress | **Linear podre** | In Progress sem branch/PR abertos; arquitetura/home editorial já em `main` (#46#53, #52…). Residual de copy/conteúdo vive em tickets cliente (MAN-98…) e MAN-126 |
| [Research: inventariar merges órfãos no Gitea](https://linear.app/maneco-workspace/issue/MAN-136/research-inventariar-merges-orfaos-no-gitea) | Done | **Linear podre** | Done no Linear; deliverable em `origin/research/man-136-orphan-gitea-merges`**não** merged em `main` |
| [Research: estado real de Casamentos × Corporate (MAN-126)](https://linear.app/maneco-workspace/issue/MAN-137/research-estado-real-de-casamentos-corporate-man-126) | Done | **Linear podre** | Done no Linear; `docs/research/man-137-man-126-state.md` em branch local ahead — **não** em `origin/main` |
| [Receber e consolidar conteúdo institucional e serviços da Michelle](https://linear.app/maneco-workspace/issue/MAN-98/receber-e-consolidar-conteudo-institucional-e-servicos-da-michelle) | Todo | **não começou · cliente** | Labels Conteúdo/Cliente; sem branch/PR |
| [Fechar portfólio e banco de imagens do lançamento](https://linear.app/maneco-workspace/issue/MAN-99/fechar-portfolio-e-banco-de-imagens-do-lancamento) | Todo | **não começou · cliente** | Labels Conteúdo/Cliente; sem branch/PR |
| [Consolidar depoimentos reais para o MVP](https://linear.app/maneco-workspace/issue/MAN-100/consolidar-depoimentos-reais-para-o-mvp) | Todo | **não começou · cliente** | Labels Conteúdo/Cliente; sem branch/PR |
| [Confirmar WhatsApp Business e canais de contato](https://linear.app/maneco-workspace/issue/MAN-101/confirmar-whatsapp-business-e-canais-de-contato) | Todo | **não começou · cliente** | Labels Conteúdo/Cliente; sem branch/PR |
| [Aplicar identidade visual oficial e rodada de revisão da cliente](https://linear.app/maneco-workspace/issue/MAN-128/aplicar-identidade-visual-oficial-e-rodada-de-revisao-da-cliente) | Todo | **não começou · cliente** | Labels Cliente/Design; sem branch/PR |
| [Papel Assistant não tem nenhuma permissão funcional](https://linear.app/maneco-workspace/issue/MAN-121/papel-assistant-nao-tem-nenhuma-permissao-funcional) | Backlog | **não começou · engenharia** | Enum `Assistant` existe; permissões funcionais não entregues; sem branch |
| [Provar falha dos gates e rollback antes de arquivar complete-foundation-parity](https://linear.app/maneco-workspace/issue/MAN-125/provar-falha-dos-gates-e-rollback-antes-de-arquivar-complete) | Todo | **não começou · engenharia** | ACs de prova controlada abertos; experiência **não** deve mergear em `main` |
| [Revisar privacidade, LGPD e consentimentos do MVP](https://linear.app/maneco-workspace/issue/MAN-116/revisar-privacidade-lgpd-e-consentimentos-do-mvp) | Todo | **não começou · engenharia** | Página privacidade existe; revisão/consentimentos ACs abertos; sem branch |
| [Mapa: classificar Amare MVP (Linear × Gitea)](https://linear.app/maneco-workspace/issue/MAN-134/mapa-classificar-amare-mvp-linear-gitea) | Todo | **não começou · engenharia** | Wayfinder map — inventário ainda em research filhos |
| [Grilling: lista de ações de sync após inventário](https://linear.app/maneco-workspace/issue/MAN-138/grilling-lista-de-acoes-de-sync-apos-inventario) | Backlog | **não começou · engenharia** | Depende do inventário (este + MAN-136/137) |
| [Configurar Search Console, Meta Pixel e mensuração de lançamento](https://linear.app/maneco-workspace/issue/MAN-129/configurar-search-console-meta-pixel-e-mensuracao-de-lancamento) | Todo | **não começou · ops-lançamento** | Mensuração de go-live; sem branch |
| [Preparar acesso e treinamento do painel administrativo](https://linear.app/maneco-workspace/issue/MAN-130/preparar-acesso-e-treinamento-do-painel-administrativo) | Todo | **não começou · ops-lançamento** | Acesso/treino go-live (label Cliente+Engenharia); sem branch |
### Fora de escopo (Pós-MVP)
| Issue | Nota |
| --- | --- |
| [MAN-111](https://linear.app/maneco-workspace/issue/MAN-111/pos-mvp-produzir-pdf-comercial-estatico-de-servicos) … [MAN-115](https://linear.app/maneco-workspace/issue/MAN-115/pos-mvp-mapear-convivencia-com-o-assessoria-vip), [MAN-131](https://linear.app/maneco-workspace/issue/MAN-131/pos-mvp-formulario-para-fornecedores-e-parcerias) | Não classificados (mapa Out of scope) |
---
## Hints verificados
| Hint | Resultado |
| --- | --- |
| MAN-132 + MAN-133 via Gitea PR #4, Linear In Progress | **Confirmado → Linear podre** (código shipped; status podre). Também GH #64 ainda open |
| MAN-126: GH #63 + branch, sem PR Gitea | **Confirmado → em voo** |
| Gitea 0 open PRs; #1#4 merged | **Confirmado** (`tea pr list`) |
## Ações de sync sugeridas (não executadas aqui)
1. Marcar **Done** no Linear: MAN-132, MAN-133 (e fechar GH #64 stale).
2. Reavaliar MAN-97 → Done ou decompor residual em tickets cliente/MAN-126.
3. Abrir **PR Gitea** para `feat/man-126-casamentos-corporate`; fechar ou ignorar GH #63 após mirror.
4. MAN-127: mover para In Progress; fechar Fluxo 2 / ACs restantes ou fatiar ticket.
5. Mergear (ou aceitar exceção) branches de research MAN-136/137/135 se “Done” exigir `main`.
6. Lista priorizada fica para [MAN-138](https://linear.app/maneco-workspace/issue/MAN-138/grilling-lista-de-acoes-de-sync-apos-inventario).
## Ambiguidades
- **Wayfinder research Done:** mapa diz Done = `main`; prática atual marca Done após comentário Linear com arquivo só na branch → classificado **Linear podre** para MAN-136/137.
- **MAN-119/122:** remoção de regressão visual (`#44`) após estabilizar — tratados como shipped (resolução chegou em `main`).
- **MAN-127:** bucket **em voo** (parcial shipped + resto aberto); alternativa seria “não começou” só do residual — preferimos um bucket por issue.
- **MAN-108/110:** sem branch dedicada; “em voo” = processo In Progress, não PR aberta.

View File

@@ -134,8 +134,7 @@
overflow-wrap: break-word; overflow-wrap: break-word;
} }
[id$='-heading'], [id$='-heading'] {
.home-chapter[id] {
scroll-margin-top: 5.5rem; scroll-margin-top: 5.5rem;
} }

View File

@@ -15,9 +15,7 @@
]) ])
@php @php
$orientationLink = \App\Support\PackageContactLink::forOrientation($settings); $bandHref = $bandCtaHref ?? route('briefing');
$bandHref = $bandCtaHref ?? $orientationLink['href'];
$bandIsWhatsapp = $bandCtaHref === null && $orientationLink['isWhatsapp'];
@endphp @endphp
<section <section
aria-labelledby="packages-heading" aria-labelledby="packages-heading"
@@ -79,11 +77,7 @@
<h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3> <h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3>
<p class="text-amare-accent-text/80">{{ $bandBody }}</p> <p class="text-amare-accent-text/80">{{ $bandBody }}</p>
</div> </div>
<a <a href="{{ $bandHref }}" class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto">
href="{{ $bandHref }}"
@if ($bandIsWhatsapp) target="_blank" rel="noopener noreferrer" @endif
class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto"
>
Conversar com a Amare Conversar com a Amare
</a> </a>
</div> </div>

View File

@@ -58,8 +58,8 @@
<header class="site-header sticky top-0 z-40 border-b border-amare-border bg-amare-bg/95 backdrop-blur-sm"> <header class="site-header sticky top-0 z-40 border-b border-amare-border bg-amare-bg/95 backdrop-blur-sm">
<div class="container-amare grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4 md:grid-cols-[1fr_auto_1fr] md:h-[74px] md:py-0"> <div class="container-amare grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4 md:grid-cols-[1fr_auto_1fr] md:h-[74px] md:py-0">
<nav id="main-nav" class="main-nav order-3 col-span-3 hidden flex-col gap-4 border-t border-amare-border pt-4 md:order-1 md:col-span-1 md:flex md:flex-row md:items-center md:gap-1 md:border-0 md:pt-0" aria-label="Principal" data-main-nav> <nav id="main-nav" class="main-nav order-3 col-span-3 hidden flex-col gap-4 border-t border-amare-border pt-4 md:order-1 md:col-span-1 md:flex md:flex-row md:items-center md:gap-1 md:border-0 md:pt-0" aria-label="Principal" data-main-nav>
<a href="{{ url('/#casamentos') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Casamentos</a> <a href="{{ route('home') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Início</a>
<a href="{{ url('/#corporate') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Corporate</a> <a href="{{ route('services.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Serviços</a>
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Portfólio</a> <a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Portfólio</a>
<a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Amare</a> <a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Amare</a>
<a href="{{ route('briefing') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:hidden">Solicitar proposta</a> <a href="{{ route('briefing') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:hidden">Solicitar proposta</a>
@@ -73,6 +73,7 @@
<a href="{{ route('briefing') }}" class="btn btn-outline hidden min-h-0 px-6 text-xs font-bold uppercase tracking-[0.09em] md:inline-flex"> <a href="{{ route('briefing') }}" class="btn btn-outline hidden min-h-0 px-6 text-xs font-bold uppercase tracking-[0.09em] md:inline-flex">
Conte seu evento Conte seu evento
</a> </a>
</a>
<button <button
type="button" type="button"
@@ -110,11 +111,10 @@
$footerSocials = collect($siteSettings->social_links ?? [])->filter(static fn ($url) => filled($url)); $footerSocials = collect($siteSettings->social_links ?? [])->filter(static fn ($url) => filled($url));
@endphp @endphp
<div class="container-amare flex flex-col gap-10 py-14 md:py-[58px]"> <div class="container-amare flex flex-col gap-8 py-14 md:flex-row md:items-end md:justify-between md:py-[58px]">
<div class="flex flex-col gap-8 md:flex-row md:items-end md:justify-between">
<div class="space-y-3"> <div class="space-y-3">
<x-brand.logo variant="on-light" class="h-10 w-auto" /> <x-brand.logo variant="on-light" class="h-10 w-auto" />
<p class="text-sm text-amare-muted">Boutique de assessoria &amp; produção &bull; São Paulo</p> <p class="text-sm text-amare-muted">Assessoria &amp; produção de eventos &bull; São Paulo</p>
</div> </div>
<p class="text-sm text-amare-muted md:text-right"> <p class="text-sm text-amare-muted md:text-right">
@@ -128,21 +128,12 @@
</p> </p>
</div> </div>
<nav aria-label="Rodapé" class="flex flex-col gap-4 border-t border-amare-border pt-8 text-sm text-amare-muted md:flex-row md:flex-wrap md:items-center md:gap-x-6 md:gap-y-2">
<a href="{{ url('/#casamentos') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Casamentos</a>
<a href="{{ url('/#corporate') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Corporate</a>
<a href="{{ route('services.index') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Serviços</a>
<a href="{{ route('portfolio.index') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Portfólio</a>
<a href="{{ route('about') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Sobre</a>
<a href="{{ route('briefing') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Briefing</a>
<a href="{{ route('contact') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Parcerias</a>
<a href="{{ route('privacy') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Privacidade</a>
</nav>
</div>
<div class="border-t border-amare-border"> <div class="border-t border-amare-border">
<div class="container-amare flex flex-col gap-2 py-6 text-sm text-amare-muted md:flex-row md:items-center md:justify-between"> <div class="container-amare flex flex-col gap-2 py-6 text-sm text-amare-muted md:flex-row md:items-center md:justify-between">
<p>&copy; {{ now()->year }} {{ $siteSettings->brand_name }}. Todos os direitos reservados.</p> <p>&copy; {{ now()->year }} {{ $siteSettings->brand_name }}. Todos os direitos reservados.</p>
<p>
<a href="{{ route('privacy') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Política de privacidade</a>
</p>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -5,38 +5,9 @@
<div class="border-b border-amare-border bg-amare-bg-deep"> <div class="border-b border-amare-border bg-amare-bg-deep">
<div class="container-amare space-y-10 py-16 md:py-24"> <div class="container-amare space-y-10 py-16 md:py-24">
<nav aria-label="Vertentes do portfólio" class="flex flex-wrap gap-x-6 gap-y-2 border-b border-amare-border pb-6 text-sm">
<a
href="{{ route('portfolio.index') }}"
@class([
'inline-flex min-h-11 items-center uppercase tracking-[0.08em] transition-colors',
'font-semibold text-amare-accent' => $vertical === null,
'text-amare-muted hover:text-amare-accent' => $vertical !== null,
])
>Todos</a>
@foreach (\App\Domain\Marketing\PortfolioVertical::cases() as $option)
<a
href="{{ route('portfolio.index', ['vertente' => $option->value]) }}"
@class([
'inline-flex min-h-11 items-center uppercase tracking-[0.08em] transition-colors',
'font-semibold text-amare-accent' => $vertical === $option,
'text-amare-muted hover:text-amare-accent' => $vertical !== $option,
])
>{{ $option->label() }}</a>
@endforeach
</nav>
@if ($cases->isEmpty()) @if ($cases->isEmpty())
@if ($vertical === \App\Domain\Marketing\PortfolioVertical::Corporate)
<div class="flex min-h-[280px] flex-col items-center justify-center gap-4 bg-amare-bg p-10 text-center">
<p class="text-xs font-bold uppercase tracking-[0.16em] text-amare-accent-deep">Portfólio Corporate</p>
<h2 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight text-amare-text">Conteúdo em construção</h2>
<p class="max-w-lg text-amare-text-muted">Ainda não publicamos cases corporativos autorizados. Em vez de inventar prova, mantemos este espaço pronto para projetos reais.</p>
<a href="{{ route('briefing') }}" class="btn btn-outline mt-2 text-xs font-bold uppercase tracking-[0.09em]">Falar sobre um evento corporativo</a>
</div>
@else
<p class="text-amare-muted">Novos casos serão publicados assim que o acervo estiver organizado. Enquanto isso, fale conosco para conhecer o nosso trabalho.</p> <p class="text-amare-muted">Novos casos serão publicados assim que o acervo estiver organizado. Enquanto isso, fale conosco para conhecer o nosso trabalho.</p>
@endif
@else @else
@php @php
$hasPrioritizedImage = false; $hasPrioritizedImage = false;
@@ -82,9 +53,6 @@
@endphp @endphp
@endif @endif
<div class="space-y-2 border-t border-amare-border pt-4"> <div class="space-y-2 border-t border-amare-border pt-4">
@if (filled($case->event_type))
<p class="text-xs font-bold uppercase tracking-[0.16em] text-amare-accent-deep">{{ $case->event_type }}</p>
@endif
<h2 class="text-2xl font-medium text-amare-text"> <h2 class="text-2xl font-medium text-amare-text">
<a href="{{ route('portfolio.show', $case->slug) }}" class="transition-colors hover:text-amare-accent">{{ $case->title }}</a> <a href="{{ route('portfolio.show', $case->slug) }}" class="transition-colors hover:text-amare-accent">{{ $case->title }}</a>
</h2> </h2>

View File

@@ -47,6 +47,7 @@
:show-intro="false" :show-intro="false"
kicker="tag" kicker="tag"
:show-subtitle="true" :show-subtitle="true"
:band-cta-href="route('briefing')"
band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático." band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático."
:show-note="false" :show-note="false"
/> />

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Tests\Feature\Application\Queries\Marketing; namespace Tests\Feature\Application\Queries\Marketing;
use App\Application\Queries\Marketing\GetPublishedPortfolioCases; use App\Application\Queries\Marketing\GetPublishedPortfolioCases;
use App\Domain\Marketing\PortfolioVertical;
use App\Models\PortfolioCase; use App\Models\PortfolioCase;
use App\Models\PortfolioImage; use App\Models\PortfolioImage;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -59,26 +58,4 @@ class GetPublishedPortfolioCasesTest extends TestCase
$this->assertTrue($cases->get(0)?->relationLoaded('images')); $this->assertTrue($cases->get(0)?->relationLoaded('images'));
$this->assertCount(2, $cases->get(0)?->images ?? []); $this->assertCount(2, $cases->get(0)?->images ?? []);
} }
public function test_filters_published_cases_by_portfolio_vertical(): void
{
PortfolioCase::factory()->published()->create([
'title' => 'Wedding Case',
'event_type' => 'Mini wedding',
'sort_order' => 10,
]);
PortfolioCase::factory()->published()->create([
'title' => 'Corporate Case',
'event_type' => 'Evento corporativo',
'sort_order' => 20,
]);
$weddings = (new GetPublishedPortfolioCases)(PortfolioVertical::Casamentos);
$corporate = (new GetPublishedPortfolioCases)(PortfolioVertical::Corporate);
$this->assertCount(1, $weddings);
$this->assertSame('Wedding Case', $weddings->first()?->title);
$this->assertCount(1, $corporate);
$this->assertSame('Corporate Case', $corporate->first()?->title);
}
} }

View File

@@ -213,26 +213,7 @@ class HomePageContentTest extends TestCase
false, false,
) )
->assertSee('target="_blank"', false) ->assertSee('target="_blank"', false)
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false) ->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false);
->assertSee(
'https://wa.me/5511988887777?text='.rawurlencode('Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.'),
false,
);
}
public function test_orientation_cta_falls_back_to_briefing_without_whatsapp_number(): void
{
SiteSetting::instance()->update(['whatsapp_number' => null]);
$response = $this->get(route('home'));
$response
->assertOk()
->assertSee('href="'.route('briefing').'"', false)
->assertDontSee(
'Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.',
false,
);
} }
public function test_corporate_steps_and_placeholder_render_from_settings(): void public function test_corporate_steps_and_placeholder_render_from_settings(): void

View File

@@ -1,60 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\PublicSite;
use App\Models\PortfolioCase;
use App\Models\SiteSetting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PortfolioVerticalFilterTest extends TestCase
{
use RefreshDatabase;
public function test_portfolio_index_filters_by_vertical_and_shows_corporate_empty_state(): void
{
SiteSetting::instance();
PortfolioCase::factory()->published()->create([
'title' => 'Casamento Jardim',
'slug' => 'casamento-jardim',
'event_type' => 'Casamento',
'sort_order' => 10,
]);
PortfolioCase::factory()->published()->create([
'title' => 'Convenção Anual',
'slug' => 'convencao-anual',
'event_type' => 'Corporativo',
'sort_order' => 20,
]);
$this->get(route('portfolio.index'))
->assertOk()
->assertSee('Casamento Jardim')
->assertSee('Convenção Anual')
->assertSee('Vertentes do portfólio')
->assertSee('Casamentos')
->assertSee('Corporate');
$this->get(route('portfolio.index', ['vertente' => 'casamentos']))
->assertOk()
->assertSee('Casamento Jardim')
->assertDontSee('Convenção Anual');
$this->get(route('portfolio.index', ['vertente' => 'corporate']))
->assertOk()
->assertSee('Convenção Anual')
->assertDontSee('Casamento Jardim');
PortfolioCase::query()->where('slug', 'convencao-anual')->update(['published_at' => null]);
$this->get(route('portfolio.index', ['vertente' => 'corporate']))
->assertOk()
->assertSee('Portfólio Corporate')
->assertSee('Conteúdo em construção')
->assertDontSee('Convenção Anual');
}
}

View File

@@ -54,8 +54,7 @@ class PublicLayoutSeoTest extends TestCase
->assertSee('Amare Brand') ->assertSee('Amare Brand')
->assertSee('hello@amare.test') ->assertSee('hello@amare.test')
->assertSee('(11) 91111-1111') ->assertSee('(11) 91111-1111')
->assertSee('Privacidade') ->assertSee('Política de privacidade')
->assertSee(route('privacy'), false)
->assertSee('https://instagram.com/amare', false); ->assertSee('https://instagram.com/amare', false);
} }
} }

View File

@@ -1,43 +0,0 @@
<?php
declare(strict_types=1);
namespace Tests\Feature\PublicSite;
use App\Models\SiteSetting;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class PublicNavigationVerticalsTest extends TestCase
{
use RefreshDatabase;
public function test_header_and_footer_expose_casamentos_and_corporate_without_implying_weddings_only(): void
{
SiteSetting::instance();
$response = $this->get(route('about'))->assertOk();
$homeCasamentos = url('/#casamentos');
$homeCorporate = url('/#corporate');
$response
->assertSee('id="main-nav"', false)
->assertSee('href="'.$homeCasamentos.'"', false)
->assertSee('>Casamentos</a>', false)
->assertSee('href="'.$homeCorporate.'"', false)
->assertSee('>Corporate</a>', false)
->assertSee('href="'.route('portfolio.index').'"', false)
->assertSee('href="'.route('about').'"', false)
->assertSee('href="'.route('services.index').'"', false)
->assertSee('href="'.route('privacy').'"', false)
->assertSee('aria-label="Rodapé"', false)
->assertSeeInOrder([
'aria-label="Rodapé"',
'href="'.$homeCasamentos.'"',
'Casamentos',
'href="'.$homeCorporate.'"',
'Corporate',
], false);
}
}

View File

@@ -50,18 +50,4 @@ class ServicesPageCtaTest extends TestCase
->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false) ->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false)
->assertDontSee('wa.me/', false); ->assertDontSee('wa.me/', false);
} }
public function test_orientation_cta_uses_distinct_whatsapp_message_when_a_number_is_configured(): void
{
SiteSetting::instance()->update(['whatsapp_number' => '+55 11 98888-7777']);
$response = $this->get(route('services.index'));
$response
->assertOk()
->assertSee(
'https://wa.me/5511988887777?text='.rawurlencode('Olá, ainda não sei qual modalidade de acompanhamento combina com o meu casamento e gostaria de orientação da Amare.'),
false,
);
}
} }