Skip to content

feat: task comments (backend + UI + tests)#66

Merged
kelvinCB merged 28 commits intomainfrom
feature/task-comments
Feb 13, 2026
Merged

feat: task comments (backend + UI + tests)#66
kelvinCB merged 28 commits intomainfrom
feature/task-comments

Conversation

@kelvinCB
Copy link
Owner

Qué incluye

  • UI de comentarios dentro del detalle de tarea (TaskDetailModal)
  • Endpoints backend para listar/crear comentarios
  • Migración SQL: public.task_comments + RLS + policies
  • Tests: unit (TaskComments) + E2E Playwright (e2e/task-comments.spec.ts)
  • Docs: docs/DATABASE_SCHEMA.md actualizado

Cómo probar

  • Unit: npm test
  • E2E: npm run test:e2e:headless -- e2e/task-comments.spec.ts

Notas

  • Se removieron logs de debug usados durante el diagnóstico del 500/PGRST205.

- Add comments UI in task modal\n- Add comments API endpoints + types\n- Add SQL migration for task_comments + RLS policies\n- Add Playwright E2E + component tests\n- Update DB schema docs
@vercel
Copy link

vercel bot commented Feb 12, 2026

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

Project Deployment Actions Updated (UTC)
task-manager-llwv Ready Ready Preview, Comment Feb 13, 2026 9:13am

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: fbc6a37959

ℹ️ 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 on lines 32 to 36
WITH CHECK (
EXISTS (
SELECT 1 FROM public.tasks
WHERE public.tasks.id = public.task_comments.task_id
AND public.tasks.user_id = auth.uid()

Choose a reason for hiding this comment

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

P2 Badge Restrict inserted comment user_id to auth.uid()

The INSERT RLS policy only verifies that the task belongs to auth.uid(), but it does not constrain user_id, so an authenticated user can insert comments on their own task while spoofing another user's identity fields via direct Supabase access. In that scenario, comment authorship data is untrusted and the row may become undeletable by the creator because the DELETE policy requires auth.uid() = user_id. Add a user_id = auth.uid() check to the WITH CHECK predicate.

Useful? React with 👍 / 👎.

const { content } = req.body;
const user_id = req.user.id;

if (!content || !content.trim()) {

Choose a reason for hiding this comment

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

P2 Badge Validate comment content type before trimming

This validation path calls content.trim() without confirming content is a string, so requests like { "content": 123 } throw a TypeError and return 500 from the catch block instead of a 4xx validation response. The bug is triggered by malformed client payloads and makes bad input look like a server failure; guard with typeof content === 'string' before trimming.

Useful? React with 👍 / 👎.

- Enforce auth.uid() = user_id in INSERT policy
- Validate comment content type before trimming
@kelvinCB
Copy link
Owner Author

Gracias por el review. ✅ Ya apliqué ambos cambios en ff2c7e9:

  1. RLS INSERT más estricto (migrations/001_create_task_comments.sql)
  • En la policy de INSERT agregué auth.uid() = user_id dentro del WITH CHECK.
  • Se mantiene además la validación de ownership de la tarea (tasks.user_id = auth.uid()).
  1. Validación de content antes de trim (backend/src/controllers/taskController.js)
  • Ahora se valida typeof content !== 'string' y responde 400 si no es string.
  • Luego se usa trimmed = content.trim() y se valida vacío.

Volví a correr los tests relevantes y el E2E de comentarios sigue pasando.

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: ff2c7e98d2

ℹ️ 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 on lines +46 to +47
if (response.data) {
setComments((prev) => [...prev, response.data!]);

Choose a reason for hiding this comment

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

P2 Badge Surface comment submission failures

This handler only processes the success path and silently ignores response.error, so in supported unauthenticated/offline flows (where the API returns Not authenticated) the form remains usable but comments are never saved and the user gets no feedback. That makes the new comments UI appear to work while dropping user actions, which is a functional regression for local/offline task usage.

Useful? React with 👍 / 👎.

- Surface API errors from addComment via toast
- Add unit test for failed comment submission path
@kelvinCB
Copy link
Owner Author

Update aplicado para el nuevo review ✅

Fix implementado

  • En TaskComments.tsx ahora se maneja explícitamente el caso response.error al enviar comentario.
  • Si falla el submit, se muestra feedback al usuario con toast.error(...) (ya no falla en silencio).
  • También se agregó fallback en catch con error genérico.

Cobertura

  • Añadí test unitario nuevo en TaskComments.test.tsx para validar que cuando addComment devuelve error se dispara toast.error.
  • Suite del componente: 4 tests pass.

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: c7080291e0

ℹ️ 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 on lines 25 to 28
const response = await taskService.getComments(taskId);
if (response.data) {
setComments(response.data);
}

Choose a reason for hiding this comment

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

P2 Badge Reset comments when a fetch for a new task fails

fetchComments only updates state when response.data exists, so on an error path the previous comments array is kept. If a user opens task A, then switches to task B and the B request fails (network/auth/backend error), the UI can keep showing task A comments under task B after loading finishes, which is incorrect task data. Clear comments on task change or explicitly handle response.error to avoid stale cross-task rendering.

Useful? React with 👍 / 👎.

- Reset comments state when loading a different task
- Surface getComments API errors with toast
- Add unit test for stale comment regression path
@kelvinCB
Copy link
Owner Author

Nuevo review atendido ✅ (P2 sobre stale comments al cambiar de tarea y fallar fetch)

Fix aplicado

  • src/components/TaskComments.tsx
    • Al iniciar fetchComments ahora se limpia estado con setComments([]) para evitar arrastrar comentarios de otra tarea.
    • Se maneja response.error en getComments y se muestra toast.error(...).
    • En catch también se muestra fallback de error.

Cobertura agregada

  • src/test/components/TaskComments.test.tsx
    • Nuevo test: valida que si la primera tarea carga comentarios y la segunda falla, no se muestran comentarios stale y se muestra el error.

Commit

  • 0751fadfix: clear stale comments on fetch failures
  • Unit test del componente: 5/5 pass.

@codex review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

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: 0751fad0c7

ℹ️ 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 on lines +32 to +33
if (response.data) {
setComments(response.data);

Choose a reason for hiding this comment

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

P2 Badge Guard comment state updates against stale fetches

When taskId changes while a previous getComments call is still in flight, the older response can resolve later and overwrite the current task’s comments through setComments(response.data), so users can see comments from the wrong task under normal network reordering. The same race also affects loading state because each request independently calls setIsLoading(false), so this should be tied to the latest request (for example via request IDs or AbortController) before updating component state.

Useful? React with 👍 / 👎.

- Guard comment state updates with request sequence IDs
- Ignore out-of-order responses and stale loading transitions
@kelvinCB
Copy link
Owner Author

Nuevo review atendido ✅ (race condition por respuestas stale en getComments)

Fix aplicado

  • src/components/TaskComments.tsx
    • Implementé guard de concurrencia con requestSeqRef.
    • Cada fetchComments toma un requestId y solo actualiza estado si sigue siendo el request más reciente.
    • Se ignoran respuestas fuera de orden para evitar sobrescribir comentarios de la tarea actual.
    • setIsLoading(false) también queda atado al request vigente para evitar flicker/estado incorrecto.

Validación

  • Test suite del componente: TaskComments.test.tsx 5/5 pass.

Commit

  • d89ae00fix: prevent stale comment fetch race

Este es un Code Review hecho por Kimi k2.5

@Kimi k2.5 por favor revisa el PR #66 completo y deja findings; si no encuentras issues, responde explícitamente Approved.

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Kimi review: no output (empty response).

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Findings

P1 - Security/Correctness

  • backend/src/controllers/taskController.js
    Issue: Missing maximum length validation on content field in addComment.
    Risk: DoS via massive payload or database constraint violations.
    Rec: Add if (trimmed.length > 2000) (or appropriate limit) before inserting.

  • backend/src/controllers/taskController.js
    Issue: No rate limiting on comment creation endpoint.
    Risk: Spam/abuse via automated posting.
    Rec: Add express-rate-limit middleware or Redis-based throttling (e.g., max 10 comments/minute per user).

P2 - UX/Edge Cases

  • backend/src/controllers/taskController.js
    Issue: getComments fetches all comments without pagination/limit.
    Risk: Performance degradation on tasks with thousands of comments.
    Rec: Add limit/offset query params or cursor-based pagination; default to 50 comments max.

  • backend/src/controllers/taskController.js
    Issue: Missing updateComment and deleteComment controller methods.
    Risk: Users cannot edit or remove their own comments despite RLS policies allowing DELETE.
    Rec: Implement PUT/PATCH and DELETE endpoints for comment management (or remove DELETE policy if not intended).

  • src/components/TaskComments.tsx
    Issue: Textarea lacks maxLength attribute and character counter.
    Risk: Users exceed backend limit unknowingly, causing submission failures.
    Rec: Add maxLength={2000} and display remaining characters.

  • e2e/task-comments.spec.ts
    Issue: Test creates persistent data (task + comment) without cleanup in afterEach/afterAll.
    Risk: Pollution of test environment and flaky subsequent runs.
    Rec: Add API call to delete the created task after test completion.

P3 - Performance/Minor

  • migrations/001_create_task_comments.sql
    Issue: Missing index on task_comments(task_id).
    Rec: Add CREATE INDEX idx_task_comments_task_id ON public.task_comments(task_id); for query performance.

  • src/components/TaskComments.tsx
    Issue: formatDate usage without locale awareness in edge cases.
    Rec: Ensure formatDate handles i18n or pass locale explicitly.

Note on Security (Non-blocking): The implementation correctly uses req.user.id (server-side JWT) rather than trusting client-provided user_id, and RLS policies properly enforce task ownership. No SQL injection vectors detected.

- Add backend comment length guard + lightweight cooldown\n- Add comments pagination and explicit column selection\n- Use maybeSingle for optional profile lookup\n- Add task_comments(task_id) index in migration\n- Add textarea maxLength + live character counter
@kelvinCB
Copy link
Owner Author

Update aplicado sobre el review largo de Kimi ✅

Cambios hechos en este push (9f6f624)

  • backend/src/controllers/taskController.js
    • Límite de longitud para comentario (MAX_COMMENT_LENGTH=2000).
    • Cooldown ligero por usuario para mitigar spam (429 si envía demasiado rápido).
    • getComments ahora usa paginación (limit/offset) con defaults seguros.
    • getComments usa selección explícita de columnas (sin select('*')).
    • profiles lookup cambiado a .maybeSingle() para no romper si no existe perfil.
  • migrations/001_create_task_comments.sql
    • Índice idx_task_comments_task_id agregado.
  • src/components/TaskComments.tsx
    • maxLength en textarea + contador visual de caracteres.
  • src/services/taskService.ts
    • getComments(taskId, limit, offset) adaptado a paginación.

Test corrido:

  • npx vitest run src/test/components/TaskComments.test.tsx

En paralelo dejé corregido el core.hooksPath del repo para que use el hook global de Kimi en los próximos pushes.

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Findings

  • P2 | backend/src/controllers/taskController.js | Rate limiting stateful en entorno distribuido

    • lastCommentAtByUser es un Map en memoria. En despliegues con múltiples réplicas o serverless, el rate limiting por usuario no funcionará consistentemente. Recomendación: Usar Redis o implementar rate limiting en capa de API Gateway/NGINX.
  • P2 | backend/src/controllers/taskController.js | Falta sanitización de contenido XSS

    • El content del comentario se almacena sin sanitizar HTML/JS. Aunque React escapa en el renderizado, el contenido podría usarse en otros contextos (emails, exports, mobile apps) causando stored XSS. Recomendación: Sanitizar con DOMPurify o similar antes de guardar.
  • P2 | migrations/001_create_task_comments.sql | RLS DELETE no verifica ownership de task

    • La política DELETE solo verifica auth.uid() = user_id, permitiendo borrar comentarios incluso si la tarea fue transferida a otro usuario (si existiera lógica de transferencia). Recomendación: Añadir verificación de que la tarea pertenece al usuario actual igual que en SELECT/INSERT.
  • P3 | src/components/TaskComments.tsx | Falta paginación en UI

    • El backend soporta limit/offset pero el frontend siempre carga todos los comentarios sin infinite scroll o paginación. Con >50 comentarios habrá degradación de performance. Recomendación: Implementar "Load more" o infinite scroll.
  • P3 | backend/src/controllers/taskController.js | Manejo de errores genérico

    • En getComments, cualquier error de Supabase (no solo "not found") devuelve 404, ocultando errores de DB reales. Recomendación: Diferenciar entre .code === 'PGRST116' (not found) vs otros errores (500).
  • P3 | src/components/TaskComments.tsx | Race condition en submit

    • Si el usuario envía rápidamente múltiples comentarios (doble click), no hay debounce/exclusión hasta que isSubmitting se active. Recomendación: Deshabilitar el botón inmediatamente al hacer click.

Nota menor: El workflow de Kimi Co-Review usa uses: kelvinCB/github-automation/.github/workflows/kimi-review-reusable.yml@main sin pin de SHA. Considerar pinear a un SHA específico para supply chain security.

@github-actions
Copy link

Kimi K2.5 Co-Review

Findings

P1 | backend/src/controllers/taskController.js

Memory leak + Rate limiting no distribuido
El Map lastCommentAtByUser crece indefinidamente (nunca se limpian entradas) y solo funciona en memoria local. En producción con múltiples réplicas del backend, el rate limiting no funcionará consistentemente entre nodos.

  • Recomendación: Mover el rate limiting a Redis o a la base de datos (ej. tabla user_rate_limits), o al menos usar un LRU cache con TTL para evitar el crecimiento ilimitado del Map.

P2 | backend/src/controllers/taskController.js

Endpoint DELETE documentado pero no implementado
La documentación de RLS en DATABASE_SCHEMA.md menciona políticas para DELETE, pero no existe controlador ni ruta HTTP para eliminar comentarios.

  • Recomendación: Implementar deleteComment controller y DELETE /tasks/:id/comments/:commentId route, o remover la política RLS de DELETE si no es requerida aún.

P2 | e2e/task-comments.spec.ts

Race condition con cooldown de 1.5s
El backend tiene COMMENT_COOLDOWN_MS = 1500. Si el test E2E se ejecuta rápidamente después de otro test que usó el mismo usuario, el botón de envío podría estar deshabilitado o retornar 429, causando flaky tests.

  • Recomendación: Agregar un wait explícito o bypass del cooldown en el entorno de test, o verificar el estado del botón antes de click y manejar el error 429 en el test.

P3 | src/components/TaskComments.tsx

Falta paginación
Aunque el backend soporta limit/offset, el frontend siempre solicita los valores por defecto (50) sin implementar infinite scroll o paginación. Esto causará problemas de performance en tareas con muchos comentarios.

  • Recomendación: Implementar "Load more" o infinite scroll utilizando los parámetros de paginación disponibles.

P3 | src/components/TaskComments.tsx

UX: No hay indicación del cooldown
El rate limiting de 1.5s existe en backend pero el usuario no recibe feedback visual durante ese tiempo; solo recibe un error toast si intenta enviar.

  • Recomendación: Agregar un timer visual o deshabilitar el botón de envío por 1.5s después de enviar un comentario exitosamente para sincronizar con el backend cooldown.

P3 | backend/src/controllers/taskController.js

Inconsistencia de tipo en task_id
El schema define task_id como bigint, pero en varios lugares se trata como string (UUID). Aunque JavaScript maneja números grandes mal, la conversión String(bc.task_id) en el servicio frontend es correcta, pero verificar que Supabase/Postgres maneje correctamente la comparación bigint ↔ string en las queries .eq('id', task_id).

  • Recomendación: Considerar usar uuid para task_id en la tabla task_comments para mantener consistencia con el tipo id de la tabla tasks (que parece ser bigint según el diff, pero verificar que no haya truncamiento en JavaScript para IDs > 2^53).

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Kimi review failed: request error/timeout.

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Kimi review failed: request error/timeout.

@github-actions
Copy link

Kimi K2.5 Co-Review

Code Review Findings

P2 - Security: Missing task ownership verification on comment mutations

File: backend/src/controllers/taskController.js (updateComment, deleteComment)

Issue: Both updateComment and deleteComment only verify that the comment belongs to the user (eq('user_id', user_id)), but do not verify that the user still has access to the parent task. If a user loses access to a task (e.g., task transferred to another user), they could still modify/delete their old comments.

Recommendation: Add a check to verify task ownership before allowing comment modifications:

// Verify task belongs to user before allowing comment mutation
const { data: task, error: taskErr } = await db
  .from('tasks')
  .select('id')
  .eq('id', task_id)
  .eq('user_id', user_id)
  .single();

if (taskErr || !task) {
  return res.status(404).json({ error: 'Task not found or access denied' });
}

P2 - UX: No rate limiting feedback in frontend

File: src/components/TaskComments.tsx

Issue: While the backend implements a 1.5s cooldown (COMMENT_COOLDOWN_MS), the frontend does not disable the submit button or show a countdown timer during the cooldown period. Users can spam-click the send button, resulting in 429 errors with "Please wait Xs" messages.

Recommendation: Implement visual cooldown state:

  • Disable submit button and show countdown when backend returns 429
  • Or implement client-side cooldown tracking to prevent submission during known cooldown windows

P3 - Security: Missing UPDATE RLS policy

File: migrations/001_create_task_comments.sql, docs/DATABASE_SCHEMA.md

Issue: The RLS policies only cover SELECT, INSERT, and DELETE. There is no explicit UPDATE policy, which means updates might fail or fall back to default restrictive behavior depending on Supabase configuration.

Recommendation: Add UPDATE policy:

CREATE POLICY "Users can update their own comments"
  ON public.task_comments
  FOR UPDATE
  USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

P3 - Documentation: Incomplete Swagger documentation

File: backend/src/routes/tasks.js

Issue: Swagger documentation is only provided for GET and POST /tasks/{id}/comments, but missing for PATCH and DELETE endpoints.

Recommendation: Add OpenAPI annotations for the remaining endpoints including path parameters (commentId) and response schemas.


P3 - UX: Missing optimistic update rollback

File: src/components/TaskComments.tsx (addComment)

Issue: On comment submission success, the component appends the new comment to local state immediately (setComments((prev) => [...prev, response.data!])). If the subsequent re-fetch (if implemented) or background sync fails, the comment might appear duplicated or out of sync.

Recommendation: Consider invalidating/refetching the comments list after successful submission to ensure consistency, or implement proper optimistic update rollback on error.


P3 - Correctness: HTML sanitization edge case

File: backend/src/controllers/taskController.js (sanitizeCommentContent)

Issue: The regex /<[^>]*>/g might not catch all HTML (e.g., malformed tags like <script without closing >). While this is XSS-safe if the frontend renders as textContent, if future changes introduce HTML rendering, this could be vulnerable.

Recommendation: Consider using a proper sanitization library like DOMPurify or sanitize-html if HTML content is ever allowed, or ensure the frontend always uses text rendering (which the current implementation does correctly).


Approved

  • Workflow configuration (.github/workflows/kimi-co-review.yml)
  • Pagination safety checks in getComments
  • Database schema design and indexing strategy
  • E2E test coverage for happy path
  • Type definitions and service layer abstraction

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Code Review: PR #66

Findings

P1 - Security/Correctness

  • backend/src/controllers/taskController.js (updateComment & deleteComment):
    No validan que el commentId pertenezca al task_id de la URL. Un usuario autenticado podría modificar/borrar comentarios de otros tasks si conoce el UUID del comentario, ya que solo filtran por user_id y commentId.
    Recomendación: Agregar .eq('task_id', task_id) a las queries de update/delete o verificar la pertenencia antes de la operación.

  • backend/src/controllers/taskController.js (addComment):
    La consulta del cooldown (latestOwnComment) no maneja errores de DB. Si falla, el código continúa permitiendo potencialmente el insert.
    Recomendación: Agregar if (error) throw error; después de la consulta del último comentario.

P2 - UX Edge Cases / Robustness

  • src/components/TaskComments.tsx:
    El parsing del cooldown con regex (wait\s+(\d+)s) es frágil y depende del formato exacto del mensaje de error del backend. Si cambia la traducción o el wording, el cooldown del frontend no funcionará.
    Recomendación: Usar el header Retry-After o un campo estructurado (retry_after_seconds) de la respuesta JSON.

  • backend/src/controllers/taskController.js (sanitizeCommentContent):
    Solo elimina tags HTML pero no escapa entidades HTML restantes ni previene otros vectores (ej. Zero-width chars, RTL override para spoofing).
    Recomendación: Considerar librería como DOMPurify o escape-html para sanitización más robusta, o al menos escapar &, <, >.

  • e2e/task-comments.spec.ts:
    Uso de test.setTimeout(60000) es excesivo y puede ocultar problemas de performance reales.
    Recomendación: Reducir a 15-30s y optimizar waits específicos.

P3 - Minor / Best Practices

  • src/services/taskService.ts:
    El casting (data as any) en el manejo del 429 es riesgoso. Definir una interfaz ErrorResponse tipada.

  • backend/src/controllers/taskController.js:
    El cooldown de 1.5s es muy corto para mitigación real de spam. Considerar aumentar a 5-10s o implementar rate limiting por IP en capa de API Gateway/Nginx.

  • .github/workflows/kimi-co-review.yml:
    Dependencia a workflow externo (kelvinCB/github-automation@main) sin pinning a SHA específico.
    Recomendación: Pin a SHA commit específico para supply chain security.

Summary

No aprobar hasta resolver P1 (validación de pertenencia task-comment en update/delete). Los demás son mejoras recomendadas.

@github-actions
Copy link

Kimi K2.5 Co-Review

Findings

P2 | src/components/TaskComments.tsx | Race condition en error handling

Problema: En fetchComments(), al producirse un error se ejecuta setComments([]) antes de mostrar el toast. Esto limpia comentarios previamente cargados en la UI durante un error de red transitorio, causando flicker y pérdida de contexto visual para el usuario.
Recomendación: No limpiar el estado comments en caso de error de fetch; mantener los datos existentes y solo mostrar el error.

P2 | src/components/TaskComments.tsx | Parsing frágil de cooldown

Problema: El frontend parsea el tiempo de espera usando regex sobre el mensaje de error (wait\s+(\d+)s). El backend ya devuelve retry_after_seconds en el JSON y el header Retry-After.
Recomendación: Usar el campo retry_after_seconds de la respuesta JSON o el header HTTP en lugar de regex sobre texto localizado.

P2 | backend/src/controllers/taskController.js | Error handling incorrecto en deleteComment

Problema: La función deleteComment retorna status 404 tanto para "comentario no encontrado" como para errores de base de datos (if (error || !deleted)), dificultando el debugging y ocultando errores 500 reales.
Recomendación: Distinguir entre error (500 Internal Server Error) y !deleted (404 Not Found).

P3 | backend/src/controllers/taskController.js | Sanitización básica de HTML

Problema: sanitizeCommentContent usa regex simple para quitar tags (/<[^>]*>/g). Esto es vulnerable a bypasses (ej. <img src=x onerror=alert(1)> si el regex no es greedy, o null bytes).
Recomendación: Usar librería robusta como dompurify o striptags, o escapar el contenido en el frontend (React ya lo hace, pero la API podría servir a otros clientes).

P3 | src/components/TaskComments.tsx | Falta validación en edición

Problema: El textarea de edición no valida longitud máxima antes de enviar (solo tiene atributo maxLength que es bypassable). El backend rechazará >2000 chars, pero la UX muestra el error post-envío.
Recomendación: Añadir validación client-side igual que en addComment, mostrando el contador de caracteres durante la edición.

P3 | src/components/TaskComments.tsx | Falta indicador de carga en edición/eliminación

Problema: No hay estado isEditing o isDeleting; el usuario puede clickear múltiples veces "Save" o "Delete" mientras la petición está en curso.
Recomendación: Deshabilitar botones y mostrar spinner durante operaciones de mutación, no solo en addComment.

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Kimi review failed: request error/timeout.

@github-actions
Copy link

Kimi K2.5 Co-Review

Findings

  • P2 migrations/001_create_task_comments.sql
    Falta índice compuesto (user_id, created_at) para la query de cooldown (order by created_at desc limit 1). Sin él, la verificación de cooldown escanea todos los comentarios del usuario en tablas grandes.
    Recomendación: Agregar CREATE INDEX idx_task_comments_user_created ON public.task_comments(user_id, created_at DESC);.

  • P2 backend/src/controllers/taskController.js (addComment)
    El chequeo de cooldown y el INSERT no son atómicos; bajo alta concurrencia (race condition) ambos requests pueden pasar la validación antes de que cualquiera inserte.
    Recomendación: Usar una transacción SQL explícita o mover el rate-limiting a una capa superior (Redis/API Gateway).

  • P2 backend/src/controllers/taskController.js (updateComment/deleteComment)
    No se valida que commentId sea un UUID válido antes de consultar. Supabase retornará error de formato que se mapea a 404, pero debería ser 400 Bad Request.
    Recomendación: Agregar validación UUID con una regex o librería (ej. validator.isUUID) al inicio del handler.

  • P3 src/components/TaskComments.tsx
    supabase.auth.getUser() no tiene .catch(); si la sesión expira o hay error de red, el error se swallow y currentUserId queda en null silenciosamente, ocultando botones de editar/eliminar que deberían estar disponibles.
    Recomendación: Agregar manejo de error explícito o inicializar currentUserId desde el auth context existente en lugar de re-fetch.

  • P3 src/components/TaskComments.tsx (fetchComments)
    Aunque usa requestSeqRef para ignorar respuestas stale, el finally block puede llamar setIsLoading(false) si el componente ya se desmontó (condición de carrera).
    Recomendación: Usar una flag de montaje (useRef boolean) o AbortController para cancelar efectos pendientes en cleanup.

  • P3 src/components/TaskComments.tsx (saveEdit)
    No hay estado de isSaving ni deshabilitación de botones durante la edición; clicks múltiples generan requests duplicadas.
    Recomendación: Agregar flag isSaving y deshabilitar botones mientras se procesa.

Notas menores

  • La sanitización XSS en sanitizeCommentContent escapa entidades HTML básicas; es adecuada dado que React escapa por defecto en el frontend (defensa en profundidad).
  • La validación de paginación (Number.isSafeInteger(offset + limit - 1)) previene overflow de enteros correctamente.

@kelvinCB
Copy link
Owner Author

Kimi K2.5 Co-Review

Kimi review failed: request error/timeout.

@github-actions
Copy link

Kimi K2.5 Co-Review

Code Review: PR #66 - Task Comments

Overall a solid implementation with good security practices (RLS, input sanitization, cooldown) and comprehensive testing. However, there are concurrency and consistency issues that should be addressed before merge.


🔴 P2 - Race Condition in Comment Cooldown

File: backend/src/controllers/taskController.js (lines 441-460)

The DB-backed cooldown check is not atomic. Two concurrent requests can query latestOwnComment simultaneously, both pass the check, and both insert.

Recommendation: Use a unique constraint on (user_id, EXTRACT(EPOCH FROM created_at)::bigint) rounded to cooldown window, or implement distributed rate-limiting (Redis). Alternatively, accept the race as low-risk but document it.


🔴 P2 - Inconsistent Error Handling Patterns

File: backend/src/controllers/taskController.js (lines 484 vs 520)

updateComment uses .single() while deleteComment uses .maybeSingle(). This creates inconsistent behavior when records don't exist (one throws, one returns null).

Recommendation: Standardize on .maybeSingle() for both to avoid unhandled exceptions from PostgREST PGRST116 errors.


🔴 P2 - Missing Transaction Boundaries

File: backend/src/controllers/taskController.js (lines 393-404, 441-476)

Task ownership verification and comment insertion are separate queries. If the task is deleted between SELECT and INSERT/UPDATE, it throws a 500 FK error instead of a controlled 404.

Recommendation: Use a transaction or at least handle P0003 (no_data_found) errors explicitly.


🟡 P3 - Missing Swagger Documentation

File: backend/src/routes/tasks.js (lines 206-208)

PATCH and DELETE endpoints for comments lack Swagger/OpenAPI documentation (only GET and POST are documented).

Recommendation: Add swagger-jsdoc comments for consistency with the rest of the API.


🟡 P3 - Client-Side Clock Drift in Cooldown UX

File: src/components/TaskComments.tsx (lines 68-82)

The countdown uses Date.now() (client time) to calculate cooldownUntil, but the server is the authority. If the client clock is ahead, the button enables before the server accepts requests; if behind, user waits longer than necessary.

Recommendation: Calculate remaining time based on the server's Retry-After header value (which you already receive) rather than local clock, or add small buffer tolerance.


🟡 P3 - Missing Composite Index

File: migrations/001_create_task_comments.sql (lines 12-15)

getComments queries filter by task_id and order by created_at. Current indices are separate.

Recommendation: Add CREATE INDEX idx_task_comments_task_created ON public.task_comments(task_id, created_at); for optimal query performance.


🟢 Approved with Reservations

The implementation is functionally correct and secure for production, but the P2 concurrency issues should be addressed in a follow-up PR to prevent potential abuse or inconsistent state under high load.

Security Note: Good job on the backend HTML escaping + React's native escaping (defense in depth). The RLS policies correctly implement user isolation.

@kelvinCB kelvinCB merged commit 2137fc1 into main Feb 13, 2026
3 checks passed
@kelvinCB kelvinCB deleted the feature/task-comments branch February 13, 2026 09:19
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

Comments