Files
ds-chat/frontend/src/lib/messageGrouping.ts
T
ksmithandClaude Sonnet 5.5 62b5eec2fb Show the date on message timestamps that aren't from today
A bare "4:04 PM" gave no way to tell today's message from one sent last
week. Today stays time-only; older messages gain just enough date to
place them: "Yesterday 4:04 PM", a weekday for the past week ("Mon
4:04 PM"), then "Oct 2, 4:04 PM" (with the year if it isn't this one).
Hovering a time shows the full date. All in the viewer's local time.

A new calendar day now always starts a new message group. Previously a
same-sender run that crossed midnight within the 5-minute window (11:58
PM then 12:01 AM) stayed under one header, which would have put the
later message under the wrong day's timestamp. The grouping rule moves
out of MessageList into messageGrouping.ts so it's unit-tested.

useLocalDay refreshes "today" at local midnight and when a hidden tab
becomes visible again, so a tab left open overnight doesn't keep showing
yesterday's messages as a bare time. .message-time no longer wraps, and
.message-author can shrink, so a long display name can't push the longer
label out of the row on a phone.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-09 15:25:22 -06:00

51 lines
2.2 KiB
TypeScript

import { getUserAvatarUrl } from '../api/users'
import { hashIndex } from './avatar'
import { isSameLocalDay } from './formatMessageTime'
import type { RoomMember } from '../types'
// #73: Slack's own threshold for the same "still grouped, but it's been a
// while" call -- past this gap a same-sender message starts a new group
// (its own avatar/name/timestamp) even with nobody else posting in
// between, so a message sent minutes later doesn't hide under a stale
// timestamp from the start of the run.
export const GROUP_BREAK_MS = 5 * 60 * 1000
interface Groupable {
user_id: string
created_at: string
}
// Whether `msg` gets its own avatar/name/timestamp header, vs. continuing
// `prev`'s group. A new calendar day always breaks the group: now that
// timestamps can carry a date, a same-sender run that crosses midnight
// (11:58 PM, then 12:01 AM) would otherwise leave the later message under a
// header showing the wrong day.
export function isGroupStart(prev: Groupable | undefined, msg: Groupable): boolean {
if (!prev || prev.user_id !== msg.user_id) return true
if (new Date(msg.created_at).getTime() - new Date(prev.created_at).getTime() > GROUP_BREAK_MS) return true
return !isSameLocalDay(prev.created_at, msg.created_at)
}
export function senderColorIndex(username: string, members: RoomMember[]): number {
const idx = members.findIndex((m) => m.username === username)
if (idx >= 0) return idx
// Fallback for a sender no longer in the room (e.g. they left): derive a
// stable index from the username instead of always colliding on 0.
return hashIndex(username)
}
export function avatarUrlFor(username: string, members: RoomMember[]): string | null {
const member = members.find((m) => m.username === username)
if (!member?.avatar_filename) return null
return getUserAvatarUrl(member.user_id, member.avatar_filename)
}
export function displayNameFor(username: string, members: RoomMember[]): string {
const member = members.find((m) => m.username === username)
return member?.display_name || username
}
export function statusFor(username: string, members: RoomMember[]): 'online' | 'offline' | undefined {
return members.find((m) => m.username === username)?.status
}