From feb8e6cab8aaae3a7fdc49a69bad9b260ac6d47f Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 23 Aug 2026 12:56:49 -0300 Subject: [PATCH] =?UTF-8?q?docs(E1):=20triage=20de=20d=C3=A9bitos=20aplica?= =?UTF-8?q?da=20=E2=80=94=20refs=20.spec.tsx=20residuais=20no=20impl=20pla?= =?UTF-8?q?n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/extrair-hook-busca-assincrona-debounce-impl.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plans/extrair-hook-busca-assincrona-debounce-impl.md b/docs/plans/extrair-hook-busca-assincrona-debounce-impl.md index 74d968f0..0e137762 100644 --- a/docs/plans/extrair-hook-busca-assincrona-debounce-impl.md +++ b/docs/plans/extrair-hook-busca-assincrona-debounce-impl.md @@ -66,7 +66,7 @@ Alternativas rejeitadas: B — a mecânica requestId/gate/debounce é a parte ma - **`ResponsibleMultiSelect`** (`src/components/campaign/shared/ResponsibleMultiSelect.tsx`): remove L67-76 e o efeito L78-107; hook com `isQueryReady={isContactSearchQueryReady}`. Mantém: `groups` por `typeLabel`, `atCapacity`, mensagem "Digite ao menos dois caracteres…" (o import de `isContactSearchQueryReady` permanece — usado no render, L224), hidden JSON, chips com `typeLabel`. - **`AsyncSearchCombobox`** (`src/components/campaign/shared/AsyncSearchCombobox.tsx`): remove estados/refs L49-60 e o efeito de busca L66-95; hook com `isQueryReady` (a prop continua no componente — usada no render do vazio, L189). **O efeito sync `selected←value` (L62-64) FICA no componente** (é contrato do combobox, não da busca). Mantém: `pinnedOptions`/`pinnedGroupHeading`, grupos "Seleção"/"Selecionado"/"Resultados", `choose()`, `emptyOptionLabel`, `queryTooShortMessage`. - **Consumers de `AsyncSearchCombobox`** (`DemandFields.tsx:85`, `ContactCombobox.tsx:38`, `ActivityTaskFields.tsx:98`): intocados (o gate é resolvido dentro do combobox). -- **`tests/unit/useAsyncSearchOptions.unit.spec.tsx`** (novo): ~5 `it` — debounce só dispara após `ASYNC_SEARCH_DEBOUNCE_MS`; query rápida em sequência cancela o timer anterior (só a última busca); gate não-pronto → reset sem chamar `search`; resposta fora de ordem descartada (requestId) e `catch` → `failed`, `finally` → `loading=false`; `open=false` não busca. +- **`tests/unit/useAsyncSearchOptions.unit.spec.ts`** (novo): ~5 `it` — debounce só dispara após `ASYNC_SEARCH_DEBOUNCE_MS`; query rápida em sequência cancela o timer anterior (só a última busca); gate não-pronto → reset sem chamar `search`; resposta fora de ordem descartada (requestId) e `catch` → `failed`, `finally` → `loading=false`; `open=false` não busca. - **Migration:** nenhuma (refactor client, sem schema). **Access / Consent:** nenhum (server actions intocadas; sem escrita nova, PII ou chave). **UI:** Impeccable A — refactor estrutural sem superfície nova. ### Dados → forma @@ -75,7 +75,7 @@ N/A — refactor de estado de UI; nenhuma métrica, série, ranking ou mapa novo ## Fases verificáveis -1. **Tracer / schema+server** — nada de schema/server (as server actions já são props dos 3 componentes e ficam intocadas; sem migration; sem access/Consent). O tracer é o próprio hook: criar `src/hooks/useAsyncSearchOptions.ts` + `tests/unit/useAsyncSearchOptions.unit.spec.tsx` e rodar `pnpm gate:fast` — a mecânica requestId/debounce/gate fica provada isolada (renderHook + fake timers) antes de tocar os 3 componentes. +1. **Tracer / schema+server** — nada de schema/server (as server actions já são props dos 3 componentes e ficam intocadas; sem migration; sem access/Consent). O tracer é o próprio hook: criar `src/hooks/useAsyncSearchOptions.ts` + `tests/unit/useAsyncSearchOptions.unit.spec.ts` e rodar `pnpm gate:fast` — a mecânica requestId/debounce/gate fica provada isolada (renderHook + fake timers) antes de tocar os 3 componentes. 2. **UI** — migrar os 3 componentes (ordem: `AsyncSearchCombobox` e `ResponsibleMultiSelect` em `shared/` primeiro, depois `DemandResponsibleMultiSelect` em `demand/`), mantendo em cada um só o que é seu (D4); registrar o hook com seu type público `useAsyncSearchOptions` (assinatura + `UseAsyncSearchOptionsResult` exportados — sem pin a atualizar: `src/hooks/` não é pinado; knip valida o consumo). Rodar os e2e afetados da superfície antes do push: `pnpm test:e2e:affected` (activity + demand). 3. **Gates** — `pnpm gate:fast`; `pnpm check:cycles` (hook é folha — sem ciclo), `pnpm format:check`, `pnpm exec knip`, `pnpm test` (unit + int — incl. `campaignDemandWorkflow.int.spec.ts`, que cobre o record de responsáveis C143), `pnpm build`; push via `pnpm push`.