Commit Graph
7 Commits
Author SHA1 Message Date
Jannis Braun d793ab8f78 fix(message-list): sticky pagination skeleton on channel switch
Two cooperating bugs let the pagination skeleton stick at the top of the
chat across channel switches: (1) the browser's post-clamp scroll event
on channel switch fired the load-more block on the new channel, and
(2) useDelayedLoading's threshold timer refreshed displayStart on every
fire, extending the minDisplay window unboundedly when isLoading cycled.

- useDelayedLoading: stamp displayStart only on the false→true show
  transition (functional setShow form to avoid stale closures).
- MessageList: suppressNextLoadMoreRef armed in Effect 3, consumed by
  the load-more block on the next scroll event, with a 250 ms fallback
  disarm so legitimate user scrolls aren't silently dropped. Suppression
  scoped to the load-more block only.
- Regression test for the displayStart refresh bug.
- Updated docs/systems/message-list.md history.
2026-05-05 19:56:26 +02:00
Jannis Braun c05c04181d fix(chat): keep scroll container mounted during initial-load skeleton
The initial-load skeleton was rendered as an early return that replaced
the JSX containing `containerRef` / `contentRef`. On slow loads, the
200ms threshold flipped the skeleton on before messages arrived, so when
messages did arrive every scroll-affecting effect (Effect A, the
ResizeObserver, the load handler, scrollend) re-fired exactly once
against null refs and bailed — and never re-attached because no dep
changed when the skeleton finally cleared. Net: chat opened scrolled to
the top instead of the bottom; saved-anchor restore was equally broken.

Render the skeleton as an absolutely-positioned overlay alongside the
(always-mounted) scroll container so all refs stay live across the
loading transition. Encode the constraint in the spec as the
"ContainerRef invariant" so future loading/empty/error UI doesn't
reintroduce the early-return pattern.
2026-05-05 00:19:00 +02:00
Jannis Braun 8ba644fa44 fix(message-list): pagination flag and scroll restore leak across channel switches
Two bugs in handleScroll's loadMoreMessages flow surfaced after the
smooth-scroll race fix.

(1) isLoadingMore stuck across channels. setIsLoadingMore(true) → await
loadMoreMessages → setIsLoadingMore(false) was unguarded. If the user
switched channels during the await, the new channel inherited the flag (same
component instance, same useState slot) and rendered the pagination skeleton
even with no load in flight. Cleared only when the original await resolved
or the component remounted (e.g., navigating to Friends and back).

(2) Wrong-channel scroll restore. The post-await rAF set
container.scrollTop = container.scrollHeight - prevScrollHeight against the
new channel's container with the old channel's prevScrollHeight, yanking
the new channel to a wrong position.

Fix:
- try/finally around the await so setIsLoadingMore(false) always runs.
- currentChannelIdRef tracks the live channelId; capture requestChannelId at
  load start and compare both before scheduling the rAF and inside the rAF
  callback (the 16ms frame gap is enough for a switch).
- Belt-and-suspenders: setIsLoadingMore(false) in the channel-switch effect
  covers the case where the await never resolves (network hang). Without it,
  a stuck await would leave the new channel inheriting the flag indefinitely.

No request cancellation — out of scope; AbortController plumbing through
chatStore is a bigger refactor and the channelId guard already silently
drops stale results.

Spec updated. Smooth-scroll fix from the previous commit untouched.
2026-04-27 18:11:31 +02:00
Jannis Braun b6b830568c fix(message-list): close smooth-scroll-to-bottom race against late-loading media
Smooth scrolls toward the bottom (new-message arrival in Effect A and the
Jump-to-Present click) animate scrollTop over many frames. Each intermediate
handleScroll measurement saw a large distanceFromBottom and flipped
isAtBottomRef to false, closing the Effect B/C gates. Lazy media (avatars,
embeds, Spotify thumbs) finishing mid-animation grew scrollHeight while the
gate was closed, so the smooth scroll landed at its originally-computed
target — leaving the user above the new bottom by ~the height of what loaded.

Fix: typed smoothScrollIntentRef ('bottom' | 'message' | null) with an 800ms
deadline. handleScroll suppresses the at-bottom flip while intent is 'bottom'
and the user hasn't wheeled past the 5000px nearBottom threshold. Effect D
fires a final defensive instant pin via native scrollend (Chrome 114+,
Safari 18+) or a setTimeout(800) fallback. 'message' intent (jump-to-message
from search) does NOT suppress — the gate flips honestly so the user is left
at the targeted message.

Verified live on nova.ddns.net Orbit → general: Jump-to-Present
lands flush at bottom; new Spotify-link messages stay at bottom as embeds
arrive via WS. docs/systems/message-list.md updated.
2026-04-27 17:51:55 +02:00
Jannis Braun 9aa40c0304 docs: refresh stale embed renderer descriptions after Task 1
Final-review reviewer flagged two minor staleness items:
- embeds.md §10 ImageEmbed bullets still described the pre-Task-1
  shape (no wrapper, no aspect-ratio). Replaced with the actual
  current shape, with an explicit pointer to the Dimension
  reservation contract section that explains why the dims-null
  branch deliberately has no fallback.
- message-list.md said VideoEmbed uses "padding-bottom" without
  noting the direct-video branch uses aspectRatio. Now describes
  both branches explicitly.

No code changes; both are documentation-only touch-ups.
2026-04-25 13:15:14 +02:00
Jannis Braun d659637930 docs(message-list): note smooth-scroll exclusion; point sentinel comment at subsystem doc
Reviewer caught two small gaps after Task 3:
- Effect A's smooth-scroll path on new messages is intentionally NOT
  instrumented with the sentinel (the animation lands asynchronously
  across frames; no intermediate scrollTop is worth pinning to). The
  doc now records this so the reader's intuition matches the code.
- The sentinel-branch comment in MessageList.tsx pointed at "spec §2",
  which is the planning doc rather than the durable subsystem spec.
  Pointed at docs/systems/message-list.md instead.
2026-04-25 13:11:05 +02:00
Jannis Braun 4b040114ed docs: add docs/systems/message-list.md subsystem spec 2026-04-25 13:06:44 +02:00