From f0a7758eb3732e825c30cad1d3b175c1051abc52 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:04:36 +0000 Subject: [PATCH 1/6] =?UTF-8?q?B138=20=E2=80=94=20atividades:=20presets=20?= =?UTF-8?q?e=20busca=20na=20omnibox;=20remove=20abas?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Solla --- .../atividades-omnibox-presets-busca-impl.md | 67 ++++++++++++ .../campaign/activity/ActivityFilters.tsx | 100 +++++------------- src/utilities/activityOmnibox.ts | 66 +++++++++++- src/utilities/activityUi.ts | 14 ++- tests/unit/activityUi.unit.spec.ts | 23 +++- tests/unit/listOmniboxB128.unit.spec.ts | 45 ++++++++ 6 files changed, 240 insertions(+), 75 deletions(-) create mode 100644 docs/plans/atividades-omnibox-presets-busca-impl.md diff --git a/docs/plans/atividades-omnibox-presets-busca-impl.md b/docs/plans/atividades-omnibox-presets-busca-impl.md new file mode 100644 index 00000000..827a1da3 --- /dev/null +++ b/docs/plans/atividades-omnibox-presets-busca-impl.md @@ -0,0 +1,67 @@ +# Impl: Atividades — presets (Próximos/…) e busca na omnibox; remover abas (B138) + +Status: aprovado +Atualizado em: 2026-08-02 +Issue: #306 +Intenção: docs/plans/atividades-omnibox-presets-busca.md +Appetite restante: herdado (~0,5–1 dia eng) + +## Leitura da intenção + +- **Outcome:** Uma barra omnibox na lista de atividades — presets de janela (Próximos/Todos/Realizados/Rascunhos) e busca por texto como chips/sugestões; seletor de abas acima some; default Próximos intacto. +- **O que NÃO negociar:** semântica de Próximos (planejado/confirmado + startAt ≥ agora); leader lockdown inalterado; deep-links `?tab=` preservados; tipo/município/status (em Todos) na omnibox. +- **O que reavaliar:** hipótese de mover preset para dentro da omnibox — confirmada; não precisa de mudança no chassis `CampaignListOmnibox`. + +## Abordagem recomendada + +```mermaid +flowchart LR + URL["URL ?tab=&q=&kind=&…"] --> parse["parseActivityListParams"] + parse --> where["buildActivityListWhere"] + parse --> omnibox["activityOmnibox adapter"] + omnibox --> UI["ActivityFilters → CampaignListOmnibox"] +``` + +**Opções consideradas:** +- A) Tab como chip de apresentação (não-URL, como scenario em municípios) +- B) Tab permanece em `?tab=` com chip/sugestões na omnibox +- C) Tab vira filtro de status composto (rejeitado pelo produto) + +**Recomendação:** B — mantém deep-links, reusa `activityUi` existente; só muda superfície. +**Rejeitadas:** A (quebra bookmarks `?tab=realizados`); C (perde modo diário nomeado). + +### Componentes / mudanças + +- **`activityUi.ts`**: adicionar `q` ao estado/URL; `buildActivityListWhere` com OR `title` + `responsible.name`. +- **`activityOmnibox.ts`**: seeds grupo `Janela` (`tab:*`); chip de janela quando `tab !== proximos`; chip `Busca:`; apply/remove/clear para `q` e `tab`. +- **`ActivityFilters.tsx`**: remover `ActivityTabSwitch`; wire `onCommitQuery`; placeholder atualizado. +- **Migration:** sem migration. +- **Access / Consent:** inalterado (staff-only lista). +- **UI:** Impeccable B — espelhar `SupporterFilters` (uma barra, chips, pending boundary existente). + +### Dados → forma + +- Chip de janela mostra `activityTabLabels[tab]` (ex. "Realizados"), não status cru. +- Busca: título + nome do responsável (já no card; precedente `contact.name` em leadership). + +## Fases verificáveis + +1. **Server/URL** — `activityUi` + testes unitários (`q`, where OR). +2. **Omnibox adapter + UI** — `activityOmnibox`, `ActivityFilters`; testes B128 estendidos. +3. **Gates** — `pnpm gate:fast`; push via `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- Saved filters B18; intervalo de datas genérico; mudanças em outras listas. +- Novo arquivo `activityOmnibox.unit.spec.ts` — estender `listOmniboxB128` + `activityUi` (evitar twin de test file). + +## Riscos e mitigação + +- **Tab + status em Todos:** ao mudar tab, limpar `status` se sair de `todos` (mesmo comportamento do antigo `buildTabHref`). +- **`q` + tab:** filtros AND — busca restringe dentro da janela ativa. + +## Aceite de engenharia + +- [x] Aceite de produto da intenção ainda coberto +- [x] Invariantes AGENTS/engineering-standards +- [x] Testes de domínio previstos (unit) onde URL/where mudam diff --git a/src/components/campaign/activity/ActivityFilters.tsx b/src/components/campaign/activity/ActivityFilters.tsx index d2a47f8c..8049065f 100644 --- a/src/components/campaign/activity/ActivityFilters.tsx +++ b/src/components/campaign/activity/ActivityFilters.tsx @@ -1,12 +1,10 @@ 'use client' -import Link from 'next/link' import { useMemo, useState } from 'react' import { CampaignListOmnibox } from '@/components/campaign/shared/CampaignListOmnibox' import type { RelationOption } from '@/components/campaign/shared/RelationMultiSelect' import { useCampaignListFilterNavigation } from '@/components/campaign/shared/useCampaignListFilterNavigation' -import { Button } from '@/components/ui/button' import { applyActivityOmniboxSuggestion, buildActivityOmniboxChips, @@ -16,46 +14,7 @@ import { removeActivityOmniboxChip, type ActivityOmniboxAction, } from '@/utilities/activityOmnibox' -import { - activityTabLabels, - activityTabs, - buildActivityListHref, - buildActivityListSearchParams, - type ActivityListState, - type ActivityTab, -} from '@/utilities/activityUi' - -const buildTabHref = (state: ActivityListState, tab: ActivityTab): string => { - const params = buildActivityListSearchParams({ - page: 1, - tab, - kind: state.kind, - status: tab === 'todos' ? state.status : undefined, - municipality: state.municipality, - }) - const query = params.toString() - return query ? `/campanha/atividades?${query}` : '/campanha/atividades' -} - -const ActivityTabSwitch = ({ state }: { state: ActivityListState }) => ( - -) +import { buildActivityListHref, type ActivityListState } from '@/utilities/activityUi' export const ActivityFilters = ({ state, @@ -116,35 +75,34 @@ export const ActivityFilters = ({ } return ( -
- - -
{ - event.preventDefault() + { + event.preventDefault() + }} + > + { + runAction(applyActivityOmniboxSuggestion({ state, suggestionId })) }} - > - { - runAction(applyActivityOmniboxSuggestion({ state, suggestionId })) - }} - onRemoveChip={(chipId) => { - runAction(removeActivityOmniboxChip({ state, chipId })) - }} - onClearAll={() => { - runAction(clearActivityOmnibox(state)) - }} - /> - -
+ onCommitQuery={(text) => { + runAction(applyActivityOmniboxSuggestion({ state, suggestionId: `q:${text}` })) + }} + onRemoveChip={(chipId) => { + runAction(removeActivityOmniboxChip({ state, chipId })) + }} + onClearAll={() => { + runAction(clearActivityOmnibox(state)) + }} + /> + ) } diff --git a/src/utilities/activityOmnibox.ts b/src/utilities/activityOmnibox.ts index 071c3a57..0aa2c203 100644 --- a/src/utilities/activityOmnibox.ts +++ b/src/utilities/activityOmnibox.ts @@ -1,6 +1,6 @@ /** - * Activity list omnibox adapter (B128). Pure / client-safe. - * Tab preset stays outside the omnibox (product map). + * Activity list omnibox adapter (B128 + B138). Pure / client-safe. + * Window presets and text search live inside the omnibox. */ import { createOmniboxSuggestionSeed, @@ -10,6 +10,8 @@ import { } from '@/lib/campaignListOmnibox' import { activityKindLabels, activityStatusLabels } from '@/lib/schemas/activity' import { + activityTabLabels, + activityTabs, buildActivityListSearchParams, parseActivityListParams, type ActivityListState, @@ -52,6 +54,24 @@ const setExclusiveField = ( return parseStateFromParams(raw) } +const tabKeywords: Record = { + proximos: ['proximos', 'próximos', 'proximo', 'próximo'], + todos: ['todos', 'todas'], + realizados: ['realizados', 'realizado'], + rascunhos: ['rascunhos', 'rascunho'], +} + +const setTab = (state: ActivityListState, tab: ActivityTab): ActivityListState => { + const params = buildActivityListSearchParams(withPageReset({ ...state, tab })) + const raw: Record = Object.fromEntries(params.entries()) + + if (tab !== 'todos') { + delete raw.status + } + + return parseStateFromParams(raw) +} + export const buildActivityOmniboxChips = ({ state, municipalityLabelsById, @@ -61,6 +81,15 @@ export const buildActivityOmniboxChips = ({ }): CampaignListOmniboxChip[] => { const chips: CampaignListOmniboxChip[] = [] + if (state.q) chips.push({ id: 'q', label: chipLabel('Busca', state.q) }) + + if (state.tab !== 'proximos') { + chips.push({ + id: `tab:${state.tab}`, + label: activityTabLabels[state.tab], + }) + } + if (state.kind) { chips.push({ id: `kind:${state.kind}`, @@ -97,6 +126,20 @@ export const buildActivityOmniboxSuggestionSeeds = ({ }) => { const seeds = [] + for (const tabValue of activityTabs) { + seeds.push( + createOmniboxSuggestionSeed( + { + id: `tab:${tabValue}`, + group: 'Janela', + label: activityTabLabels[tabValue], + keywords: ['janela', ...tabKeywords[tabValue]], + }, + { emptyQueryVisible: true }, + ), + ) + } + for (const [value, label] of Object.entries(activityKindLabels)) { seeds.push( createOmniboxSuggestionSeed( @@ -153,6 +196,19 @@ export const applyActivityOmniboxSuggestion = ({ state: ActivityListState suggestionId: string }): ActivityOmniboxAction => { + if (suggestionId.startsWith('q:')) { + const q = suggestionId.slice(2) + return { kind: 'url', state: withPageReset({ ...state, q: q || undefined }) } + } + + if (suggestionId.startsWith('tab:')) { + const value = suggestionId.slice(4) as ActivityTab + if (!activityTabs.includes(value)) return { kind: 'url', state } + const next = + state.tab === value ? setTab(state, 'proximos') : setTab(state, value) + return { kind: 'url', state: next } + } + if (suggestionId.startsWith('kind:')) { const value = suggestionId.slice(5) const next = @@ -190,6 +246,12 @@ export const removeActivityOmniboxChip = ({ state: ActivityListState chipId: string }): ActivityOmniboxAction => { + if (chipId === 'q') return { kind: 'url', state: withPageReset({ ...state, q: undefined }) } + + if (chipId.startsWith('tab:')) { + return { kind: 'url', state: setTab(state, 'proximos') } + } + if (chipId.startsWith('kind:')) { return { kind: 'url', state: setExclusiveField(state, 'kind', undefined) } } diff --git a/src/utilities/activityUi.ts b/src/utilities/activityUi.ts index b8dd55ce..490fcd9e 100644 --- a/src/utilities/activityUi.ts +++ b/src/utilities/activityUi.ts @@ -4,6 +4,7 @@ import { activityKinds, activityStatuses, type ActivityKind } from '@/lib/schema import { buildListHref, firstValue, + normalizedText, resolveListUrl, strictDecimalInteger, type RawSearchParams as CampaignListRawSearchParams, @@ -26,6 +27,7 @@ export const activityTabLabels: Record = { export type ActivityListState = { page: number tab: ActivityTab + q?: string kind?: ActivityKind status?: ActivityStatus municipality?: number @@ -33,7 +35,7 @@ export type ActivityListState = { type RawSearchParams = CampaignListRawSearchParams -const activityListParamNames = ['tab', 'kind', 'status', 'municipality', 'page'] as const +const activityListParamNames = ['q', 'tab', 'kind', 'status', 'municipality', 'page'] as const const activityListParamNameSet = new Set(activityListParamNames) @@ -48,6 +50,7 @@ const isActivityStatus = (value: string | undefined): value is ActivityStatus => export const parseActivityListParams = (params: RawSearchParams): ActivityListState => { const rawPage = strictDecimalInteger(firstValue(params.page)) + const q = normalizedText(firstValue(params.q)) const rawTab = firstValue(params.tab) const tab = isActivityTab(rawTab) ? rawTab : 'proximos' const rawKind = firstValue(params.kind) @@ -59,6 +62,7 @@ export const parseActivityListParams = (params: RawSearchParams): ActivityListSt return { page: rawPage ?? 1, tab, + ...(q ? { q } : {}), ...(kind ? { kind } : {}), ...(status ? { status } : {}), ...(municipality ? { municipality } : {}), @@ -68,6 +72,12 @@ export const parseActivityListParams = (params: RawSearchParams): ActivityListSt export const buildActivityListWhere = (state: ActivityListState, now: Date): Where => { const filters: Where[] = [] + if (state.q) { + filters.push({ + or: [{ title: { contains: state.q } }, { 'responsible.name': { contains: state.q } }], + }) + } + if (state.kind) filters.push({ kind: { equals: state.kind } }) if (state.municipality) filters.push({ municipality: { equals: state.municipality } }) @@ -91,6 +101,7 @@ export const buildActivityListSearchParams = ( ): URLSearchParams => { const canonicalState = parseActivityListParams({ page: String(page), + q: state.q, tab: state.tab, kind: state.kind, status: state.status, @@ -98,6 +109,7 @@ export const buildActivityListSearchParams = ( }) const params = new URLSearchParams() + if (canonicalState.q) params.set('q', canonicalState.q) if (canonicalState.tab !== 'proximos') params.set('tab', canonicalState.tab) if (canonicalState.kind) params.set('kind', canonicalState.kind) if (canonicalState.status) params.set('status', canonicalState.status) diff --git a/tests/unit/activityUi.unit.spec.ts b/tests/unit/activityUi.unit.spec.ts index a1b87609..e5dd86b2 100644 --- a/tests/unit/activityUi.unit.spec.ts +++ b/tests/unit/activityUi.unit.spec.ts @@ -39,6 +39,11 @@ describe('parseActivityListParams', () => { expect(parseActivityListParams({ municipality: '12' }).municipality).toBe(12) expect(parseActivityListParams({ municipality: '0' }).municipality).toBeUndefined() }) + + it('parses and trims q', () => { + expect(parseActivityListParams({ q: ' comício ' }).q).toBe('comício') + expect(parseActivityListParams({ q: ' ' }).q).toBeUndefined() + }) }) describe('buildActivityListWhere (tab → where matrix)', () => { @@ -81,6 +86,19 @@ describe('buildActivityListWhere (tab → where matrix)', () => { ], }) }) + + it('filters by title or responsible name when q is set', () => { + const where = buildActivityListWhere({ page: 1, tab: 'proximos', q: 'Maria' }, NOW) + expect(where).toEqual({ + and: [ + { + or: [{ title: { contains: 'Maria' } }, { 'responsible.name': { contains: 'Maria' } }], + }, + { status: { in: ['planejado', 'confirmado'] } }, + { startAt: { greater_than_equal: NOW.toISOString() } }, + ], + }) + }) }) describe('buildActivityListSearchParams', () => { @@ -95,11 +113,14 @@ describe('buildActivityListSearchParams', () => { const params = buildActivityListSearchParams({ page: 1, tab: 'todos', + q: 'comício', kind: 'caminhada', status: 'cancelado', municipality: 9, }) - expect(params.toString()).toBe('tab=todos&kind=caminhada&status=cancelado&municipality=9') + expect(params.toString()).toBe( + 'q=com%C3%ADcio&tab=todos&kind=caminhada&status=cancelado&municipality=9', + ) }) }) diff --git a/tests/unit/listOmniboxB128.unit.spec.ts b/tests/unit/listOmniboxB128.unit.spec.ts index 836311c5..5f1f7a6f 100644 --- a/tests/unit/listOmniboxB128.unit.spec.ts +++ b/tests/unit/listOmniboxB128.unit.spec.ts @@ -7,7 +7,10 @@ import { import { applyActivityOmniboxSuggestion, buildActivityOmniboxChips, + buildActivityOmniboxSuggestionSeeds, clearActivityOmnibox, + filterActivityOmniboxSuggestions, + removeActivityOmniboxChip, } from '@/utilities/activityOmnibox' import { parseActivityListParams } from '@/utilities/activityUi' import { parseAdvisorListParams } from '@/utilities/advisor/advisorListUrl' @@ -186,6 +189,48 @@ describe('list omnibox adapters (B128)', () => { expect(applied.kind).toBe('url') }) + it('activity applies window preset and search (B138)', () => { + const base = parseActivityListParams({}) + const seeds = buildActivityOmniboxSuggestionSeeds({ tab: base.tab, municipalityOptions: [] }) + const suggestions = filterActivityOmniboxSuggestions(seeds, 'realizados') + expect(suggestions.some((entry) => entry.id === 'tab:realizados')).toBe(true) + + const tabApplied = applyActivityOmniboxSuggestion({ + state: base, + suggestionId: 'tab:realizados', + }) + expect(tabApplied.kind).toBe('url') + if (tabApplied.kind === 'url') expect(tabApplied.state.tab).toBe('realizados') + + const searchApplied = applyActivityOmniboxSuggestion({ + state: parseActivityListParams({ tab: 'todos', status: 'planejado' }), + suggestionId: 'q:comício', + }) + expect(searchApplied.kind).toBe('url') + if (searchApplied.kind === 'url') { + expect(searchApplied.state.q).toBe('comício') + expect(searchApplied.state.status).toBe('planejado') + } + + const leavingTodos = applyActivityOmniboxSuggestion({ + state: parseActivityListParams({ tab: 'todos', status: 'planejado' }), + suggestionId: 'tab:realizados', + }) + if (leavingTodos.kind === 'url') expect(leavingTodos.state.status).toBeUndefined() + + const chips = buildActivityOmniboxChips({ + state: parseActivityListParams({ tab: 'realizados', q: 'Maria' }), + municipalityLabelsById: new Map(), + }) + expect(chips.map((chip) => chip.id)).toEqual(['q', 'tab:realizados']) + + const removedTab = removeActivityOmniboxChip({ + state: parseActivityListParams({ tab: 'realizados' }), + chipId: 'tab:realizados', + }) + if (removedTab.kind === 'url') expect(removedTab.state.tab).toBe('proximos') + }) + it('demand toggles exclusive status, kind and search; preserves activity on clear', () => { const state = parseDemandListParams({ status: 'aberta', From 4ea208523960b35ebb7841d16e19938c307d50af Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:08:06 +0000 Subject: [PATCH 2/6] =?UTF-8?q?B138=20=E2=80=94=20simplify:=20min-length?= =?UTF-8?q?=20e=20normaliza=C3=A7=C3=A3o=20da=20busca?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Francisco Solla --- .../atividades-omnibox-presets-busca-impl.md | 7 +++---- src/utilities/activityOmnibox.ts | 6 ++++-- src/utilities/activityUi.ts | 18 ++++++++++++++---- tests/unit/activityUi.unit.spec.ts | 1 + 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/docs/plans/atividades-omnibox-presets-busca-impl.md b/docs/plans/atividades-omnibox-presets-busca-impl.md index 827a1da3..c6344316 100644 --- a/docs/plans/atividades-omnibox-presets-busca-impl.md +++ b/docs/plans/atividades-omnibox-presets-busca-impl.md @@ -60,8 +60,7 @@ flowchart LR - **Tab + status em Todos:** ao mudar tab, limpar `status` se sair de `todos` (mesmo comportamento do antigo `buildTabHref`). - **`q` + tab:** filtros AND — busca restringe dentro da janela ativa. -## Aceite de engenharia +## Débitos deferidos (simplify B138) -- [x] Aceite de produto da intenção ainda coberto -- [x] Invariantes AGENTS/engineering-standards -- [x] Testes de domínio previstos (unit) onde URL/where mudam +- **DRY municipality/setExclusiveField helpers** (score 3, `defer_trigger`): extrair quando 3º domínio omnibox precisar do mesmo padrão — já ledgerado em B128; não bloqueia B138. +- **Int test `responsible.name` contains** (score 3, `defer_trigger`): where JSON coberto em unit; int espelhando supporter quando houver fixture de activity com responsável. diff --git a/src/utilities/activityOmnibox.ts b/src/utilities/activityOmnibox.ts index 0aa2c203..a7594271 100644 --- a/src/utilities/activityOmnibox.ts +++ b/src/utilities/activityOmnibox.ts @@ -8,6 +8,7 @@ import { type CampaignListOmniboxChip, type CampaignListOmniboxSuggestion, } from '@/lib/campaignListOmnibox' +import { isContactSearchQueryReady, normalizeContactSearchQuery } from '@/lib/contactSearchQuery' import { activityKindLabels, activityStatusLabels } from '@/lib/schemas/activity' import { activityTabLabels, @@ -197,8 +198,9 @@ export const applyActivityOmniboxSuggestion = ({ suggestionId: string }): ActivityOmniboxAction => { if (suggestionId.startsWith('q:')) { - const q = suggestionId.slice(2) - return { kind: 'url', state: withPageReset({ ...state, q: q || undefined }) } + const { trimmed } = normalizeContactSearchQuery(suggestionId.slice(2)) + const q = isContactSearchQueryReady(trimmed) ? trimmed : undefined + return { kind: 'url', state: withPageReset({ ...state, q }) } } if (suggestionId.startsWith('tab:')) { diff --git a/src/utilities/activityUi.ts b/src/utilities/activityUi.ts index 490fcd9e..a42236fa 100644 --- a/src/utilities/activityUi.ts +++ b/src/utilities/activityUi.ts @@ -1,5 +1,6 @@ import type { Where } from 'payload' +import { isContactSearchQueryReady, normalizeContactSearchQuery } from '@/lib/contactSearchQuery' import { activityKinds, activityStatuses, type ActivityKind } from '@/lib/schemas/activity' import { buildListHref, @@ -48,9 +49,15 @@ const isActivityKind = (value: string | undefined): value is ActivityKind => const isActivityStatus = (value: string | undefined): value is ActivityStatus => activityStatuses.includes(value as ActivityStatus) +const isActivityListSearchReady = (raw: string | undefined): string | undefined => { + if (!raw) return undefined + const { trimmed } = normalizeContactSearchQuery(raw) + return isContactSearchQueryReady(trimmed) ? trimmed : undefined +} + export const parseActivityListParams = (params: RawSearchParams): ActivityListState => { const rawPage = strictDecimalInteger(firstValue(params.page)) - const q = normalizedText(firstValue(params.q)) + const q = isActivityListSearchReady(normalizedText(firstValue(params.q))) const rawTab = firstValue(params.tab) const tab = isActivityTab(rawTab) ? rawTab : 'proximos' const rawKind = firstValue(params.kind) @@ -73,9 +80,12 @@ export const buildActivityListWhere = (state: ActivityListState, now: Date): Whe const filters: Where[] = [] if (state.q) { - filters.push({ - or: [{ title: { contains: state.q } }, { 'responsible.name': { contains: state.q } }], - }) + const q = isActivityListSearchReady(state.q) + if (q) { + filters.push({ + or: [{ title: { contains: q } }, { 'responsible.name': { contains: q } }], + }) + } } if (state.kind) filters.push({ kind: { equals: state.kind } }) diff --git a/tests/unit/activityUi.unit.spec.ts b/tests/unit/activityUi.unit.spec.ts index e5dd86b2..e91cab15 100644 --- a/tests/unit/activityUi.unit.spec.ts +++ b/tests/unit/activityUi.unit.spec.ts @@ -43,6 +43,7 @@ describe('parseActivityListParams', () => { it('parses and trims q', () => { expect(parseActivityListParams({ q: ' comício ' }).q).toBe('comício') expect(parseActivityListParams({ q: ' ' }).q).toBeUndefined() + expect(parseActivityListParams({ q: 'a' }).q).toBeUndefined() }) }) From be4de299b11ab959630a2bf804c8f52c0f09d072 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:08:51 +0000 Subject: [PATCH 3/6] chore: format --- docs/plans/atividades-omnibox-presets-busca-impl.md | 1 + src/utilities/activityOmnibox.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/plans/atividades-omnibox-presets-busca-impl.md b/docs/plans/atividades-omnibox-presets-busca-impl.md index c6344316..1acf9343 100644 --- a/docs/plans/atividades-omnibox-presets-busca-impl.md +++ b/docs/plans/atividades-omnibox-presets-busca-impl.md @@ -23,6 +23,7 @@ flowchart LR ``` **Opções consideradas:** + - A) Tab como chip de apresentação (não-URL, como scenario em municípios) - B) Tab permanece em `?tab=` com chip/sugestões na omnibox - C) Tab vira filtro de status composto (rejeitado pelo produto) diff --git a/src/utilities/activityOmnibox.ts b/src/utilities/activityOmnibox.ts index a7594271..daaa1598 100644 --- a/src/utilities/activityOmnibox.ts +++ b/src/utilities/activityOmnibox.ts @@ -206,8 +206,7 @@ export const applyActivityOmniboxSuggestion = ({ if (suggestionId.startsWith('tab:')) { const value = suggestionId.slice(4) as ActivityTab if (!activityTabs.includes(value)) return { kind: 'url', state } - const next = - state.tab === value ? setTab(state, 'proximos') : setTab(state, value) + const next = state.tab === value ? setTab(state, 'proximos') : setTab(state, value) return { kind: 'url', state: next } } From cf18f4084d3c03f493cee27cfb9edbd18c95eae0 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:28:54 +0000 Subject: [PATCH 4/6] chore: remove dead searchOnlyListOmnibox exports (knip) --- src/lib/searchOnlyListOmnibox.ts | 34 +------------------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/src/lib/searchOnlyListOmnibox.ts b/src/lib/searchOnlyListOmnibox.ts index ef35f92b..23db977e 100644 --- a/src/lib/searchOnlyListOmnibox.ts +++ b/src/lib/searchOnlyListOmnibox.ts @@ -2,11 +2,7 @@ * Degenerate omnibox for lists that only expose free-text search (B128: * organizações). Pure / client-safe. */ -import { - filterOmniboxSuggestionSeeds, - type CampaignListOmniboxChip, - type CampaignListOmniboxSuggestion, -} from '@/lib/campaignListOmnibox' +import { type CampaignListOmniboxChip } from '@/lib/campaignListOmnibox' export type SearchOnlyListState = { q?: string @@ -23,9 +19,6 @@ export const buildSearchOnlyOmniboxChips = ( state: SearchOnlyListState, ): CampaignListOmniboxChip[] => (state.q ? [{ id: 'q', label: chipLabel('Busca', state.q) }] : []) -export const buildSearchOnlyOmniboxSuggestions = (query: string): CampaignListOmniboxSuggestion[] => - filterOmniboxSuggestionSeeds([], query) - export const applySearchOnlyOmniboxSuggestion = ({ state, suggestionId, @@ -41,28 +34,3 @@ export const applySearchOnlyOmniboxSuggestion = ( } return { kind: 'url', state } } - -export const removeSearchOnlyOmniboxChip = ({ - state, - chipId, - withPageReset, -}: { - state: T - chipId: string - withPageReset: (next: T) => T -}): SearchOnlyOmniboxAction => { - if (chipId === 'q') { - return { kind: 'url', state: withPageReset({ ...state, q: undefined }) } - } - return { kind: 'url', state } -} - -export const clearSearchOnlyOmnibox = ({ - cleared, -}: { - state: T - cleared: T -}): SearchOnlyOmniboxAction => ({ - kind: 'clear', - state: cleared, -}) From 519721cbfd3341dc4bd92f00b132eb3ff4653d29 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 2 Aug 2026 23:44:51 +0000 Subject: [PATCH 5/6] ci: retrigger checks From 0d124b47cb32d3e2cb95d5e7bd7d2134d2994d4d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 3 Aug 2026 00:01:48 +0000 Subject: [PATCH 6/6] fix: restore searchOnlyListOmnibox exports from main (CI merge) --- src/lib/searchOnlyListOmnibox.ts | 34 ++++++++++++++++++++++++- tests/unit/listOmniboxB128.unit.spec.ts | 18 +++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/lib/searchOnlyListOmnibox.ts b/src/lib/searchOnlyListOmnibox.ts index 23db977e..ef35f92b 100644 --- a/src/lib/searchOnlyListOmnibox.ts +++ b/src/lib/searchOnlyListOmnibox.ts @@ -2,7 +2,11 @@ * Degenerate omnibox for lists that only expose free-text search (B128: * organizações). Pure / client-safe. */ -import { type CampaignListOmniboxChip } from '@/lib/campaignListOmnibox' +import { + filterOmniboxSuggestionSeeds, + type CampaignListOmniboxChip, + type CampaignListOmniboxSuggestion, +} from '@/lib/campaignListOmnibox' export type SearchOnlyListState = { q?: string @@ -19,6 +23,9 @@ export const buildSearchOnlyOmniboxChips = ( state: SearchOnlyListState, ): CampaignListOmniboxChip[] => (state.q ? [{ id: 'q', label: chipLabel('Busca', state.q) }] : []) +export const buildSearchOnlyOmniboxSuggestions = (query: string): CampaignListOmniboxSuggestion[] => + filterOmniboxSuggestionSeeds([], query) + export const applySearchOnlyOmniboxSuggestion = ({ state, suggestionId, @@ -34,3 +41,28 @@ export const applySearchOnlyOmniboxSuggestion = ( } return { kind: 'url', state } } + +export const removeSearchOnlyOmniboxChip = ({ + state, + chipId, + withPageReset, +}: { + state: T + chipId: string + withPageReset: (next: T) => T +}): SearchOnlyOmniboxAction => { + if (chipId === 'q') { + return { kind: 'url', state: withPageReset({ ...state, q: undefined }) } + } + return { kind: 'url', state } +} + +export const clearSearchOnlyOmnibox = ({ + cleared, +}: { + state: T + cleared: T +}): SearchOnlyOmniboxAction => ({ + kind: 'clear', + state: cleared, +}) diff --git a/tests/unit/listOmniboxB128.unit.spec.ts b/tests/unit/listOmniboxB128.unit.spec.ts index 5f1f7a6f..69c4a944 100644 --- a/tests/unit/listOmniboxB128.unit.spec.ts +++ b/tests/unit/listOmniboxB128.unit.spec.ts @@ -3,6 +3,9 @@ import { describe, expect, it } from 'vitest' import { applySearchOnlyOmniboxSuggestion, buildSearchOnlyOmniboxChips, + buildSearchOnlyOmniboxSuggestions, + clearSearchOnlyOmnibox, + removeSearchOnlyOmniboxChip, } from '@/lib/searchOnlyListOmnibox' import { applyActivityOmniboxSuggestion, @@ -309,6 +312,21 @@ describe('list omnibox adapters (B128)', () => { withPageReset: (next) => ({ ...next, page: 1 }), }) expect(applied).toEqual({ kind: 'url', state: { page: 1, q: 'ana' } }) + + expect(buildSearchOnlyOmniboxSuggestions('x')[0]?.id).toBe('q:x') + + const removed = removeSearchOnlyOmniboxChip({ + state: { q: 'sindicato', page: 2 }, + chipId: 'q', + withPageReset: (next) => ({ ...next, page: 1 }), + }) + expect(removed).toEqual({ kind: 'url', state: { page: 1, q: undefined } }) + + const cleared = clearSearchOnlyOmnibox({ + state: { q: 'sindicato' }, + cleared: {}, + }) + expect(cleared).toEqual({ kind: 'clear', state: {} }) }) it('advisor toggles municipality portfolio filter and suggests by name', () => {