Skip to content

feat: multi-tab chat + repo integrations + V tono consistente + Clerk webhook - #72

Open
turbillon50 wants to merge 3 commits into
mainfrom
claude/improve-chat-ui-2IIUT
Open

feat: multi-tab chat + repo integrations + V tono consistente + Clerk webhook#72
turbillon50 wants to merge 3 commits into
mainfrom
claude/improve-chat-ui-2IIUT

Conversation

@turbillon50

Copy link
Copy Markdown
Owner

Resumen (4 fases en un PR)

Fase 2 — V personalidad consistente cross-scope

Instrucción ABSOLUTA en el system prompt: el scope (general vs proyecto) cambia QUÉ contexto carga, NUNCA CÓMO habla. Luis es el operador único — cercano, directo, sin formalidades.

Fase 3 — /app/repovision: tus repos + integraciones

Nuevo endpoint GET /api/repos que cross-referencea repos de GitHub con la tabla projects y devuelve cada repo enriquecido con: tracked, vercel_url, dominio, last_audit_at, etc.

UI rehecha en /app/repovision:

  • Stats: total / tracked / con deploy / archivados
  • Filtros: activos / tracked / untracked / con deploy / archivados
  • Búsqueda por nombre, lenguaje, descripción
  • Card por repo con badges V / Vercel / Dominio / Archivo
  • "Preguntar a V" link directo con contexto del repo

Fase 4 — Multi-tab chat

Reemplazado el dropdown de scope con TABS horizontales:

  • Cada tab = un scope, persiste en vforge_chat_open_scopes
  • "+" abre dropdown para añadir otro proyecto
  • "X" para cerrar tab
  • Switch instantáneo, cada tab mantiene su session_id propio
  • Reset button para nueva sesión en el scope activo

Fase 1B — Clerk webhook

Nuevo POST /api/auth/webhook (verifica firma con svix) que sincroniza user.created / user.updated / user.deleted a nuestra tabla users. Soft-delete preserva FKs.

Config requerida en Vercel:

  • CLERK_WEBHOOK_SECRET = signing secret de Clerk Dashboard → Webhooks
  • Endpoint en Clerk: https://<dominio>/api/auth/webhook
  • Eventos: user.created, user.updated, user.deleted

Cambios técnicos

  • 8 archivos modificados, 818 inserciones / 132 eliminaciones
  • Nueva dep: svix ^1.94 (firma webhooks)
  • SW bump v7-multichat
  • Build verde, typecheck verde

Lo que NO toqué (intencional)

https://claude.ai/code/session_01ViBLo5sQLZ9ZvSKZyqcPHm


Generated by Claude Code

…nsistente + Clerk webhook

4 cambios pedidos por Luis, todos en este push:

## 1. V personalidad consistente cross-scope (Fase 2)
Agregada instrucción ABSOLUTA en buildSystemPrompt explicando que el
scope (general vs proyecto) cambia QUÉ contexto carga, NUNCA CÓMO
habla. Luis es el operador único: cercano, directo, sin formalidades.
Si V se pone profesional en un chat y casual en otro, es BUG.
lib/forge/system-prompt.ts:202-216

## 2. /app/repovision: repos + integraciones (Fase 3)
Nuevo endpoint GET /api/repos que llama listAllUserRepos() y
cross-referencea con la tabla projects para enriquecer cada repo con:
- tracked (¿está en V?)
- projectCategory / projectStatus
- vercel_url + dominio
- last_audit_at

UI completamente rehecha (repovision/page.tsx):
- 4 stats (total / tracked / con deploy / archivados)
- Filtros (activos / tracked / untracked / con deploy / archivados)
- Búsqueda por nombre, lenguaje, descripción
- Card por repo con badges de integración (V, Vercel, Dominio, Archivo)
- Privacidad visible (lock/unlock)
- "Preguntar a V" link directo al chat con el contexto del repo

## 3. Multi-tab chat (Fase 4)
Reemplazado el dropdown de scope con TABS horizontales:
- Cada tab = un scope (general o proyecto), persiste en localStorage
- "+" abre dropdown para añadir otro proyecto como tab adicional
- "X" en cada tab para cerrar (excepto el último general)
- Switch entre tabs es instantáneo, cada tab mantiene su sesión propia
  (ya existía session_id per scope en el backend)
- Reset button (antes "Plus") inicia nueva sesión en el scope activo
- Mobile: scroll horizontal en la barra de tabs si hay muchos
- Persistencia: vforge_chat_open_scopes en localStorage

## 4. Clerk webhook para auth real (Fase 1B)
Nuevo endpoint POST /api/auth/webhook que recibe user.created /
user.updated / user.deleted de Clerk, verifica firma con svix, y
sincroniza la fila en users. Sin esto los signups de Clerk no
aterrizan en nuestra DB.

Config requerida en Vercel:
- CLERK_WEBHOOK_SECRET (de Clerk dashboard → Webhooks)
- Endpoint: https://<dominio>/api/auth/webhook
- Eventos: user.created, user.updated, user.deleted

Soft-delete en user.deleted para no romper foreign keys de
audit_events / conversations.

## Bonus
- SW bump v7-multichat para invalidar cache vieja
- Dependencia nueva: svix ^1.94 (verificación de firma webhook)
@vercel

vercel Bot commented May 20, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
vforge Ready Ready Preview, Comment May 20, 2026 5:13am

Request Review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd7c7859af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/api/repos/route.ts Outdated
Comment on lines +55 to +59
* No requiere auth porque la sesión es single-user (operator_luis).
* Cuando entre Clerk multi-tenant, esto pasará a leer el token de
* GitHub del usuario en sesión.
*/
export async function GET() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Require operator auth for repository inventory endpoint

Protect this route with requireOperatorAuth before returning data, because it currently exposes the operator’s GitHub repository list (including private repo metadata) plus internal integration details (tracked, vercel_url, domain, etc.) to any unauthenticated caller. I checked the current middleware (middleware.ts) and it is pass-through (no auth.protect()), so this endpoint is publicly reachable in deployed environments unless guarded at the route level.

Useful? React with 👍 / 👎.


<div className="mt-3 flex items-center justify-between gap-2 border-t border-app pt-3">
<Link
href={`/app/chat?project=${i.projectId ?? ""}&q=${encodeURIComponent(`cuéntame de ${r.name}`)}`}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wire chat deep-link params into chat initialization

Make the chat page consume the project/q query params, because this new link advertises “Preguntar a V” with repo context but currently just navigates to /app/chat without applying those parameters. As implemented, users clicking from RepoVision do not switch to the repo scope or get the suggested prompt prefilled, so the CTA behavior is broken relative to the intended flow.

Useful? React with 👍 / 👎.

…k params

Codex catches:

## P1 (security): /api/repos exponía repos privados sin auth
El endpoint devolvía la lista completa de repos del operador (incluyendo
privados) + metadata de integraciones (vercel_url, dominios, tracked)
a CUALQUIER caller sin auth. Middleware es pass-through (auth.protect
sigue desactivado pendiente M11).

Fix:
- /api/repos ahora requiere requireOperatorAuth (Bearer token) igual
  que /api/vault/*
- /app/repovision agrega flujo de Unlock idéntico al de /app/secrets:
  lee token de localStorage, si no existe muestra pantalla bloqueada
  con botón "Unlock" → window.prompt → setItem → loadRepos.
- Mensajes de error en español según status code (401/403/503).

## P2 (UX): deep-link de chat no se aplicaba
El link "Preguntar a V" en RepoVision pasaba ?project=ID&q=TEXT pero
ChatExperience no consumía esos params, así que el CTA navegaba pero
no cambiaba de scope ni pre-llenaba el input.

Fix:
- useSearchParams() en ChatExperience
- En el mount-only useEffect: si hay ?project=ID, lo usamos como
  scope inicial (prioridad sobre el saved en localStorage). Si hay
  ?q=TEXTO, pre-llena el input. Una sola vez al cargar — si Luis
  navega después no se re-pisa.
Next 15 requiere envolver useSearchParams() en <Suspense> o el build
estático falla con "missing-suspense-with-csr-bailout". El deep-link
handler que agregué para ?project=X&q=Y necesitaba esto.

Fallback null porque ChatExperience ya tiene loading states propios
para los mensajes y proyectos.
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.

2 participants