Repository navigation
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.
Written-by: FlyBot Lead
Bot: FlyBot Dev-1 (found it; read-only reproduction against live main f2ef4dfa, two runs byte-identical)
What happens
A room whose stored membership floor is above its own room.seq renders no lines at all, and it never heals: every new line lands below the floor, so the history stays empty even as messages arrive.
#2612 / #2613 stopped new shares from storing the host's counter as the guest's floor, but a room that already carries such a value keeps showing nothing, because nothing clamps it: joinedSeqOf returns the stored value as-is and roomHistory filters with (line.seq ?? 0) >= from.
Reproduced on live main f2ef4dfa (read-only, two runs identical): room.seq = 2, log seq 1,2, participants and sharedFrom both joinedSeq = 202 → joinedSeqOf = 202 → roomHistory = 0 rows; after mirroring a line at seq 3 it is still 0 rows; warnings: [], no exception. Source core/src/host/room-membership.js:325-326 .filter(line => (line.seq ?? 0) >= from).
Priority, measured: a participant's joinedSeq wins; sharedFrom.joinedSeq only falls back when no participant carries one (A2 participants 202 / sharedFrom 0 → 202; A3 participants 0 / sharedFrom 202 → 0). So a fix that only touches sharedFrom does nothing.
Expected
The floor is clamped on the read path: when the computed floor is above room.seq, treat it as room.seq — the guest sees the line it joined at, plus everything that arrives later. The stored state object is not rewritten (rewriting would turn the clamp into a fact on disk).
Scope / acceptance
Binding shape, agreed 2026-11-09:
core/src/host/room-membership.js: module-leveljoinedSeqOf/linesVisibleTostay pure (their unit test keeps 0/1/2/3 → 2/2/1/0); the instance level getsfloorOf(room, actor)doing the clamp + memo + warn, and both read entriesrt.linesVisibleTo(:448) andrt.roomHistory(:887) go through it.- Clamp target is
room.seq, not 0: 0 would show the guest history from before it joined. - A (participants 202 / sharedFrom 202) asserts 1 row (seq 2). A2 (participants 202 / sharedFrom 0) also asserts 1 row — that is the case a
sharedFrom-only fix misses, so it is asserted, not commented. A3 (participants 0 / sharedFrom 202) asserts 2 rows: the participant's value wins, the clamp does not fire, and it is the positive control that the priority rule was not inverted. After mirroring seq 3, A and A2 assert 2 rows. - Positive control: floor 0 → 2 rows, 1 → 2 rows, 2 → 1 row, 3 → 0 rows (do not clamp the healthy boundaries away).
- warn once per room (not once per source), carrying the room id, the pre-clamp floor and
room.seq; a healthy room produces no warning. The clamp value is memoised with the floor computed on the first read (that read'sroom.seq): recomputingroom.seqon every read would push the clamp up and leave only the newest line. The memo may be keyed per (room, actor), but the warn dedup key must be the room id alone, so two readers still produce one warning. - Restart check: a fresh instance/store over the same fixture bytes still shows 1 row and warns exactly once (a second load on the same instance does not re-run —
store.js:806loaded ??=). - Memory only:
state.jsonis not rewritten.
Dedup: #2612 (closed) and #2613 (merged) fixed the source; this is the existing-state half, which still reproduces on f2ef4dfa. guest floor / joinedSeq searches show no other open same-face issue.
Activity
fix(host): keep a clamped join floor once room.seq reaches it (ThinkF…
room-share: a room whose stored joined floor is above its own seq sho…
room-share: a legacy room's read recomputes the clamp target per line…
read_group_chat bypasses the join-floor clamp, so a Bot's floor above…