From 653f04062b677dcc2287dd492273785a672d59b4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 17:56:51 +0000 Subject: [PATCH 1/2] =?UTF-8?q?B102=20=E2=80=94=20fix=20drawer=20search=20?= =?UTF-8?q?crash=20on=20focus/type=20(TooltipProvider=20scope)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lift TooltipProvider in the campaign app layout so it wraps CampaignAppScrollChrome (scroll + quick-actions drawer). Focus→suggest with priority-alta hits mounted CampaignHoverTooltip outside the old provider and crashed the page. Unit pins cover focus→suggest and type→search. Co-authored-by: Francisco Solla --- docs/CHANGELOG-AGENTS.md | 2 + docs/plans/crash-busca-bottom-drawer.md | 11 ++- src/app/(campaign)/campanha/(app)/layout.tsx | 13 ++- .../campaignQuickActionsDrawer.unit.spec.tsx | 94 +++++++++++++++++-- 4 files changed, 109 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index 43a0f53c..f78fc0cc 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**Recently resolved (2026-08-01):** **B102** (Issue #129) — crash na busca do bottom drawer (focus/digitação): `TooltipProvider` do layout `(app)` envolvia só `{children}` e o drawer (irmão do scrollport) renderizava hits `priority:alta` via `CampaignHoverTooltip` sem provider. Fix: provider envolve `CampaignAppScrollChrome`; pins unit focus→suggest e type→search. Plano: [`crash-busca-bottom-drawer.md`](docs/plans/crash-busca-bottom-drawer.md). + **Recently resolved (2026-08-01):** **Pool pin `fast=false`** — `resolvePoolModel` agora envia `fast=false` em todo spawn cuja família Cloud anuncia o param (`composer-2.5`, `grok-4.5`, …). Antes o supervisor pedia só `{ id: "composer-2.5" }`; a API Create-Agent resolve o default da variante (`fast=true`) e o usage dashboard mostrava `composer-2.5-fast` em todos os workers do pool. Skills `model-selection` / `agent-pool` + plano do orquestrador atualizados. **Recently resolved (2026-08-01):** **C8 F1–F2** (Issue #37) — freshness audit confirmou entrega em código desde 2026-07-19: locks advisory em batch (`acquireTextAdvisoryLocks`), bulk import com `.returning()` + leituras drizzle, overview derivando `total` de `totalDocs`, migration `pg_trgm`, guard `contactSearchQuery` (mín. 2 chars). Plano [`escala-dry-pos-c6.md`](docs/plans/escala-dry-pos-c6.md) fechado; follow-ups de filtros/forms permanecem no C9. diff --git a/docs/plans/crash-busca-bottom-drawer.md b/docs/plans/crash-busca-bottom-drawer.md index 240b0472..9195e00f 100644 --- a/docs/plans/crash-busca-bottom-drawer.md +++ b/docs/plans/crash-busca-bottom-drawer.md @@ -1,6 +1,6 @@ # Crash — busca no bottom drawer (focus / digitação) -Status: registrado +Status: em implementação Atualizado em: 2026-08-01 Issue: #129 (file-miss #133) Priority: P0 @@ -9,6 +9,12 @@ Impeccable: B — `CampaignQuickActionsDrawer` + `CampaignGlobalSearchBody` Appetite: ~0,5–1d eng; root-cause + fix + pin focus/type; sem migration Responsável: — +## Freshness audit (2026-08-01) + +- Arquivos citados existem; assinaturas de `useHomeSearch` / `useHomeSearchResultsState` / drawer intactas. +- Sem migration; deps soft B91/B100 done. +- **Root cause confirmada (repro unit):** `TooltipProvider` no layout `(app)` envolvia só `{children}` dentro de `CampaignAppScrollChrome`. O drawer monta como **irmão** do scrollport → fora do provider. Focus → `mode:suggest` → hits `priority:'alta'` → `MunicipalityPriorityIndicator` → `CampaignHoverTooltip` → throw ``Tooltip` must be used within `TooltipProvider`` → página cai. Início não crasha porque o drawer não monta lá e a busca vive sob o provider dos children. Digitar com hits prioritários crashava pelo mesmo motivo; o pin B100 só mudava query com payload vazio (sem flag). + ## Design (Impeccable) Âncoras: `PRODUCT.md` (Feel the action) · B91/B100 drawer search · tema `campaign`. @@ -66,10 +72,11 @@ Diagnóstico preliminar (código em `main`): - **Fix mínimo no caminho focus→suggest→render; não redesenhar snap.** Geometria/gesto → **B105**. **Rejeitado:** reescrever busca só para o drawer. - **File-miss obrigatório** com causa + PR culpado quando conhecido. **Rejeitado:** só Issue de feature sem harvest. - **i18n:** ids existentes; sem copy nova. +- **Fix (as-built):** elevar `TooltipProvider` no layout `(app)` para envolver `CampaignAppScrollChrome` inteiro (scroll + drawer). Harness unit espelha isso. ## Questões em aberto -- **Causa raiz exata antes do claim?** **Opções:** A) fechar no `work-issue` com repro primeiro | B) bipartir plan/exec. **Recomendação:** **A** — appetite cabe; hypothese já estreita. _(assumido)_ +- **Causa raiz exata antes do claim?** **Opções:** A) fechar no `work-issue` com repro primeiro | B) bipartir plan/exec. **Recomendação:** **A** — appetite cabe; hypothese já estreita. _(assumido — fechado no freshness/repro)_ ## Abordagem proposta diff --git a/src/app/(campaign)/campanha/(app)/layout.tsx b/src/app/(campaign)/campanha/(app)/layout.tsx index f0dbcd7e..e23233e9 100644 --- a/src/app/(campaign)/campanha/(app)/layout.tsx +++ b/src/app/(campaign)/campanha/(app)/layout.tsx @@ -86,9 +86,16 @@ export default async function CampaignAppLayout({ children }: { children: React. - - {children} - + {/* + Provider must wrap CampaignAppScrollChrome, not only page children: + the mobile quick-actions drawer (B91/B100) mounts as a sibling of + the scrollport and renders search hits with CampaignHoverTooltip + (priority flag). Nested only around {children} left focus→suggest + without a provider and crashed the page (B102). + */} + + {children} + {biometricEnrollment ? : null} diff --git a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx index 2ff36fa9..1b0a876b 100644 --- a/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx +++ b/tests/unit/campaignQuickActionsDrawer.unit.spec.tsx @@ -10,6 +10,8 @@ import { CampaignQuickActionsSnapProvider, useCampaignQuickActionsSnap, } from '@/components/campaign/shell/CampaignQuickActionsSnapContext' +import { TooltipProvider } from '@/components/ui/tooltip' +import type { HomeSearchSuccessResponse } from '@/lib/campaignHomeSearchHits' import { postCampaignJson } from '@/lib/campaignJsonRequest' import { QUICK_ACTIONS_SCROLL_COLLAPSE_THRESHOLD_PX, @@ -26,9 +28,9 @@ vi.mock('next/navigation', () => ({ usePathname: () => '/campanha/municipios/foo', })) -const idleSuggestPayload = { - status: 'success' as const, - resultKind: 'suggest' as const, +const idleSuggestPayload: HomeSearchSuccessResponse = { + status: 'success', + resultKind: 'suggest', municipalities: [], territories: [], advisors: [], @@ -38,6 +40,26 @@ const idleSuggestPayload = { demands: [], } +/** Priority-alta hit — mounts MunicipalityPriorityIndicator → CampaignHoverTooltip. */ +const suggestWithPriorityPayload: HomeSearchSuccessResponse = { + ...idleSuggestPayload, + municipalities: [ + { + kind: 'municipality', + slug: 'cairu', + name: 'Cairu', + region: 'Baixo Sul', + priority: 'alta', + votePosition2022: null, + }, + ], +} + +const searchWithPriorityPayload: HomeSearchSuccessResponse = { + ...suggestWithPriorityPayload, + resultKind: 'search', +} + const matchMediaMock = vi.fn() beforeEach(() => { @@ -59,11 +81,17 @@ afterEach(() => { vi.unstubAllGlobals() }) +/** + * Mirrors campaign (app) layout: TooltipProvider wraps chrome that includes the + * drawer (B102). Nested only around page children left focus→suggest crashing. + */ const renderQuickActionsChrome = (ui: ReactNode) => render( - - {ui} - , + + + {ui} + + , ) const SnapReadout = () => { @@ -134,6 +162,60 @@ describe('CampaignQuickActionsDrawer (B100)', () => { }) }) + it('focus posts suggest and renders priority hits without crashing (B102)', async () => { + vi.mocked(postCampaignJson).mockResolvedValue({ + ok: true, + payload: suggestWithPriorityPayload, + }) + + renderQuickActionsChrome() + + fireEvent.focus(screen.getByLabelText('Buscar na campanha')) + + await waitFor(() => { + expect(postCampaignJson).toHaveBeenCalledWith( + '/campanha/home-search', + { mode: 'suggest' }, + expect.any(AbortSignal), + ) + }) + + await waitFor(() => { + expect(screen.getByRole('region', { name: 'Sugestões' })).toBeTruthy() + expect(screen.getByText('Cairu')).toBeTruthy() + expect(screen.getByLabelText('Município prioritário')).toBeTruthy() + }) + }) + + it('typing keeps search results with priority hits mounted (B102)', async () => { + vi.mocked(postCampaignJson).mockImplementation(async (_url, body) => { + if (typeof body === 'object' && body !== null && 'mode' in body && body.mode === 'search') { + return { ok: true, payload: searchWithPriorityPayload } + } + return { ok: true, payload: idleSuggestPayload } + }) + + renderQuickActionsChrome() + + const input = screen.getByLabelText('Buscar na campanha') + fireEvent.focus(input) + fireEvent.change(input, { target: { value: 'ca' } }) + + await waitFor(() => { + expect(postCampaignJson).toHaveBeenCalledWith( + '/campanha/home-search', + { mode: 'search', query: 'ca' }, + expect.any(AbortSignal), + ) + }) + + await waitFor(() => { + expect(screen.getByRole('region', { name: 'Municípios' })).toBeTruthy() + expect(screen.getByText('Cairu')).toBeTruthy() + expect(screen.getByLabelText('Município prioritário')).toBeTruthy() + }) + }) + it('toggles between dock and collapsed from the handle', () => { vi.mocked(postCampaignJson).mockResolvedValue({ ok: true, From 0174ae25db8a4db8644f77efc41addbc0091e18e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 1 Aug 2026 17:59:30 +0000 Subject: [PATCH 2/2] =?UTF-8?q?B102=20=E2=80=94=20pin=20layout=20TooltipPr?= =?UTF-8?q?ovider=20wrap=20+=20mark=20plan=20done?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Conventions guard prevents regressing provider under CampaignAppScrollChrome. Co-authored-by: Francisco Solla --- docs/plans/crash-busca-bottom-drawer.md | 6 +++--- tests/unit/codebaseConventions.unit.spec.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/docs/plans/crash-busca-bottom-drawer.md b/docs/plans/crash-busca-bottom-drawer.md index 9195e00f..60942981 100644 --- a/docs/plans/crash-busca-bottom-drawer.md +++ b/docs/plans/crash-busca-bottom-drawer.md @@ -1,8 +1,8 @@ # Crash — busca no bottom drawer (focus / digitação) -Status: em implementação +Status: done Atualizado em: 2026-08-01 -Issue: #129 (file-miss #133) +Issue: #129 (file-miss plan #133 · delivery #135) Priority: P0 Model: cursor-grok-4.5-medium Impeccable: B — `CampaignQuickActionsDrawer` + `CampaignGlobalSearchBody` @@ -76,7 +76,7 @@ Diagnóstico preliminar (código em `main`): ## Questões em aberto -- **Causa raiz exata antes do claim?** **Opções:** A) fechar no `work-issue` com repro primeiro | B) bipartir plan/exec. **Recomendação:** **A** — appetite cabe; hypothese já estreita. _(assumido — fechado no freshness/repro)_ +- _(nenhuma — root cause fechada no repro unit; fix as-built acima)_ ## Abordagem proposta diff --git a/tests/unit/codebaseConventions.unit.spec.ts b/tests/unit/codebaseConventions.unit.spec.ts index 432ccc9a..7789c165 100644 --- a/tests/unit/codebaseConventions.unit.spec.ts +++ b/tests/unit/codebaseConventions.unit.spec.ts @@ -667,3 +667,22 @@ describe('public site metadata global access', () => { expect(offenders, 'import and call resolveSiteMetadata from @/utilities/seo').toEqual([]) }) }) + +describe('campaign TooltipProvider wraps quick-actions chrome (B102)', () => { + // B91/B100 mount the drawer as a sibling of the scrollport inside + // CampaignAppScrollChrome. Nesting TooltipProvider only around {children} + // left focus→suggest priority hits (CampaignHoverTooltip) without a + // provider and crashed the page. + it('lifts TooltipProvider around CampaignAppScrollChrome in the app layout', () => { + const layoutPath = resolve(repoRoot, 'src/app/(campaign)/campanha/(app)/layout.tsx') + expect(existsSync(layoutPath)).toBe(true) + const source = readFileSync(layoutPath, 'utf8') + + expect(source).toMatch( + /]*>\s*\s*<\/TooltipProvider>/, + ) + expect(source).not.toMatch( + /]*>\s*\s*<\/CampaignAppScrollChrome>/, + ) + }) +})