From 5c6bd134bd95d061d4ef43008ffc3b9f89c2d34e Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 23 Feb 2026 17:01:26 -0600 Subject: [PATCH] Signed-off-by: Max --- README.md | 15 +++++++++++++++ scripts/README.md | 14 ++++++++++++++ src/app/page.tsx | 21 ++++++++++++++++++++- src/app/tasks/[taskId]/page.tsx | 21 ++++++++++++++++++++- supabase/update_task_comments_only.sql | 22 ---------------------- 5 files changed, 69 insertions(+), 24 deletions(-) delete mode 100644 supabase/update_task_comments_only.sql diff --git a/README.md b/README.md index 8266856..098acab 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,21 @@ Task and sprint board built with Next.js + Zustand and Supabase-backed API persi - Delete any comment or reply from the thread - Thread data is persisted in Supabase through the existing `/api/tasks` sync flow. +Current persisted comment shape: + +```json +{ + "id": "string", + "text": "string", + "createdAt": "2026-02-23T19:00:00.000Z", + "commentAuthorId": "user-uuid-or-assistant", + "replies": [] +} +``` + +- `commentAuthorId` is required. +- Legacy `author` objects are not supported by the UI parser. + ### Backlog drag and drop Backlog view supports moving tasks between: diff --git a/scripts/README.md b/scripts/README.md index bcf5015..5cb6925 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -85,6 +85,20 @@ A unified CLI that covers all API operations. ./scripts/gantt.sh task attach ./research.pdf ``` +Comment payload persisted by the API/CLI: + +```json +{ + "id": "string", + "text": "string", + "createdAt": "2026-02-23T19:00:00.000Z", + "commentAuthorId": "user-uuid-or-assistant", + "replies": [] +} +``` + +- `commentAuthorId` is the only supported author field. + ### Project Commands ```bash diff --git a/src/app/page.tsx b/src/app/page.tsx index e2538aa..cdf0bba 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -468,6 +468,25 @@ export default function Home() { return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name)) }, [users, currentUser]) + const knownUsersById = useMemo(() => { + const byId = new Map() + + const addUser = (id: string | undefined, name: string | undefined, avatarUrl?: string, email?: string) => { + if (!id || !name) return + if (id.trim().length === 0 || name.trim().length === 0) return + byId.set(id, { id, name, avatarUrl, email }) + } + + assignableUsers.forEach((user) => byId.set(user.id, user)) + tasks.forEach((task) => { + addUser(task.createdById, task.createdByName, task.createdByAvatarUrl) + addUser(task.updatedById, task.updatedByName, task.updatedByAvatarUrl) + addUser(task.assigneeId, task.assigneeName, task.assigneeAvatarUrl, task.assigneeEmail) + }) + + return byId + }, [assignableUsers, tasks]) + useEffect(() => { let isMounted = true const loadSession = async () => { @@ -756,7 +775,7 @@ export default function Home() { const resolveAssignee = (assigneeId: string | undefined) => { if (!assigneeId) return null - return assignableUsers.find((user) => user.id === assigneeId) || null + return knownUsersById.get(assigneeId) || null } const setNewTaskAssignee = (assigneeId: string) => { diff --git a/src/app/tasks/[taskId]/page.tsx b/src/app/tasks/[taskId]/page.tsx index b2172ff..4376d40 100644 --- a/src/app/tasks/[taskId]/page.tsx +++ b/src/app/tasks/[taskId]/page.tsx @@ -330,6 +330,25 @@ export default function TaskDetailPage() { return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name)) }, [users, currentUser]) + const knownUsersById = useMemo(() => { + const byId = new Map() + + const addUser = (id: string | undefined, name: string | undefined, avatarUrl?: string, email?: string) => { + if (!id || !name) return + if (id.trim().length === 0 || name.trim().length === 0) return + byId.set(id, { id, name, avatarUrl, email }) + } + + assignableUsers.forEach((user) => byId.set(user.id, user)) + tasks.forEach((task) => { + addUser(task.createdById, task.createdByName, task.createdByAvatarUrl) + addUser(task.updatedById, task.updatedByName, task.updatedByAvatarUrl) + addUser(task.assigneeId, task.assigneeName, task.assigneeAvatarUrl, task.assigneeEmail) + }) + + return byId + }, [assignableUsers, tasks]) + const sortedSprints = useMemo( () => sprints @@ -419,7 +438,7 @@ export default function TaskDetailPage() { const resolveAssignee = (assigneeId: string | undefined) => { if (!assigneeId) return null - return assignableUsers.find((user) => user.id === assigneeId) || null + return knownUsersById.get(assigneeId) || null } const setEditedTaskAssignee = (assigneeId: string) => { diff --git a/supabase/update_task_comments_only.sql b/supabase/update_task_comments_only.sql deleted file mode 100644 index 072414f..0000000 --- a/supabase/update_task_comments_only.sql +++ /dev/null @@ -1,22 +0,0 @@ --- Updates only tasks.comments using {id, comments} payload. --- Generated from supabase/tasks.json on 2026-02-23. -BEGIN; - -WITH payload AS ( - SELECT * - FROM jsonb_to_recordset( - $payload$ -[{"id":"57bc2efe-eb60-4cc7-a8b2-59d7bf8dc736","comments":[{"id":"c-start","text":"Subagent spawned for Mission page progress enhancements","createdAt":"2026-02-22T05:02:39.074Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"d9905880-1b03-49e2-8f81-bf0362055571","comments":[{"id":"c-start","text":"[2026-02-23 13:45] Subagent spawned to build Project Screen. Status changed to in-progress.","createdAt":"2026-02-23T13:45:00Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"4b81b11d-2348-44aa-9bf6-b00ba075ac4a","comments":[{"id":"c-start","text":"[2026-02-23 13:45] Subagent spawned to work on this bug. Status changed to in-progress.","createdAt":"2026-02-23T13:45:00Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"ce4a73a0-713c-4707-ae96-69dc40199ea5","comments":[{"id":"c-status","text":"[2026-02-23 13:45] This task has been sitting in-progress but no work has been done. Research on TTS (text-to-speech) for converting daily digest to podcast format is still needed. Need to research: 1) TTS providers (ElevenLabs, OpenAI, etc.), 2) Audio hosting options, 3) RSS feed generation for podcast distribution. Will pick up when higher priority tasks are complete.","createdAt":"2026-02-23T13:45:00Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"e326dcb6-c096-4845-8f5b-8aaf9ed1d40e","comments":[{"id":"c16","text":"FIXED: Updated cron job with pkill cleanup before restart + 2s delay. Created backup script: monitor-restart.sh","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"7da72025-4216-4bb8-ad98-a8cb1b89d687","comments":[{"id":"c27","text":"COMPLETED: Root cause analysis done. Primary suspect: Next.js dev server memory leaks. Secondary: SSH timeout, OOM killer, power mgmt. Full report: root-cause-analysis.md. Monitoring script deployed.","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"f526d0b1-6602-464b-aa0e-d1038918fbef","comments":[{"id":"c28","text":"COMPLETED: Added priority buttons to task detail dialog. Click any task to see Low/Medium/High/Urgent buttons with color coding. Changes apply immediately.","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"96dd0ce8-4ac7-4e1d-9e5f-06648e9304ff","comments":[{"id":"c29","text":"COMPLETED: Full rebuild with Next.js + shadcn/ui + Framer Motion. Dark OLED theme, glass-morphism cards, animated status indicators, sparkline visualizations, grid/list views, tooltips, progress bars. Production-grade at http://localhost:3005","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"92cdad6c-0f48-4c2e-b306-2a0be0a90703","comments":[{"id":"c1","text":"Need 1-to-many notes, not one big text field","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c2","text":"Agreed - will rebuild with proper comment threads","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"6e78e125-7ced-415a-9705-fd4a82e3ff2f","comments":[{"id":"c3","text":"User has local Gitea at http://192.168.1.128:3000","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c7","text":"✅ All 3 repos created and pushed to Gitea: gantt-board, blog-backup, heartbeat-monitor","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"0ed2b9b9-787a-4441-a35b-3047cc9c9cf9","comments":[{"id":"c39","text":"SUCCESS: Playwright + Google Chrome works! Successfully captured screenshot of http://localhost:3005 using headless Chrome","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c47","text":"COMPLETED: Playwright installed globally. Screenshots now work anytime without setup.","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"3b168e8f-d0a0-4341-a382-673d1e42f9de","comments":[{"id":"c41","text":"COMPLETED: Fixed parseDigest to extract URLs from markdown links [Title](url) in title lines","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c42","text":"COMPLETED: Title is now the clickable link with external link icon on hover","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"5782e4a8-60e0-4b1b-87af-6fe0cf0eb1fd","comments":[{"id":"c44","text":"COMPLETED: Added /api/tasks endpoint with file-based storage","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c45","text":"COMPLETED: Store now syncs from server on load and auto-syncs changes","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"341b7747-9a99-409e-99ea-52dfddacefe7","comments":[{"id":"c58","text":"COMPLETED: Installed react-markdown and remark-gfm","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c61","text":"COMPLETED: Links now open in new tab with blue styling and hover effects","createdAt":"2026-02-18T17:01:23.109Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]},{"id":"e4a28449-c065-409a-8205-cdec01c910d5","comments":[{"id":"c66","text":"All 3 apps verified to have data/ directories with JSON persistence","createdAt":"2026-02-19T13:00:00.000Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c67","text":"Created daily-backup.sh script to commit data to Git","createdAt":"2026-02-19T13:00:00.000Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c68","text":"Added cron job for 11:00 PM CST daily backups","createdAt":"2026-02-19T13:00:00.000Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]},{"id":"c69","text":"Backup logs to memory/backup.log for monitoring","createdAt":"2026-02-19T13:00:00.000Z","commentAuthorId":"9c29cc99-81a1-4e75-8dff-cd7cc5ceb5aa","replies":[]}]}] -$payload$::jsonb - ) AS x(id uuid, comments jsonb) -), -updated AS ( - UPDATE tasks t - SET comments = p.comments - FROM payload p - WHERE t.id = p.id - RETURNING t.id -) -SELECT COUNT(*) AS updated_rows FROM updated; - -COMMIT;