Skip to content

fix(web): show the room gap marker when only thread replies follow the hole - #58

Merged
zooidbot merged 2 commits into
mainfrom
fix/room-gap-marker-thread-replies
Oct 14, 2026
Merged

zooidbot merged 2 commits into
mainfrom
fix/room-gap-marker-thread-replies

Conversation

@benocodes

Copy link
Copy Markdown
Contributor

Problem

In #product on zooid.zoon.eco, the 06:00 standup thread and the 21:00 nightly-bugs thread were missing from the room view. A newer thread and an older one showed up next to each other with no "Load messages" marker between them.

The room view put a gap marker only before a rendered event inside the timeline right after the hole (use-timeline.ts). In an agent room that timeline is usually:

  • all thread replies, which the room view filters out, and
  • replies whose roots were fetched separately (ensureRootFetched). Those roots belong to no timeline, so getTimelineForEvent returns nothing.

With no anchor, the marker was dropped. "Load more" pages the oldest timeline (#54), so it never filled the hole either.

Fix

roomGapBeforeEventIds puts each gap marker before the first rendered event below the hole:

  • for an event that belongs to a timeline: that timeline is the gap's timeline or a later one. Ordering by timeline means same-millisecond timestamps can't break it.
  • for a fetched root with no timeline: its timestamp is later than everything loaded above the hole.

This matches the "this timeline or any later one" rule ZNC103 (#55) added for the thread view. Holes that resolve to the same event share one marker.

Tests

  • New unit tests in use-timeline.test.tsx:
    • the #product repro: replies only after the hole, root fetched from inside it. This one fails on main.
    • a hole with only replies after it, before a later top-level message
    • no anchor on a fetched root that's older than the hole
  • Full web suite: 984 pass. Typecheck clean.
  • New Storybook scene Structures/TimelinePanel › WithHistoryGapBeforeFetchedThread, checked in light and dark mode. The marker sits between the two threads.

🤖 Generated with Claude Code

…e hole

The room view anchored a gap marker only to a rendered event inside the
timeline right after the hole. In agent rooms that timeline is usually
all thread replies (filtered out of the room view), and their roots are
fetched on their own and sit in no timeline, so the marker was dropped
and whole threads vanished with no indication.

Anchor to the first rendered event below the hole instead: one in the
gap's timeline or a later one, or a fetched root newer than everything
loaded above it. This mirrors what ZNC103 did for the thread view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@zooidbot zooidbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed against ZNC103 (D3 anchor rule, D4 fill in place) and the surrounding use-timeline.ts / use-fill-gap.ts.

The anchoring change is right. roomGapBeforeEventIds applies the thread view's "this timeline or any later one" rule to the room view, and the timestamp fallback for fetched roots is the only signal there is for an event that belongs to no timeline. The repro test fails on main and the dedupe test covers two holes that resolve to one anchor. No objections to the hook.

Blocking: the marker this PR adds can't be clicked to fill the hole

The PR doesn't touch use-fill-gap.ts, and fillGap starts like this:

const anchored = room.getUnfilteredTimelineSet().getTimelineForEvent(eventId);
if (!anchored) return;

In the case this PR exists for (the #product repro, test 1, and the WithHistoryGapBeforeFetchedThread story), the anchor is a fetched root that belongs to no timeline. So getTimelineForEvent returns null and the click does nothing: no spinner, no pagination, and the marker never retires. "Load more" pages the oldest timeline, so it doesn't fill the hole either. We'd go from no marker to a marker that does nothing when clicked. ZNC103 D4 (inherited from clients#22) says a marker fills its hole in place and retires on join.

Fix: have fillGap paginate the hole, not the anchor's timeline. Smallest change: when the anchor has no timeline, pick the gap-start timeline nearest above it. That's the latest gap whose hole start (holeStart as computed in roomGapBeforeEventIds) is before the anchor's ts. A gap-start timeline has no backward neighbour, so it's already its own chain head. Do it either by sharing that resolution from use-timeline.ts or by returning an anchor → gap timeline mapping next to gapBeforeEventIds, whichever reads cleaner. Keep the existing chain-head path for anchors that do have a timeline (room and thread views both rely on it).

Test: a use-fill-gap.test.tsx case with a fetched-root anchor (same setup as test 1) that asserts paginateEventTimeline gets called with the gap timeline, backwards. Ideally a second step joins the timelines and checks that gapBeforeEventIds goes empty.

Non-blocking

  • Test 3 (fetched root older than the hole, only replies after it) correctly shows no room marker per D3. The hole is still real for that thread, and the thread view's marker covers it. Nothing to change.

… thread root

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

@zooidbot zooidbot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed dca0afe. The blocking finding is fixed. When the anchor has no timeline, fillGap falls back to gapTimelineAbove, and holeStart is now shared, so the marker and the fill find the same hole. Anchors that do have a timeline still take the chain-head path. The new use-fill-gap test covers the fetched-root anchor end to end: it paginates the gap timeline backwards, and the marker goes away once the timelines join. No blocking findings. Merging.

@zooidbot
zooidbot merged commit 3ef6d21 into main Oct 14, 2026
1 check passed
@zooidbot
zooidbot deleted the fix/room-gap-marker-thread-replies branch October 14, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants