diff --git a/desktop/src/features/channels/ui/ChannelPane.tsx b/desktop/src/features/channels/ui/ChannelPane.tsx index f89245e535..c6c4ea58ab 100644 --- a/desktop/src/features/channels/ui/ChannelPane.tsx +++ b/desktop/src/features/channels/ui/ChannelPane.tsx @@ -651,7 +651,8 @@ export const ChannelPane = React.memo(function ChannelPane({ ) : (
diff --git a/desktop/src/features/messages/lib/timelineItems.test.mjs b/desktop/src/features/messages/lib/timelineItems.test.mjs index 58ea21722b..54b6319f9d 100644 --- a/desktop/src/features/messages/lib/timelineItems.test.mjs +++ b/desktop/src/features/messages/lib/timelineItems.test.mjs @@ -2,7 +2,11 @@ import assert from "node:assert/strict"; import test from "node:test"; import { KIND_SYSTEM_MESSAGE } from "@/shared/constants/kinds"; -import { buildTimelineItems, getTimelineItemKey } from "./timelineItems.ts"; +import { + buildTimelineDayGroups, + buildTimelineItems, + getTimelineItemKey, +} from "./timelineItems.ts"; function dayAt(year, month, day, hour = 12) { return Math.floor( @@ -100,3 +104,42 @@ test("getTimelineItemKey: keys are unique across the stream", () => { const keys = items.map(getTimelineItemKey); assert.equal(new Set(keys).size, keys.length); }); + +test("buildTimelineDayGroups: moves non-day rows under their day section", () => { + const entries = [ + entry({ id: "d1a", createdAt: dayAt(2026, 6, 12) }), + entry({ id: "d1b", createdAt: dayAt(2026, 6, 12, 13) }), + entry({ id: "d2a", createdAt: dayAt(2026, 6, 13) }), + entry({ id: "d2b", createdAt: dayAt(2026, 6, 13, 13) }), + ]; + const { items } = buildTimelineItems(entries, "d2b"); + + const groups = buildTimelineDayGroups(items); + + assert.equal(groups.length, 2); + assert.deepEqual( + groups.map((group) => group.items.map((item) => item.kind)), + [ + ["message", "message"], + ["message", "unread-divider", "message"], + ], + ); + assert.ok(groups.every((group) => group.headingTimestamp !== null)); +}); + +test("buildTimelineDayGroups: preserves leading rows without a day divider", () => { + const leadingRows = [ + { kind: "unread-divider", key: "unread-a" }, + { kind: "message", key: "a", entry: entry({ id: "a" }) }, + ]; + + const groups = buildTimelineDayGroups(leadingRows); + + assert.deepEqual(groups, [ + { + key: "day-undated", + headingTimestamp: null, + items: leadingRows, + }, + ]); +}); diff --git a/desktop/src/features/messages/lib/timelineItems.ts b/desktop/src/features/messages/lib/timelineItems.ts index bb20ac2163..a4cacd2b84 100644 --- a/desktop/src/features/messages/lib/timelineItems.ts +++ b/desktop/src/features/messages/lib/timelineItems.ts @@ -30,6 +30,14 @@ export type TimelineItemsResult = { items: TimelineItem[]; }; +export type TimelineNonDayItem = Exclude; + +export type TimelineDayGroup = { + key: string; + headingTimestamp: number | null; + items: TimelineNonDayItem[]; +}; + /** Stable per-item key, unique across the flattened stream. */ export function getTimelineItemKey(item: TimelineItem): string { return item.key; @@ -84,3 +92,35 @@ export function buildTimelineItems( return { items }; } + +export function buildTimelineDayGroups( + items: readonly TimelineItem[], +): TimelineDayGroup[] { + const groups: TimelineDayGroup[] = []; + let currentGroup: TimelineDayGroup | null = null; + + for (const item of items) { + if (item.kind === "day-divider") { + currentGroup = { + key: item.key, + headingTimestamp: item.headingTimestamp, + items: [], + }; + groups.push(currentGroup); + continue; + } + + if (!currentGroup) { + currentGroup = { + key: "day-undated", + headingTimestamp: null, + items: [], + }; + groups.push(currentGroup); + } + + currentGroup.items.push(item); + } + + return groups; +} diff --git a/desktop/src/features/messages/ui/DayDivider.tsx b/desktop/src/features/messages/ui/DayDivider.tsx index e5007a709b..8dff4b7a06 100644 --- a/desktop/src/features/messages/ui/DayDivider.tsx +++ b/desktop/src/features/messages/ui/DayDivider.tsx @@ -2,11 +2,11 @@ export function DayDivider({ label }: { label: string }) { return (
-

+

{label}

diff --git a/desktop/src/features/messages/ui/MessageThreadPanel.tsx b/desktop/src/features/messages/ui/MessageThreadPanel.tsx index a146c76cea..6df725a259 100644 --- a/desktop/src/features/messages/ui/MessageThreadPanel.tsx +++ b/desktop/src/features/messages/ui/MessageThreadPanel.tsx @@ -797,7 +797,7 @@ export function MessageThreadPanel({ const threadFooter = ( <> {!isAtBottom ? ( -
+