Signed-off-by: Max <ai-agent@topdoglabs.com>

This commit is contained in:
Max 2026-02-23 17:01:26 -06:00
parent 8aaca14e3a
commit 5c6bd134bd
5 changed files with 69 additions and 24 deletions

View File

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

View File

@ -85,6 +85,20 @@ A unified CLI that covers all API operations.
./scripts/gantt.sh task attach <task-uuid> ./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

View File

@ -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<string, AssignableUser>()
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) => {

View File

@ -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<string, AssignableUser>()
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) => {

File diff suppressed because one or more lines are too long