Skip to content

feat(crm): Fase 5 — tela cheia estilo WhatsApp, auto-sync de contato …#53

Merged
sejaanomalo-web merged 1 commit into
mainfrom
claude/crm-fase5-conversas-tela-cheia-auto-sync
Jul 11, 2026
Merged

feat(crm): Fase 5 — tela cheia estilo WhatsApp, auto-sync de contato …#53
sejaanomalo-web merged 1 commit into
mainfrom
claude/crm-fase5-conversas-tela-cheia-auto-sync

Conversation

@sejaanomalo-web

@sejaanomalo-web sejaanomalo-web commented Jul 11, 2026

Copy link
Copy Markdown
Owner

…e correção do travamento

Ataca os dois problemas relatados (conversas travando, sem chat pra escrever) e o pedido de uma experiência "nível WhatsApp" dentro do CRM.

Correção do travamento (causa raiz identificada em 2 pontos)

  • listarMensagensDoLead não tinha limite: conversas com muito histórico carregavam TODAS as mensagens de uma vez, travando a página. Agora pagina (60 mensagens por vez, igual WhatsApp Web) com "↑ Carregar mensagens anteriores" no topo da thread — preserva a posição de scroll ao carregar mais, e não perde as páginas já carregadas quando chega mensagem nova (merge em vez de substituir).
  • enviarAudioAction guardava o base64 CRU (até 8MB) direto no banco quando o upload pro Storage falhava — algumas conversas ficavam com várias mensagens de vários MB cada, travando ao abrir. Agora só usa esse fallback pra áudios pequenos (≤300KB); acima disso a mensagem fica sem player mas a entrega no WhatsApp continua normal. Migração 20260713 já limpa o que ficou travado (zera midia_url de áudios gigantes salvos por engano).

Auto-sync de contato (nome/foto) sem precisar clicar

  • Abrir uma conversa sem nome/foto já dispara a busca no WhatsApp sozinho (silencioso, respeita nome_manual). Throttle de 6h evita martelar a Evolution em conversas sem informação disponível.
  • Cron diário novo (/api/crm/sincronizar-contatos) cobre o backlog de leads que ninguém abriu ainda — sem depender de abrir cada conversa uma por uma.
  • Botão manual "↻ Atualizar do WhatsApp" continua disponível, sempre forçando nova tentativa.

Redesign: CRM em tela cheia, estilo WhatsApp Web

  • Seletor no topo (Conversas / Kanban / Calendário) via ?view= — cada modo ocupa a tela inteira, em vez de ficarem empilhados. Compatível com ?lead= (ex: card do Kanban abre em "?view=conversas&lead=X").
  • Conversas: sidebar + thread ocupando praticamente toda a altura da janela.
  • Kanban: preenche a tela inteira; colunas mais altas.

Kanban: etapas personalizáveis

  • "+ Nova etapa" cria uma coluna custom por usuário (nome + cor), sem afetar as 7 etapas padrão compartilhadas. Dono pode excluir a própria (com confirmação inline) — leads voltam pra "sem etapa" em vez de sumir. moverLeadAction valida que a etapa é sua antes de mover um lead pra ela.

Migração 20260713: crm_etapas.usuario_id (etapa custom), crm_leads. perfil_sync_tentado_em (throttle do auto-sync), repara midia_url gigante.

Summary by CodeRabbit

  • New Features

    • Added dedicated Conversations, Kanban, and Calendar views in the CRM.
    • Added custom Kanban stage creation and deletion.
    • Added loading of older conversation messages.
  • Improvements

    • Improved scrolling and message history navigation.
    • Added automatic WhatsApp contact profile synchronization with throttling.
    • Kanban and Calendar links now open the selected conversation directly.
  • Bug Fixes

    • Prevented oversized audio data from slowing conversation loading.

…e correção do travamento

Ataca os dois problemas relatados (conversas travando, sem chat pra
escrever) e o pedido de uma experiência "nível WhatsApp" dentro do CRM.

Correção do travamento (causa raiz identificada em 2 pontos)
- listarMensagensDoLead não tinha limite: conversas com muito histórico
  carregavam TODAS as mensagens de uma vez, travando a página. Agora pagina
  (60 mensagens por vez, igual WhatsApp Web) com "↑ Carregar mensagens
  anteriores" no topo da thread — preserva a posição de scroll ao carregar
  mais, e não perde as páginas já carregadas quando chega mensagem nova
  (merge em vez de substituir).
- enviarAudioAction guardava o base64 CRU (até 8MB) direto no banco quando o
  upload pro Storage falhava — algumas conversas ficavam com várias
  mensagens de vários MB cada, travando ao abrir. Agora só usa esse fallback
  pra áudios pequenos (≤300KB); acima disso a mensagem fica sem player mas a
  entrega no WhatsApp continua normal. Migração 20260713 já limpa o que
  ficou travado (zera midia_url de áudios gigantes salvos por engano).

Auto-sync de contato (nome/foto) sem precisar clicar
- Abrir uma conversa sem nome/foto já dispara a busca no WhatsApp sozinho
  (silencioso, respeita nome_manual). Throttle de 6h evita martelar a
  Evolution em conversas sem informação disponível.
- Cron diário novo (/api/crm/sincronizar-contatos) cobre o backlog de leads
  que ninguém abriu ainda — sem depender de abrir cada conversa uma por uma.
- Botão manual "↻ Atualizar do WhatsApp" continua disponível, sempre forçando
  nova tentativa.

Redesign: CRM em tela cheia, estilo WhatsApp Web
- Seletor no topo (Conversas / Kanban / Calendário) via ?view= — cada modo
  ocupa a tela inteira, em vez de ficarem empilhados. Compatível com ?lead=
  (ex: card do Kanban abre em "?view=conversas&lead=X").
- Conversas: sidebar + thread ocupando praticamente toda a altura da janela.
- Kanban: preenche a tela inteira; colunas mais altas.

Kanban: etapas personalizáveis
- "+ Nova etapa" cria uma coluna custom por usuário (nome + cor), sem afetar
  as 7 etapas padrão compartilhadas. Dono pode excluir a própria (com
  confirmação inline) — leads voltam pra "sem etapa" em vez de sumir.
  moverLeadAction valida que a etapa é sua antes de mover um lead pra ela.

Migração 20260713: crm_etapas.usuario_id (etapa custom), crm_leads.
perfil_sync_tentado_em (throttle do auto-sync), repara midia_url gigante.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
metas-anomalo Ready Ready Preview, Comment Jul 11, 2026 2:38pm

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The CRM dashboard now supports query-driven Conversations, Kanban, and Calendar views. Conversations load paginated history, Kanban supports user-owned stages, and contact profile synchronization gains throttling, scheduled execution, and forced refresh behavior.

Changes

CRM platform enhancements

Layer / File(s) Summary
CRM view navigation
app/dashboard/crm/page.tsx, components/crm/Calendario.tsx, components/crm/Kanban.tsx
The dashboard selects and renders each CRM view through ?view=, while links preserve conversation and lead selection.
Paginated conversation history
lib/crm-leads.ts, lib/crm-mensagens-actions.ts, components/crm/Thread.tsx
Message queries return pages, older messages load through a cursor, scroll position is preserved, and large inline audio fallbacks are disabled.
User-owned Kanban stages
lib/crm-etapas.ts, lib/crm-kanban-actions.ts, components/crm/Kanban.tsx
Stages are filtered by ownership, protected during lead moves, and can be created or deleted through Kanban controls.
Throttled contact synchronization
lib/crm-leads-actions.ts, app/api/crm/sincronizar-contatos/route.ts, supabase/migrations/*, vercel.json, components/crm/Thread.tsx
Profile synchronization supports throttling and forced refreshes, with a scheduled route and database fields for attempt tracking and media cleanup.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CRMPage
  participant Thread
  participant MessageAction
  participant Supabase
  CRMPage->>Supabase: Load initial message page
  CRMPage->>Thread: Pass mensagensIniciais and temMaisAntigasInicial
  Thread->>MessageAction: Request older messages with cursor
  MessageAction->>Supabase: Query earlier crm_mensagens
  Supabase-->>MessageAction: Return paginated messages
  MessageAction-->>Thread: Return messages and temMaisAntigas
  Thread->>Thread: Merge messages and preserve scroll position
Loading
sequenceDiagram
  participant VercelCron
  participant SyncRoute
  participant Supabase
  participant Evolution
  VercelCron->>SyncRoute: Invoke scheduled contact synchronization
  SyncRoute->>Supabase: Query eligible leads and CRM instances
  SyncRoute->>Evolution: Fetch contact profile
  Evolution-->>SyncRoute: Return profile fields
  SyncRoute->>Supabase: Update lead profile and attempt timestamp
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main CRM overhaul, including the full-screen WhatsApp-style UI and contact auto-sync work.
Docstring Coverage ✅ Passed Docstring coverage is 83.33% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/crm-fase5-conversas-tela-cheia-auto-sync

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/crm-leads-actions.ts (1)

101-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

achouAlgo can report success when nothing was actually written.

achouAlgo checks perfil.nome || perfil.sobre || perfil.foto, but patch.nome is only set when !lead.nome_manual. If Evolution returns only a nome for a manually-named lead (no sobre/foto), patch ends up with just timestamp fields, yet achouAlgo is still true and the action returns { ok: true } — misleading the caller (and triggering an unnecessary router.refresh()) into thinking something changed.

Suggested fix
   const { error } = await db
     .from("crm_leads")
     .update(patch)
     .eq("id", leadId)
     .eq("usuario_id", usuario.id)
   if (error) return { ok: false, erro: error.message }

   revalidatePath("/dashboard/crm")
-  const achouAlgo = Boolean(perfil.nome || perfil.sobre || perfil.foto)
+  const achouAlgo = Boolean(patch.nome || patch.sobre || patch.foto_url)
   if (!achouAlgo) return { ok: false, erro: "Nada novo encontrado para este contato." }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crm-leads-actions.ts` around lines 101 - 119, Update the success check in
the action containing the patch construction so it reflects fields actually
written, not merely values returned in perfil. Base achouAlgo on whether patch
includes a meaningful CRM field (nome, sobre, or foto_url), excluding the
timestamp fields, while preserving the existing no-change error response and
revalidation behavior.
🧹 Nitpick comments (1)
supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql (1)

27-30: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Unbatched full-table UPDATE on crm_mensagens.

This scans the entire crm_mensagens table (no index on midia_url/length()) and updates every matching row in one statement. If run in the same transaction as the index builds above, it extends the write-lock window on crm_leads until commit. For a large/active messages table, consider batching (e.g., by id ranges or a LIMIT-based loop) to reduce lock/transaction duration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql`
around lines 27 - 30, Replace the unbatched full-table UPDATE in the migration
with a bounded batching approach using the table’s existing primary-key symbol,
processing matching crm_mensagens rows in small batches until none remain.
Preserve the midia_url filter and null assignment while minimizing lock and
transaction duration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/crm/sincronizar-contatos/route.ts`:
- Around line 9-14: Reduce the batch size constant LOTE and add a wall-clock
budget to the sequential contact-processing loop so buscarPerfilContatoEvolution
calls cannot exceed maxDuration. Before each iteration, stop processing when the
elapsed time approaches the 60-second limit, while preserving updates for
contacts already completed.

In `@lib/crm-mensagens-actions.ts`:
- Around line 21-44: Update carregarMensagensAntigasAction to use the same
null-safe ordering as the thread, falling back to created_at when wa_timestamp
is null, and implement a tie-broken cursor that includes messages sharing the
boundary timestamp. Ensure rows with null wa_timestamp remain reachable while
preserving descending pagination and the existing limit/result behavior.

In `@supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql`:
- Around line 27-30: Align the cleanup predicate in the crm_mensagens migration
with the LIMITE_FALLBACK_INLINE cap used by the inline audio fallback, replacing
the 200000-character threshold with the same effective limit. Preserve the
existing data-URL filter and nullification behavior.
- Around line 9-19: Update the index creation statements for
crm_leads_perfil_sync_pendente_idx and crm_etapas_usuario_idx to use concurrent
index creation, preserving their existing columns and partial predicate. Ensure
the migration remains compatible with the transaction requirements of concurrent
index creation.

---

Outside diff comments:
In `@lib/crm-leads-actions.ts`:
- Around line 101-119: Update the success check in the action containing the
patch construction so it reflects fields actually written, not merely values
returned in perfil. Base achouAlgo on whether patch includes a meaningful CRM
field (nome, sobre, or foto_url), excluding the timestamp fields, while
preserving the existing no-change error response and revalidation behavior.

---

Nitpick comments:
In `@supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql`:
- Around line 27-30: Replace the unbatched full-table UPDATE in the migration
with a bounded batching approach using the table’s existing primary-key symbol,
processing matching crm_mensagens rows in small batches until none remain.
Preserve the midia_url filter and null assignment while minimizing lock and
transaction duration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 03b0e8c4-254b-4f15-8f00-8583bea8dea0

📥 Commits

Reviewing files that changed from the base of the PR and between a5a652e and bcf8491.

📒 Files selected for processing (13)
  • app/api/crm/sincronizar-contatos/route.ts
  • app/dashboard/crm/page.tsx
  • components/crm/Calendario.tsx
  • components/crm/Kanban.tsx
  • components/crm/PainelKanbanCalendario.tsx
  • components/crm/Thread.tsx
  • lib/crm-etapas.ts
  • lib/crm-kanban-actions.ts
  • lib/crm-leads-actions.ts
  • lib/crm-leads.ts
  • lib/crm-mensagens-actions.ts
  • supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql
  • vercel.json
💤 Files with no reviewable changes (1)
  • components/crm/PainelKanbanCalendario.tsx

Comment on lines +9 to +14
export const maxDuration = 60

// Batelada por execução — folgado o bastante pra não estourar maxDuration=60s
// (cada chamada à Evolution tem timeout de 12s), mas limitado pra não
// martelar a API em contas com muitos leads sem foto/nome de uma vez.
const LOTE = 25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect target file with line numbers
sed -n '1,260p' app/api/crm/sincronizar-contatos/route.ts | nl -ba

echo
echo '--- search for related symbols ---'
rg -n "buscarPerfilContatoEvolution|maxDuration|LOTE|ORCAMENTO_MS|Date\.now\(\)|AbortController|timeout" app/api/crm -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
path = Path('app/api/crm/sincronizar-contatos/route.ts')
for i, line in enumerate(path.read_text().splitlines(), 1):
    print(f"{i:4d}: {line}")
PY

echo
echo '--- search for related symbols ---'
rg -n "buscarPerfilContatoEvolution|maxDuration|LOTE|ORCAMENTO_MS|Date\.now\(\)|AbortController|timeout" app/api/crm -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 5329


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find the evolution helper implementation
fd -a "evolution.*" .
echo
rg -n "function buscarPerfilContatoEvolution|const buscarPerfilContatoEvolution|buscarPerfilContatoEvolution|AbortController|timeout|12000|12s|12_000" lib src app -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 1762


Batch can still exceed maxDuration=60

buscarPerfilContatoEvolution uses a 12s timeout, and this loop runs sequentially for up to 25 leads. In the worst case, the batch can run for several minutes and get cut off mid-run, leaving partial updates.

Lower LOTE and/or add a wall-clock budget before entering the next iteration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/crm/sincronizar-contatos/route.ts` around lines 9 - 14, Reduce the
batch size constant LOTE and add a wall-clock budget to the sequential
contact-processing loop so buscarPerfilContatoEvolution calls cannot exceed
maxDuration. Before each iteration, stop processing when the elapsed time
approaches the 60-second limit, while preserving updates for contacts already
completed.

Comment on lines +21 to +44
export async function carregarMensagensAntigasAction(
leadId: string,
antesIso: string,
limite = MENSAGENS_POR_PAGINA
): Promise<PaginaMensagens> {
const usuario = await getUsuarioAtual()
if (!usuario) return { mensagens: [], temMaisAntigas: false }
const db = getSupabaseAdmin()
if (!db) return { mensagens: [], temMaisAntigas: false }
const { data, error } = await db
.from("crm_mensagens")
.select("*")
.eq("lead_id", leadId)
.eq("usuario_id", usuario.id)
.lt("wa_timestamp", antesIso)
.order("wa_timestamp", { ascending: false, nullsFirst: false })
.limit(limite)
if (error) {
console.error("[crm_mensagens] carregar antigas error", error.message)
return { mensagens: [], temMaisAntigas: false }
}
const linhas = ((data ?? []) as CrmMensagemRow[]).reverse()
return { mensagens: linhas, temMaisAntigas: linhas.length === limite }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant file with line numbers
sed -n '1,220p' lib/crm-mensagens-actions.ts | nl -ba

# Find the caller and cursor construction
rg -n "carregarMensagensAntigasAction|maisAntiga\.wa_timestamp|created_at" components src lib -S

# Inspect the caller file around the pagination logic
caller=$(rg -l "carregarMensagensAntigasAction|maisAntiga\.wa_timestamp|created_at" components src lib -S | head -n 1 || true)
if [ -n "${caller:-}" ]; then
  echo "=== CALLER: $caller ==="
  sed -n '1,260p' "$caller" | nl -ba
fi

# Locate the row type to confirm nullability
rg -n "type CrmMensagemRow|interface CrmMensagemRow|wa_timestamp" lib src components -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== lib/crm-mensagens-actions.ts ==='
awk '{printf "%4d  %s\n", NR, $0}' lib/crm-mensagens-actions.ts | sed -n '1,220p'

printf '\n%s\n' '=== search: caller + cursor construction ==='
rg -n "carregarMensagensAntigasAction|maisAntiga\.wa_timestamp|created_at" components src lib -S

printf '\n%s\n' '=== CrmMensagemRow / wa_timestamp ==='
rg -n "type CrmMensagemRow|interface CrmMensagemRow|wa_timestamp" components src lib -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 13597


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== lib/crm-leads.ts (row types) ==='
awk '{printf "%4d  %s\n", NR, $0}' lib/crm-leads.ts | sed -n '1,180p'

printf '\n%s\n' '=== components/crm/Thread.tsx (pagination + sort) ==='
awk '{printf "%4d  %s\n", NR, $0}' components/crm/Thread.tsx | sed -n '1,220p'

printf '\n%s\n' '=== crm_mensagens writes / wa_timestamp assignments ==='
rg -n "from\\(\"crm_mensagens\"\\)|wa_timestamp|created_at" lib components -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 20178


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== lib/crm-inbound.ts (wa_timestamp source) ==='
awk '{printf "%4d  %s\n", NR, $0}' lib/crm-inbound.ts | sed -n '450,520p'

printf '\n%s\n' '=== exact query shape in lib/crm-mensagens-actions.ts ==='
awk '{printf "%4d  %s\n", NR, $0}' lib/crm-mensagens-actions.ts | sed -n '21,45p'

Repository: sejaanomalo-web/metas-anomalo

Length of output: 4121


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== waTs provenance in lib/crm-inbound.ts ==='
rg -n "waTs|wa_timestamp" lib/crm-inbound.ts -n -S
awk '{printf "%4d  %s\n", NR, $0}' lib/crm-inbound.ts | sed -n '380,500p'

printf '\n%s\n' '=== any explicit null writes to wa_timestamp ==='
rg -n "wa_timestamp:\s*null|wa_timestamp\s*=\s*null|wa_timestamp.*null" lib components app -S

printf '\n%s\n' '=== all crm_mensagens inserts/upserts/updates ==='
rg -n "from\\(\"crm_mensagens\"\\)|upsert\\(|insert\\(|update\\(" lib components app -S

Repository: sejaanomalo-web/metas-anomalo

Length of output: 13629


Strict wa_timestamp paging can drop history. .lt("wa_timestamp", antesIso) makes rows with wa_timestamp = null unreachable, and using only wa_timestamp as the cursor can skip messages that share the boundary timestamp. The thread already falls back to created_at when sorting, so the query needs the same null-safe, tie-broken cursor.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crm-mensagens-actions.ts` around lines 21 - 44, Update
carregarMensagensAntigasAction to use the same null-safe ordering as the thread,
falling back to created_at when wa_timestamp is null, and implement a tie-broken
cursor that includes messages sharing the boundary timestamp. Ensure rows with
null wa_timestamp remain reachable while preserving descending pagination and
the existing limit/result behavior.

Comment on lines +9 to +19
alter table public.crm_etapas add column if not exists usuario_id uuid;
create index if not exists crm_etapas_usuario_idx on public.crm_etapas (usuario_id);

-- perfil_sync_tentado_em: carimbo da última tentativa de buscar nome/foto/
-- recado no WhatsApp (manual ou automática) — evita bater no Evolution a
-- cada abertura de conversa/rodada de cron pra contato sem informação
-- disponível (throttle, não trava o dado já sincronizado).
alter table public.crm_leads add column if not exists perfil_sync_tentado_em timestamptz;
create index if not exists crm_leads_perfil_sync_pendente_idx
on public.crm_leads (perfil_sync_tentado_em)
where nome_manual = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the migration and surrounding migration runner/config to determine
# whether this file is executed inside a transaction and whether CONCURRENTLY is allowed.
git ls-files | rg '(^|/)(supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle\.sql|supabase/|migrations/|db/|sql/|package\.json|README\.md)$'

printf '\n--- migration file (lines 1-120) ---\n'
cat -n supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql | sed -n '1,120p'

printf '\n--- search for migration runner / transaction settings ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' \
  'transaction|BEGIN;|COMMIT;|CREATE INDEX CONCURRENTLY|migrations.*transaction|supabase db push|db push|seed|migration' \
  . | sed -n '1,240p'

Repository: sejaanomalo-web/metas-anomalo

Length of output: 3817


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- package.json ---\n'
cat -n package.json | sed -n '1,220p'

printf '\n--- README.md (migration-related sections) ---\n'
rg -n -A4 -B4 'supabase|migration|apply_migration|transaction|concurrently' README.md supabase docs | sed -n '1,260p'

printf '\n--- search for apply_migration definition/usages ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' 'apply_migration' . | sed -n '1,240p'

Repository: sejaanomalo-web/metas-anomalo

Length of output: 13975


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- README.md (migration-related sections) ---\n'
rg -n -A4 -B4 'supabase|migration|apply_migration|transaction|concurrently' README.md supabase docs | sed -n '1,260p'

printf '\n--- search for apply_migration definition/usages ---\n'
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' 'apply_migration' . | sed -n '1,240p'

Repository: sejaanomalo-web/metas-anomalo

Length of output: 12786


🌐 Web query:

Supabase CLI migration files transaction default CREATE INDEX CONCURRENTLY migration transaction block docs

💡 Result:

The Supabase CLI manages migrations by running them inside a transaction block by default [1]. However, certain PostgreSQL operations, such as CREATE INDEX CONCURRENTLY, cannot be executed within a transaction block [2][3][4]. Recent updates to the Supabase CLI (as of June 2026) have introduced intelligent handling for these incompatible statements [5][6]. When the CLI detects pipeline-incompatible statements—specifically CREATE INDEX CONCURRENTLY, VACUUM, REINDEX CONCURRENTLY, ALTER SYSTEM, and CLUSTER—it automatically adjusts its execution strategy [5][6]: 1. It flushes any open transaction batch. 2. It executes the incompatible statement as a standalone command outside of any transaction block. 3. It resumes batching subsequent statements within a transaction. This mechanism ensures that migrations containing these operations can proceed without causing SQLSTATE 25001 errors, while still maintaining the atomicity of the surrounding migration steps [5][6]. If you are encountering issues in older versions of the CLI or specific edge cases, it is recommended to ensure your CLI is updated to the latest version to leverage this native support [5][6]. Alternatively, for complex migrations, some developers prefer to execute these operations via a separate, non-transactional script or psql command to have full control over the execution environment [4].

Citations:


Make the crm_leads index concurrent

create index on public.crm_leads will block writes while it builds, and this table is on a hot webhook path. Switch crm_leads_perfil_sync_pendente_idx to CREATE INDEX CONCURRENTLY; crm_etapas_usuario_idx can follow the same pattern for consistency.

🧰 Tools
🪛 Squawk (2.59.0)

[warning] 10-10: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)


[warning] 17-19: During normal index creation, table updates are blocked, but reads are still allowed. Use concurrently to avoid blocking writes.

(require-concurrent-index-creation)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql`
around lines 9 - 19, Update the index creation statements for
crm_leads_perfil_sync_pendente_idx and crm_etapas_usuario_idx to use concurrent
index creation, preserving their existing columns and partial predicate. Ensure
the migration remains compatible with the transaction requirements of concurrent
index creation.

Source: Linters/SAST tools

Comment on lines +27 to +30
update public.crm_mensagens
set midia_url = null
where midia_url like 'data:%'
and length(midia_url) > 200000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Cleanup threshold doesn't match the ongoing insert cap.

This one-time cleanup wipes midia_url above 200000 chars, but lib/crm-mensagens-actions.ts's LIMITE_FALLBACK_INLINE (300 * 1024 ≈ 307,200 bytes) allows new inline audio fallbacks up to a larger size going forward. Messages inserted in the 200KB–300KB range after this migration runs won't be caught by any future cleanup, contradicting the stated intent of capping oversized inline media.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@supabase/migrations/20260713_crm_fase5_etapas_custom_sync_throttle.sql`
around lines 27 - 30, Align the cleanup predicate in the crm_mensagens migration
with the LIMITE_FALLBACK_INLINE cap used by the inline audio fallback, replacing
the 200000-character threshold with the same effective limit. Preserve the
existing data-URL filter and nullification behavior.

@sejaanomalo-web
sejaanomalo-web merged commit e800d9f into main Jul 11, 2026
3 checks passed
@sejaanomalo-web
sejaanomalo-web deleted the claude/crm-fase5-conversas-tela-cheia-auto-sync branch July 11, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant