Sort direct messages alphabetically (#1180)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-06-22 13:53:16 -07:00
committed by GitHub
parent 7dcf22d50e
commit 04df2c8dc6
3 changed files with 92 additions and 1 deletions

View File

@@ -0,0 +1,72 @@
import assert from "node:assert/strict";
import test from "node:test";
import { sortDmChannelsByLabel } from "./dmSidebarSort.ts";
function makeDm(id, name) {
return {
archivedAt: null,
channelType: "dm",
description: "",
id,
isMember: true,
lastMessageAt: null,
memberCount: 2,
memberPubkeys: [],
name,
participantPubkeys: [],
participants: [],
purpose: null,
topic: null,
ttlDeadline: null,
ttlSeconds: null,
visibility: "private",
};
}
test("sorts direct messages by resolved display label", () => {
const sorted = sortDmChannelsByLabel(
[
makeDm("c", "Group DM (3)"),
makeDm("a", "Group DM (3)"),
makeDm("b", "Group DM (3)"),
],
{
a: "Fizz",
b: "Brain",
c: "Wes",
},
);
assert.deepEqual(
sorted.map((channel) => channel.id),
["b", "a", "c"],
);
});
test("falls back to channel name until labels resolve", () => {
const sorted = sortDmChannelsByLabel(
[makeDm("b", "Zed"), makeDm("a", "Amy")],
{},
);
assert.deepEqual(
sorted.map((channel) => channel.id),
["a", "b"],
);
});
test("uses channel id as a deterministic tie breaker", () => {
const sorted = sortDmChannelsByLabel(
[makeDm("b", "Group DM (3)"), makeDm("a", "Group DM (3)")],
{
a: "Wes",
b: "Wes",
},
);
assert.deepEqual(
sorted.map((channel) => channel.id),
["a", "b"],
);
});

View File

@@ -0,0 +1,14 @@
import type { Channel } from "@/shared/api/types";
export function sortDmChannelsByLabel(
channels: Channel[],
channelLabels: Record<string, string>,
) {
return [...channels].sort((left, right) => {
const leftLabel = channelLabels[left.id] ?? left.name;
const rightLabel = channelLabels[right.id] ?? right.name;
return (
leftLabel.localeCompare(rightLabel) || left.id.localeCompare(right.id)
);
});
}

View File

@@ -21,6 +21,7 @@ import {
type ChannelSection,
} from "@/features/sidebar/lib/useChannelSections";
import { useDmSidebarMetadata } from "@/features/sidebar/useDmSidebarMetadata";
import { sortDmChannelsByLabel } from "@/features/sidebar/lib/dmSidebarSort";
import { useSidebarScrollLock } from "@/features/sidebar/lib/useSidebarScrollLock";
import { useUnreadOverflow } from "@/features/sidebar/lib/useUnreadOverflow";
import {
@@ -458,6 +459,10 @@ export function AppSidebar({
fallbackDisplayName,
profileDisplayName: profile?.displayName,
});
const sortedDirectMessages = React.useMemo(
() => sortDmChannelsByLabel(directMessages, dmChannelLabels),
[directMessages, dmChannelLabels],
);
const sidebarLoadingShape = useSidebarLoadingShape({
activeWorkspaceId: activeWorkspace?.id,
currentPubkey,
@@ -787,7 +792,7 @@ export function AppSidebar({
dmParticipantsByChannelId={dmParticipantsByChannelId}
isCollapsed={collapsedGroups.directMessages}
isActiveChannel={selectedView === "channel"}
items={directMessages}
items={sortedDirectMessages}
channelLabels={dmChannelLabels}
onHideDm={onHideDm}
onMarkChannelRead={onMarkChannelRead}