diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts index c1a6c05..5c29c8a 100644 --- a/src/app/api/tasks/route.ts +++ b/src/app/api/tasks/route.ts @@ -63,8 +63,8 @@ const TASK_PRIORITIES: Task["priority"][] = ["low", "medium", "high", "urgent"]; const SPRINT_STATUSES: Sprint["status"][] = ["planning", "active", "completed"]; const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; -// Optimized field selection - fetch all fields needed for board and detail display -const TASK_FIELDS = [ +// Field sets are split so board loads can avoid heavy attachment payloads. +const TASK_BASE_FIELDS = [ "id", "title", "type", @@ -80,10 +80,11 @@ const TASK_FIELDS = [ "due_date", "tags", "comments", - "attachments", "description", ]; +const TASK_DETAIL_ONLY_FIELDS = ["attachments"]; + class HttpError extends Error { readonly status: number; readonly details?: Record; @@ -271,7 +272,7 @@ function mapTaskRow(row: Record, usersById: Map mapProjectRow(row as Record)), sprints: (sprints || []).map((row) => mapSprintRow(row as Record)), - tasks: ((taskRows as unknown as Record[] | null) || []).map((row) => mapTaskRow(row, usersById, false)), + tasks: ((taskRows as unknown as Record[] | null) || []).map((row) => mapTaskRow(row, usersById, includeFullTaskData)), currentUser: { id: user.id, name: user.name, diff --git a/src/app/tasks/[taskId]/page.tsx b/src/app/tasks/[taskId]/page.tsx index 4376d40..8e5550c 100644 --- a/src/app/tasks/[taskId]/page.tsx +++ b/src/app/tasks/[taskId]/page.tsx @@ -256,7 +256,7 @@ export default function TaskDetailPage() { useEffect(() => { if (!authReady) return - syncFromServer() + syncFromServer({ includeFullTaskData: true }) }, [authReady, syncFromServer]) useEffect(() => { @@ -393,7 +393,7 @@ export default function TaskDetailPage() { } } - const handleAddComment = () => { + const handleAddComment = async () => { if (!editedTask || !newComment.trim()) return const commentAuthorId = getCurrentUserCommentAuthorId(currentUser) @@ -401,14 +401,28 @@ export default function TaskDetailPage() { toast.error("You must be signed in to add a comment.") return } - setEditedTask({ + + const nextTask: Task = { ...editedTask, comments: [...getComments(editedTask.comments), buildComment(newComment.trim(), commentAuthorId)], - }) + } + + setEditedTask(nextTask) setNewComment("") + + const success = await updateTask(nextTask.id, { + ...nextTask, + comments: getComments(nextTask.comments), + }) + if (!success) { + toast.error("Failed to save comment", { + description: "Comment was added locally but could not sync to the server.", + duration: 5000, + }) + } } - const handleAddReply = (parentId: string) => { + const handleAddReply = async (parentId: string) => { if (!editedTask) return const text = replyDrafts[parentId]?.trim() if (!text) return @@ -418,13 +432,26 @@ export default function TaskDetailPage() { toast.error("You must be signed in to reply.") return } - setEditedTask({ + const nextTask: Task = { ...editedTask, comments: addReplyToThread(getComments(editedTask.comments), parentId, buildComment(text, commentAuthorId)), - }) + } + + setEditedTask(nextTask) setReplyDrafts((prev) => ({ ...prev, [parentId]: "" })) setOpenReplyEditors((prev) => ({ ...prev, [parentId]: false })) + + const success = await updateTask(nextTask.id, { + ...nextTask, + comments: getComments(nextTask.comments), + }) + if (!success) { + toast.error("Failed to save reply", { + description: "Reply was added locally but could not sync to the server.", + duration: 5000, + }) + } } const handleDeleteComment = (commentId: string) => { diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index eb9fc45..2d73246 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -392,8 +392,6 @@ export async function revokeSession(token: string): Promise { } export async function getUserBySessionToken(token: string): Promise { - await deleteExpiredSessions(); - const supabase = getServiceSupabase(); const tokenHash = hashSessionToken(token); const now = new Date().toISOString(); diff --git a/src/stores/useTaskStore.ts b/src/stores/useTaskStore.ts index b87d5a4..b2d6d2e 100644 --- a/src/stores/useTaskStore.ts +++ b/src/stores/useTaskStore.ts @@ -90,7 +90,7 @@ interface TaskStore { syncError: string | null // Sync actions - syncFromServer: () => Promise + syncFromServer: (options?: { includeFullTaskData?: boolean }) => Promise setCurrentUser: (user: Partial) => void // Project actions @@ -326,11 +326,12 @@ export const useTaskStore = create()( lastSynced: null, syncError: null, - syncFromServer: async () => { + syncFromServer: async (options) => { console.log('>>> syncFromServer START') set({ isLoading: true, syncError: null }) try { - const res = await fetch('/api/tasks', { cache: 'no-store' }) + const query = options?.includeFullTaskData ? '?include=detail' : '' + const res = await fetch(`/api/tasks${query}`, { cache: 'no-store' }) console.log('>>> syncFromServer: API response status:', res.status) if (!res.ok) { const errorPayload = await res.json().catch(() => ({}))