{ "id": "inbox-notifications", "name": "Inbox & Notifications", "status": "designing", "createdAt": "2026-03-25", "completedAt": null, "description": "Complete inbox notification system: sidebar unread badge, notification triggers for all key actions, archive UI, issue navigation from notifications, and real-time sync across tabs.", "currentState": "Inbox page exists with two-column layout. 5 notification triggers implemented (issue assign, reassign, status change, task complete, task fail). No sidebar badge. No archive button. No link to issue. Mark read/archive don't broadcast WS events. SDK return types wrong for mark read/archive.", "decisions": [ "Inbox uses useInboxStore from global store (not page-local useState)", "Sidebar badge reads unread count from store, updated by WS events", "Backend adds GET /api/inbox/unread-count endpoint using existing CountUnreadInbox SQL query", "Mark read and archive broadcast WS events (inbox:read, inbox:archived) for cross-tab sync", "New notification triggers: comment on assigned issue (notify assignee), status change notifies creator too, unassign notifies old assignee", "SDK markInboxRead and archiveInbox return Promise not Promise", "Inbox detail shows 'View Issue' link when issue_id is present" ], "tasks": [ { "task": "Backend: Add GET /api/inbox/unread-count endpoint", "done": false, "scope": "New handler using existing CountUnreadInbox query. Returns { count: number }. Requires auth + workspace membership. Route added to router.go." }, { "task": "Backend: Broadcast WS events for mark read and archive", "done": false, "scope": "MarkInboxRead broadcasts inbox:read event with { item_id, recipient_id }. ArchiveInboxItem broadcasts inbox:archived with { item_id, recipient_id }. Events added to protocol/events.go." }, { "task": "Backend: Add notification trigger for comment on assigned issue", "done": false, "scope": "When comment created, if issue has assignee and commenter is not the assignee, create inbox item type 'mentioned' severity 'info' for assignee. Implemented via event bus listener." }, { "task": "Backend: Status change notifies creator in addition to assignee", "done": false, "scope": "When issue status changes, create inbox item for creator (if creator != the person making the change). Existing assignee notification stays." }, { "task": "Backend: Unassign notifies old assignee", "done": false, "scope": "When issue assignee changes from A to B (or to null), create inbox item for old assignee A with type 'status_change' and title 'Unassigned from: {issue title}'." }, { "task": "SDK: Fix markInboxRead and archiveInbox return types", "done": false, "scope": "Change markInboxRead from Promise to Promise. Change archiveInbox from Promise to Promise. Update type imports." }, { "task": "Frontend: Add WS event types for inbox:read and inbox:archived", "done": false, "scope": "Add inbox:read and inbox:archived to WSEventType in packages/types/src/events.ts. Add payload types." }, { "task": "Frontend: Sidebar unread badge", "done": false, "scope": "Sidebar Inbox nav item shows unread count badge (shadcn Badge variant). Initial count from /api/inbox/unread-count on mount. Incremented on inbox:new, decremented on inbox:read/inbox:archived WS events." }, { "task": "Frontend: Inbox detail 'View Issue' navigation", "done": false, "scope": "When selected item has issue_id, show 'View Issue' button/link in InboxDetail that navigates to /issues/{issue_id}. Use shadcn Button variant='outline'." }, { "task": "Frontend: Archive button in inbox detail", "done": false, "scope": "Archive button next to Mark Read in InboxDetail. Calls api.archiveInbox(id). On success, removes item from store. Shows toast confirmation." }, { "task": "Frontend: Inbox real-time sync for read/archive", "done": false, "scope": "useRealtimeSync handles inbox:read (mark item read in store) and inbox:archived (remove item from store). Badge count updates automatically from store." }, { "task": "Frontend: Inbox loading/empty states with shadcn", "done": false, "scope": "Initial load shows Skeleton. Empty inbox shows centered illustration/text 'All caught up'. Error shows toast. Consistent with other pages." } ] }