Skip to content

Refactor inbox to unified messaging interface with channels and DMs - #345

Merged
2witstudios merged 4 commits into
masterfrom
claude/redesign-inbox-layout-vkldN
Feb 3, 2026
Merged

2witstudios merged 4 commits into
masterfrom
claude/redesign-inbox-layout-vkldN

Conversation

@2witstudios

@2witstudios 2witstudios commented Feb 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

This PR refactors the inbox system to provide a unified messaging interface that consolidates channels and direct messages under a single /dashboard/inbox route structure. The changes extract messaging logic into reusable components and establish a cleaner separation between channel conversations and direct message conversations.

Key Changes

  • Unified Inbox Structure: Reorganized inbox routes to use /dashboard/inbox/channel/[pageId] for channels and /dashboard/inbox/dm/[conversationId] for direct messages, replacing the previous /dashboard/messages route

  • Component Extraction: Created InboxCenterList component to handle the default inbox view, reducing code duplication across inbox pages

  • Channel Messaging: Implemented full-featured channel page with:

    • Real-time message synchronization via WebSocket
    • Message reactions with optimistic updates
    • File attachment support with preview/download
    • Markdown rendering with StreamingMarkdown
    • Permission-based access control (view-only vs. edit)
    • Pull-to-refresh functionality
  • Direct Messaging: Implemented DM page with:

    • Real-time message updates via WebSocket
    • Message read status tracking
    • Edit history indicators
    • Connection-based user selection
  • New Conversation Flow: Added /dashboard/inbox/new page to initiate new conversations with:

    • Connection-based user search and selection
    • Conversation creation or retrieval
    • Automatic navigation to conversation thread
  • Backward Compatibility: Maintained redirect from old /dashboard/messages/[conversationId] route to new inbox DM route

Implementation Details

  • Leveraged existing ChannelInput component for both channels and DMs
  • Integrated useSocketStore for real-time updates across messaging features
  • Used usePermissions hook for granular access control in channels
  • Implemented optimistic UI updates for messages and reactions with rollback on failure
  • Added proper error handling and user feedback via toast notifications

https://claude.ai/code/session_01JH2oYLLDvguLZ5pWCZzttL

Summary by CodeRabbit

  • New Features

    • Introduced unified Inbox view with separate sections for direct messages and channels
    • Added message search functionality within the inbox
    • Enabled message reactions and file attachment support in conversations
  • User Interface

    • Consolidated messaging navigation under a new Inbox interface
    • Redesigned inbox layout with improved organization and real-time message updates

- Redesign /dashboard/inbox to show message list in center area (like Gmail)
- When clicking a conversation, transition to sidebar list + chat in center
- Create new inbox routes for DMs (/dashboard/inbox/dm/[id]) and channels
  (/dashboard/inbox/channel/[id])
- Add "Open in Drive" button for channels to navigate to drive context
- Add "New Message" button to start new conversations from inbox
- Update InboxSidebar to use new inbox routes
- Redirect legacy /dashboard/messages routes to new inbox routes
- Remove unused MessagesLeftSidebar from MemoizedSidebar

https://claude.ai/code/session_01JH2oYLLDvguLZ5pWCZzttL
- Delete /dashboard/messages directory and all subroutes
- Remove MessagesLeftSidebar component (no longer needed)
- Update all references to use new /dashboard/inbox routes:
  - connections page: /dashboard/inbox/dm/[id]
  - notification dropdown: /dashboard/inbox/dm/[id]
  - tab-title parsing and tests
  - sample-dashboard.html
- Remove 'messages' from FULL_PAGE_ROUTES in dashboard layout
- Clean up MemoizedSidebar (remove legacy messages handling)

https://claude.ai/code/session_01JH2oYLLDvguLZ5pWCZzttL
@coderabbitai

coderabbitai Bot commented Feb 3, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 27 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

This PR consolidates the messaging system into a unified inbox architecture, removing the legacy /dashboard/messages route and introducing structured inbox subpaths (/inbox/dm, /inbox/channel, /inbox/new). It replaces messaging sidebar components with inbox-specific logic, updates all navigation references, and reorganizes tab routing metadata accordingly.

Changes

Cohort / File(s) Summary
Route & Layout Removal
apps/web/src/app/dashboard/messages/layout.tsx, apps/web/src/app/dashboard/messages/page.tsx
Deleted the entire messages route structure, including the layout wrapper and main page component.
Inbox Page Components
apps/web/src/app/dashboard/inbox/page.tsx, apps/web/src/app/dashboard/inbox/dm/[conversationId]/page.tsx, apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx, apps/web/src/app/dashboard/inbox/new/page.tsx, apps/web/src/app/dashboard/[driveId]/inbox/page.tsx
Created new inbox-scoped pages with DM and channel subpaths; renamed ConversationPage to InboxDMPage; introduced InboxChannelPage with socket integration, real-time updates, and message attachments; delegated drive-inbox view to InboxCenterList; updated routing paths from /messages to /inbox variants.
Inbox Components
apps/web/src/components/inbox/InboxCenterList.tsx
New component providing unified inbox view with SWR-based data fetching, real-time socket updates, pagination, search filtering, and drive-scoped queries.
Sidebar & Navigation
apps/web/src/components/layout/left-sidebar/MemoizedSidebar.tsx, apps/web/src/components/layout/left-sidebar/InboxSidebar.tsx, apps/web/src/components/layout/left-sidebar/MessagesLeftSidebar.tsx, apps/web/src/components/notifications/NotificationDropdown.tsx
Removed MessagesLeftSidebar and replaced MessagesRoute detection with InboxRoute logic in MemoizedSidebar; updated InboxSidebar to route to new /inbox/dm and /inbox/channel paths; updated NotificationDropdown navigation to use new inbox path.
Routing & Tab Infrastructure
apps/web/src/lib/tabs/tab-title.ts, apps/web/src/lib/tabs/__tests__/tab-title.test.ts, apps/web/src/app/dashboard/layout.tsx
Replaced messages/messages-conversation PathTypes with inbox/inbox-dm/inbox-channel/inbox-new; updated tab metadata mappings and parsing logic; removed /dashboard/messages from FULL_PAGE_ROUTES.
Documentation & Sample Resources
apps/web/src/lib/canvas/sample-dashboard.html
Updated navigation and card links to reference /dashboard/inbox instead of /dashboard/messages.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 Hopping through the inbox maze,
Where messages now greet our gaze,
Old routes are swept, new paths aligned,
A unified structure, thoughtfully designed,
No more /messages, just /inbox—refined! 📬

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring the inbox into a unified messaging interface that consolidates channels and DMs under a single route structure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/redesign-inbox-layout-vkldN

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/web/src/app/dashboard/inbox/channel/`[pageId]/page.tsx:
- Around line 83-92: The fetchMessages useEffect lacks error handling and
doesn't check HTTP status before calling res.json(); update the async
fetchMessages function (used in the useEffect that depends on pageId) to wrap
the fetchWithAuth call and JSON parsing in a try/catch, check res.ok and throw
or handle non-ok responses (e.g., read error body or create a descriptive Error)
before calling res.json(), and on error set a safe state (e.g., setMessages([])
or a separate error state) and optionally log the error so the UI doesn't
receive invalid data.
- Around line 195-203: handleRefresh currently parses the fetch response without
checking HTTP status; update the handleRefresh async function to verify res.ok
after awaiting fetchWithAuth(`/api/channels/${pageId}/messages`) (same pattern
used in the initial fetch), and if not ok throw or handle an error (e.g., throw
new Error(`Failed to fetch messages: ${res.status}`) or call
setMessages([])/log) so the catch block receives a proper error instead of
attempting to parse a non-OK body; ensure you reference the handleRefresh
function and use the pageId dependency already present.
🧹 Nitpick comments (3)
apps/web/src/components/inbox/InboxCenterList.tsx (1)

84-106: Consider deduplicating items when loading more pages.

The loadMore function appends moreData.items directly without checking for duplicates. If real-time updates via socket add an item that also appears in the next page, duplicates could occur.

♻️ Proposed fix to deduplicate items
       const moreData: InboxResponse = await response.json();

-      setAllItems((prev) => [...prev, ...moreData.items]);
+      setAllItems((prev) => {
+        const existingIds = new Set(prev.map(item => `${item.type}-${item.id}`));
+        const newItems = moreData.items.filter(
+          item => !existingIds.has(`${item.type}-${item.id}`)
+        );
+        return [...prev, ...newItems];
+      });
       setPagination(moreData.pagination);
apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx (1)

242-277: Potential stale closure in handleRemoveReaction.

The callback captures messages in its dependency array, which means a new function is created on every message update. This is necessary to find the removed reaction for rollback, but could cause stale closures during rapid interactions. Consider using a ref or functional state update to access current messages.

♻️ Alternative using ref to avoid stale closure
+  const messagesRef = useRef<MessageWithUser[]>([]);
+  messagesRef.current = messages;

   const handleRemoveReaction = useCallback(async (messageId: string, emoji: string) => {
     if (!user) return;

-    const removedReaction = messages
+    const removedReaction = messagesRef.current
       .find((m) => m.id === messageId)
       ?.reactions?.find((r) => r.emoji === emoji && r.userId === user.id);
     // ... rest of the function
-  }, [pageId, user, messages]);
+  }, [pageId, user]);
apps/web/src/app/dashboard/inbox/dm/[conversationId]/page.tsx (1)

119-132: Consider adding optimistic UI for sent messages.

Unlike the channel page which shows optimistic messages immediately, this DM page clears the input and waits for the socket event to display the sent message. This could feel less responsive to users. Consider adding optimistic message display for consistency.

This is a UX enhancement and not critical since the socket response is typically fast.

Comment thread apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx
Comment thread apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx

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

Copy link
Copy Markdown

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: 96d70f738e

ℹ️ 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 +45 to +49
useInboxSocket({ driveId });

const { data, error, isLoading } = useSWR<InboxResponse>(apiUrl, fetcher, {
refreshInterval: 0,
isPaused: () => hasLoadedRef.current && isEditingActive(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Wire inbox socket updates into center list

The center list calls useInboxSocket, but it never uses the hook’s hasLoadedRef to mark the initial fetch as complete. That ref stays false, so useInboxSocket drops all inbox update events and the main inbox view won’t update in real time (new messages/read status changes won’t appear until a manual refresh). Consider using the hook’s returned hasLoadedRef in the SWR onSuccess handler (as the sidebar does), or change the hook so it doesn’t rely on an external ref.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in commit 13194b2. The issue was that useInboxSocket had its own internal hasLoadedRef that never got set to true, causing all socket events to be dropped.

Fix: Modified the hook to accept an optional external hasLoadedRef parameter:

// useInboxSocket.ts
export function useInboxSocket({ driveId, hasLoadedRef: externalRef }: UseInboxSocketOptions = {}) {
  const internalRef = useRef(false);
  const hasLoadedRef = externalRef ?? internalRef;
  // ...
}
// InboxCenterList.tsx
useInboxSocket({ driveId, hasLoadedRef });

Now the component's hasLoadedRef (which is set to true in the SWR onSuccess callback) is shared with the socket hook, so socket events are properly processed after initial data loads.

- Remove dead code referencing 'messages-conversation' in useTabMeta.ts
  (type was removed from PathType during route refactor)
- Add error handling for fetchMessages in channel page (check res.ok)
- Add response status check in handleRefresh (check res.ok)
- Wire useInboxSocket to use component's hasLoadedRef for proper socket
  event processing after initial data load
@2witstudios

Copy link
Copy Markdown
Owner Author

Review Feedback Addressed

I've addressed all the review comments in commit 13194b2:

1. TypeScript Error Fix (CI Blocker)

  • File: apps/web/src/hooks/useTabMeta.ts
  • Issue: Dead code checking for 'messages-conversation' type that no longer exists in PathType
  • Fix: Removed the obsolete conditional block (lines 147-154) since inbox types are now handled by getStaticTabMeta()

2. CodeRabbit: Error handling in fetchMessages

  • File: apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx:83-92
  • Fix: Added try/catch wrapper, check res.ok before parsing JSON, set messages to empty array on error with console logging

3. CodeRabbit: Status check in handleRefresh

  • File: apps/web/src/app/dashboard/inbox/channel/[pageId]/page.tsx:195-203
  • Fix: Added res.ok check before parsing JSON response

4. Codex: Wire inbox socket updates into center list

  • Files: apps/web/src/hooks/useInboxSocket.ts, apps/web/src/components/inbox/InboxCenterList.tsx
  • Issue: The hook had its own internal hasLoadedRef that never got set to true, causing socket events to be dropped
  • Fix: Modified hook to accept optional external hasLoadedRef parameter, updated InboxCenterList to pass its ref to the hook

All changes verified with:

  • TypeScript compiles: pnpm --filter web typecheck ✓
  • Lint passes: pnpm --filter web lint ✓
  • Tab-title tests pass: 30/30 ✓

Addresses CodeRabbit nitpick: socket updates could add items that also
appear in the next pagination page, causing visual duplicates.
@2witstudios

Copy link
Copy Markdown
Owner Author

Nitpick Comments Addressed

1. InboxCenterList deduplication (commit 4f8d1e0) ✅
Fixed the loadMore function to deduplicate items, preventing visual duplicates when socket events race with pagination:

setAllItems((prev) => {
  const existingIds = new Set(prev.map(item => `${item.type}-${item.id}`));
  const newItems = moreData.items.filter(
    item => !existingIds.has(`${item.type}-${item.id}`)
  );
  return [...prev, ...newItems];
});

2. handleRemoveReaction stale closure - Intentionally not addressed
The function already recreates on messages changes (it's in the dep array). The edge case (rapid reaction + message update) has minimal impact - worst case the rollback doesn't find the exact reaction. Adding a useRef would add complexity for a theoretical concern.

3. Optimistic UI for DM sent messages - Deferred
This is a UX enhancement for consistency with channels. Socket responses are typically fast (<100ms). Can be a follow-up enhancement but out of scope for this refactoring PR.

@2witstudios
2witstudios merged commit f308298 into master Feb 3, 2026
3 checks passed
@2witstudios
2witstudios deleted the claude/redesign-inbox-layout-vkldN branch February 6, 2026 01:27
2witstudios added a commit that referenced this pull request Sep 22, 2026
fix(ai): meter /btw side questions, keep rejected queued sends, close CodeQL #345
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.

2 participants