Skip to content

Hard to tell which day you're reading in a busy channel: add a floating day indicator (WhatsApp-style) #77

Description

@maxlamagna

Summary

The UI already inserts day separators into the timeline (maybeInsertDateDivider in static/chat.js: "Today", "Yesterday", full date), which works great while a day fits on screen. But in a busy channel a single day can be hundreds of messages long, so once you scroll into history the separator that tells you which day you're reading is often hundreds of messages above the viewport. Reading back through a long working session, there's no way to tell what day the messages in front of you belong to without scrolling all the way up to find the divider.

Proposal

A transient floating day pill, the way WhatsApp Web does it: while you scroll, a small capsule appears top-centre showing the day of the messages currently at the top of the view, and it fades out about a second after you stop scrolling. It floats over the content (no layout shift, nothing permanently covered, clicks pass straight through it) and only ever paints its own small capsule; the in-flow day separators are untouched.

The floating indicator is a frontend-only additive UI change with no server code or settings; the same PR also includes the targeted per-channel Clear fix below. Outside scrolling, the indicator itself is hidden.

A bug found along the way (fixed in the same PR)

While building this I found that per-channel Clear leaves orphaned day separators behind: the clear loop removes only elements with dataset.id, but dividers are created with only dataset.channel (static/chat.js), and filterMessagesByChannel (static/channels.js) only toggles visibility. So after clearing a channel its separators linger in the DOM, and the next message duplicates one. Reproduce on current main: clear a channel that has messages from a previous day, and the old day's separator is still there. The PR fixes the clear loop to remove matching dividers too; the indicator needs that anyway so it can never show a date from cleared history.

Implementation notes

  • Host is a zero-height position: sticky first child of #timeline: no flow height, stays centred when side panels resize, pointer-events: none and aria-hidden="true" so it can't intercept clicks or reach screen readers, and there is no full-width surface that could cover content.
  • The label scan is cheap and stays cheap on big rooms: it iterates a cached live HTMLCollection of .date-divider elements (follows divider add/remove automatically) at most once per requestAnimationFrame, driven from the existing timeline scroll listener; cost scales with the number of day dividers (tens), not messages (thousands).
  • Stale-state hooks: the label is recomputed (without triggering the show/fade cycle) after channel switch restore and after both clear paths, and the pill hides immediately when no divider is above the viewport.
  • Static asset ?v= cache-busts bumped for the touched files.

Implementation

Implemented in PR #78 (4 static files, 109 additions / 5 deletions; no server changes). Tested locally on a real multi-agent workspace (3000+ messages, 8 channels): node --check passes on both touched JS files; live checks covered correct day while scrolling both directions, fade-out on idle, day handoff at boundaries, channel switch and per-channel clear showing no stale day, click-through, and narrow-window layout.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions