Make conversation queues mutually exclusive

This commit is contained in:
Jamie Brunton committed 2026-08-14 09:25:45 +01:00
1 parent 3bb1dda6bb
commit 2447563c60
20 files changed
+386 -220

No files matched your search

@@ -21,12 +21,12 @@ import {
CONVERSATION_SORTS,
DEFAULT_CONVERSATION_FILTERS,
accountOptions,
conversationQueue,
filterCounts,
isUnread,
manualFlowLabel,
needsReply,
partitionConversations,
triageStatus,
type ConversationFilter,
type ConversationFilters,
type ConversationSort,
@@ -87,16 +87,19 @@ function sellerScanSummary(result: SellerScanResponse): string {
return `${parts.join(' · ')}.`;
}
/** Independent state chips. A conversation can be several of these at once. */
/** One workflow chip plus independent presentation/ownership badges. */
function conversationChips(conversation: ActiveChat, now: number): ConversationChip[] {
const chips: ConversationChip[] = [];
if (conversation.recipientAccountStatus === 'deleted') {
chips.push({ label: 'Account deleted' });
return chips;
switch (conversationQueue(conversation)) {
case 'needs-reply': chips.push({ label: 'Needs attention' }); break;
case 'awaiting-reply': chips.push({ label: 'Waiting' }); break;
case 'sellers': chips.push({ label: 'Seller' }); break;
case 'snoozed': chips.push({ label: 'Snoozed' }); break;
case 'done': chips.push({ label: 'Done' }); break;
case 'account-deleted': chips.push({ label: 'Account deleted' }); break;
case 'archived': chips.push({ label: 'Archived' }); break;
}
if (isUnread(conversation)) chips.push({ label: 'Unread' });
if (needsReply(conversation)) chips.push({ label: 'Needs reply' });
else if (conversation.isFromUs && triageStatus(conversation) === 'open') chips.push({ label: 'Waiting on them' });
const manualLabel = manualFlowLabel(conversation);
if (manualLabel) {
chips.push({
@@ -107,10 +110,8 @@ function conversationChips(conversation: ActiveChat, now: number): ConversationC
: manualLabel,
});
}
const status = triageStatus(conversation);
if (status === 'snoozed') chips.push({ label: 'Snoozed' });
if (status === 'done') chips.push({ label: 'Done' });
if (needsReply(conversation) && conversationAge(conversation.lastMessageTime, now) !== 'fresh') {
if (conversationQueue(conversation) === 'needs-reply'
&& conversationAge(conversation.lastMessageTime, now) !== 'fresh') {
chips.push({ label: 'Overdue' });
}
return chips;
@@ -133,7 +134,6 @@ function ConversationRow({
redditUsernameLabel(conversation.username),
...chips.map((chip) => chip.hint ?? chip.label),
relativeTime,
...(conversation.sellerDetected ? ['Seller'] : []),
...(conversation.hasReceivedImages ? ['Image received'] : []),
...(conversation.readOnly ? ['Removed account, read-only'] : []),
];
@@ -186,7 +186,6 @@ function ConversationRow({
Read-only
</span>
) : null}
{conversation.sellerDetected ? <span className="conversation-seller-marker">Seller</span> : null}
{conversation.hasReceivedImages ? <span className="conversation-image-marker">Image received</span> : null}
<span className="conversation-list-profile"><RedditUsernameLink username={conversation.username} /></span>
</span>
@@ -235,7 +234,7 @@ export function ConversationsPage({
const allConversations = conversationsQuery.data ?? [];
const { visible: liveConversations, hiddenSellerCount } = useMemo(
const { visible: liveConversations } = useMemo(
() => partitionConversations(allConversations, activeFilters),
[allConversations, activeFilters],
);
@@ -514,11 +513,27 @@ export function ConversationsPage({
) : null}
<label className="conversation-seller-toggle">
<input
checked={activeFilters.showSellers}
onChange={(event) => updateFilters({ ...activeFilters, showSellers: event.target.checked })}
checked={activeFilters.unreadOnly}
onChange={(event) => updateFilters({ ...activeFilters, unreadOnly: event.target.checked })}
type="checkbox"
/>
Show sellers
Unread only
</label>
<label className="conversation-seller-toggle">
<input
checked={activeFilters.manualOnly}
onChange={(event) => updateFilters({ ...activeFilters, manualOnly: event.target.checked })}
type="checkbox"
/>
Manual only
</label>
<label className="conversation-seller-toggle">
<input
checked={activeFilters.imagesOnly}
onChange={(event) => updateFilters({ ...activeFilters, imagesOnly: event.target.checked })}
type="checkbox"
/>
Has images
</label>
</div>
{visibleConversations.length > 0 ? (
@@ -577,9 +592,6 @@ export function ConversationsPage({
<div className="conversation-list-state">
<strong>No matching conversations.</strong>
<span>Try a different search or filter.</span>
{hiddenSellerCount > 0 ? (
<span>{hiddenSellerCount} hidden by the seller filter.</span>
) : null}
</div>
) : (
<>
@@ -605,9 +617,6 @@ export function ConversationsPage({
})}
</ul>
<p className="conversation-list-footer">
{hiddenSellerCount > 0
? `${hiddenSellerCount} hidden by the seller filter.`
: null}
<span className="conversation-shortcut-hint">
j/k move · Enter opens · u toggles unread · e marks done · / searches
</span>