From f89fc4b81ff05778dba2cd403ed93349d50f9f37 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 21:16:46 +0000 Subject: [PATCH] =?UTF-8?q?feat(campanha):=20B133=20=E2=80=94=20listas=20s?= =?UTF-8?q?em=20subt=C3=ADtulo=20de=20prosa=20+=20filtro=20salvo=20no=20he?= =?UTF-8?q?ader?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove prose subtitles from list routes, Quadro and create/edit chrome - Keep subtitles on Organizações, Conceitos, Perfil, Contatos (out of scope) - MunicipalityListPageChrome: saved filter name as header subtitle when URL matches - Edit pages: title-only chrome override - Impl plan: docs/plans/listas-sem-subtitulo-prosa-impl.md Closes #283 Co-authored-by: Francisco Solla --- docs/plans/listas-sem-subtitulo-prosa-impl.md | 67 ++++++++++++++ .../(app)/atividades/[slug]/editar/page.tsx | 13 +-- .../(app)/municipios/[slug]/editar/page.tsx | 14 +-- .../campanha/(app)/municipios/page.tsx | 2 + .../MunicipalityListPageChrome.tsx | 41 +++++++++ src/lib/campaignPageChrome.ts | 91 ++++++------------- tests/unit/campaignPageChrome.unit.spec.ts | 24 +++-- 7 files changed, 160 insertions(+), 92 deletions(-) create mode 100644 docs/plans/listas-sem-subtitulo-prosa-impl.md create mode 100644 src/components/campaign/municipality/MunicipalityListPageChrome.tsx diff --git a/docs/plans/listas-sem-subtitulo-prosa-impl.md b/docs/plans/listas-sem-subtitulo-prosa-impl.md new file mode 100644 index 00000000..057daa15 --- /dev/null +++ b/docs/plans/listas-sem-subtitulo-prosa-impl.md @@ -0,0 +1,67 @@ +# Impl: Listas `/campanha` — subtítulos curtos (sem prosa; filtro salvo em Municípios) + +Status: aprovado +Atualizado em: 2026-08-02 +Issue: #283 +Intenção: docs/plans/listas-sem-subtitulo-prosa.md +Appetite restante: ~0,5 dia eng (herdado) + +## Leitura da intenção + +- **Outcome:** Header das listas citadas, Quadro e create/edit estáticos mostram só o título — sem parágrafo de prosa. Em `/campanha/municipios`, quando a URL casa com um filtro salvo (B18), o subtítulo do header passa a ser o **nome** do filtro; a label de resumo duplicada some da barra. +- **O que NÃO negociar:** Organizações, Conceitos, Perfil, Contatos mantêm subtítulos atuais; chips/omnibox como editor de filtro ficam; leader lockdown intocado; não puxar serializador pesado de URL para o layout `(app)`. +- **O que reavaliar:** A label de resumo (`formatMunicipalityActiveFiltersSummary`) ao lado da busca **já foi removida** no B127 (omnibox). O aceite de “sumir a label duplicada” está satisfeito — só garantir que não reintroduzimos resumo visível na barra. + +## Abordagem recomendada + +```mermaid +flowchart LR + A[campaignPageChrome pathRules] -->|sectionOnly| B[Header título só] + C[MunicipalityListPageChrome] -->|SetCampaignPageChrome| D[Subtítulo = nome filtro salvo] + E[Edit pages] -->|remove subtitle override| B +``` + +**Opções consideradas:** +- A) Apagar `subtitle` no catálogo e manter `pathRules` com `resolveCatalogEntry` — catálogo ainda carrega prosa morta. +- B) **`pathRules` → `sectionOnly(title)`** para rotas no escopo; catálogo limpo nas entradas afetadas; metadata já usa só `title`. +- C) `SetCampaignPageChrome` em cada rota create/edit — duplica o catálogo. + +**Recomendação:** B — o dono (`campaignPageChrome.ts`) já resolve chrome por pathname; overrides pontuais só onde o estado é client-side (filtro salvo) ou dinâmico (editar com nome da entidade). + +**Rejeitadas:** C (twins); A (prosa morta no catálogo). + +### Componentes / mudanças + +- **`campaignPageChrome.ts`**: `sectionOnly` nas listas citadas, Quadro, create/edit estáticos; remove prosa das entradas de catálogo afetadas; mantém subtítulos em org/conceitos/perfil/contatos. +- **`MunicipalityListPageChrome.tsx`** (novo): client; `useMunicipalitySavedFilters` + `isSameListHref` (mesmo contrato da sidebar B18); `SetCampaignPageChrome` só quando há match. +- **Edit pages** (`municipios/.../editar`, `atividades/.../editar`): chrome/metadata sem `subtitle`. +- **Migration:** sem migration. +- **Access / Consent:** nenhum. +- **UI:** B — encaixe no chrome B123; sem redesign. + +### Dados → forma + +- Não há dados novos — só orientação espacial no header. + +## Fases verificáveis + +1. **Chrome catálogo + edit** — `campaignPageChrome.ts`, edit pages, testes unitários. +2. **Filtro salvo** — `MunicipalityListPageChrome` na página de lista; teste unitário do match. +3. **Gates** — `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- Reescrever subtítulos de Organizações/Conceitos/Perfil/Contatos. +- Subtítulo para URL filtrada ad hoc (só filtro salvo casado). +- Reintroduzir `formatMunicipalityActiveFiltersSummary` visível na barra. + +## Riscos e mitigação + +- **Acoplamento layout ↔ URL municípios:** `isSameListHref` já é client-safe e usado na sidebar — reusar, não importar `buildMunicipalityListHref` no layout. +- **Regressão E2E filtros salvos:** sidebar + Renomear intactos; só chrome do header muda. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto +- [x] Invariantes AGENTS/engineering-standards +- [x] Testes de domínio previstos (unit chrome + saved-filter match) diff --git a/src/app/(campaign)/campanha/(app)/atividades/[slug]/editar/page.tsx b/src/app/(campaign)/campanha/(app)/atividades/[slug]/editar/page.tsx index 447dea5a..2ce65db2 100644 --- a/src/app/(campaign)/campanha/(app)/atividades/[slug]/editar/page.tsx +++ b/src/app/(campaign)/campanha/(app)/atividades/[slug]/editar/page.tsx @@ -22,12 +22,9 @@ type EditActivityPageProps = { params: Promise<{ slug: string }> } -const activityEditSubtitle = 'Atualize os detalhes da atividade sem alterar o título original.' - export async function generateMetadata({ params }: EditActivityPageProps) { const { slug } = await params - if (!slug) - return campaignPageMetadata({ title: 'Editar atividade', subtitle: activityEditSubtitle }) + if (!slug) return campaignPageMetadata({ title: 'Editar atividade' }) const [user, payload] = await Promise.all([ requireCampaignPageActor({ gate: 'staff', redirectTo: '/campanha/atividades' }), @@ -36,9 +33,9 @@ export async function generateMetadata({ params }: EditActivityPageProps) { try { const view = await getActivityEditPageData(payload, user, slug) - return campaignPageMetadata({ title: `Editar ${view.title}`, subtitle: activityEditSubtitle }) + return campaignPageMetadata({ title: `Editar ${view.title}` }) } catch { - return campaignPageMetadata({ title: 'Editar atividade', subtitle: activityEditSubtitle }) + return campaignPageMetadata({ title: 'Editar atividade' }) } } @@ -63,9 +60,7 @@ export default async function EditActivityPage({ params }: EditActivityPageProps return (
- + } -const municipalityEditSubtitle = - 'A geografia do município é pré-definida e não pode ser alterada. Aqui você edita metas, votos estimados, inteligência e tendência política.' - export async function generateMetadata({ params }: MunicipalityEditPageProps) { const { slug } = await params const payload = await getPayload({ config }) @@ -43,12 +40,9 @@ export async function generateMetadata({ params }: MunicipalityEditPageProps) { try { const context = await resolveAccessibleMunicipalityContext(payload, user, slug) const view = await getMunicipalityDetailViewModel(payload, context, user) - return campaignPageMetadata({ - title: `Editar ${view.name}`, - subtitle: municipalityEditSubtitle, - }) + return campaignPageMetadata({ title: `Editar ${view.name}` }) } catch { - return campaignPageMetadata({ title: 'Editar município', subtitle: municipalityEditSubtitle }) + return campaignPageMetadata({ title: 'Editar município' }) } } @@ -76,9 +70,7 @@ export default async function MunicipalityEditPage({ params }: MunicipalityEditP return ( - +