From 2ffe51180ba6d74427ee915e4e2bff8333f0614c Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Mon, 27 Jul 2026 13:38:45 -0700 Subject: [PATCH] Fix new messages indicator when coming back to the tab --- src/routes/spaces/[relay]/[h]/+page.svelte | 38 +++++++++++++++------ src/routes/spaces/[relay]/chat/+page.svelte | 38 +++++++++++++++------ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/routes/spaces/[relay]/[h]/+page.svelte b/src/routes/spaces/[relay]/[h]/+page.svelte index e89c61f2..0f238231 100644 --- a/src/routes/spaces/[relay]/[h]/+page.svelte +++ b/src/routes/spaces/[relay]/[h]/+page.svelte @@ -57,8 +57,6 @@ import {pushToast} from "@app/toast" const {h, relay} = $page.params as MakeNonOptional - const mounted = now() - const lastChecked = $checked[$page.url.pathname] const url = decodeRelay(relay) const room = deriveRoom(url, h) const isVoiceRoom = $derived(getRoomType($room) === RoomType.Voice) @@ -292,6 +290,16 @@ } } + const onVisibilityChange = () => { + if (document.hidden) { + lastVisibleAt = now() + } else if ($events.some(e => e.pubkey !== $pubkey && e.created_at > lastVisibleAt)) { + newMessagesAfter = lastVisibleAt + newMessagesBefore = now() + newMessagesSeen = false + } + } + let joining = $state(false) let leaving = $state(false) let userHasScrolled = $state(false) @@ -302,6 +310,9 @@ let share = $state(popKey("share")) let parent: TrustedEvent | undefined = $state() let element: HTMLElement | undefined = $state() + let lastVisibleAt = now() + let newMessagesAfter = $state($checked[$page.url.pathname]) + let newMessagesBefore = $state(now()) let newMessagesSeen = false let showFixedNewMessages = $state(false) let showScrollButton = $state(false) @@ -327,9 +338,11 @@ if (events) { const lastUserEvent = $events.findLast(e => e.pubkey === $pubkey) - // Adjust last checked to account for messages that came from a different device - const adjustedLastChecked = - lastChecked && lastUserEvent ? Math.max(lastUserEvent.created_at, lastChecked) : lastChecked + // Adjust the boundary to account for messages that came from a different device + const adjustedAfter = + newMessagesAfter && lastUserEvent + ? Math.max(lastUserEvent.created_at, newMessagesAfter) + : newMessagesAfter for (const event of $events) { if (seen.has(event.id)) { @@ -340,10 +353,10 @@ if ( !newMessagesSeen && - adjustedLastChecked && + adjustedAfter && event.pubkey !== $pubkey && - event.created_at > adjustedLastChecked && - event.created_at < mounted + event.created_at > adjustedAfter && + event.created_at < newMessagesBefore ) { elements.push({type: "new-messages", id: "new-messages"}) newMessagesSeen = true @@ -424,8 +437,13 @@ onMount(() => { start() - // Wrap in a closure to avoid calling a stale cleanup function - return () => cleanup?.() + document.addEventListener("visibilitychange", onVisibilityChange) + + return () => { + // Wrap in a closure to avoid calling a stale cleanup function + cleanup?.() + document.removeEventListener("visibilitychange", onVisibilityChange) + } }) diff --git a/src/routes/spaces/[relay]/chat/+page.svelte b/src/routes/spaces/[relay]/chat/+page.svelte index 0bf01237..a17d4345 100644 --- a/src/routes/spaces/[relay]/chat/+page.svelte +++ b/src/routes/spaces/[relay]/chat/+page.svelte @@ -36,8 +36,6 @@ import {makeFeed} from "@app/feeds" import {popKey} from "@lib/implicit" - const mounted = now() - const lastChecked = $checked[$page.url.pathname] const url = decodeRelay($page.params.relay!) const shouldProtect = canEnforceNip70(url) const at = $derived(parseInt($page.url.searchParams.get("at")!)) @@ -171,6 +169,16 @@ } } + const onVisibilityChange = () => { + if (document.hidden) { + lastVisibleAt = now() + } else if ($events.some(e => e.pubkey !== $pubkey && e.created_at > lastVisibleAt)) { + newMessagesAfter = lastVisibleAt + newMessagesBefore = now() + newMessagesSeen = false + } + } + let loadingBackward = $state(true) let loadingForward = $state(true) let userHasScrolled = $state(false) @@ -179,6 +187,9 @@ let share = $state(popKey("share")) let parent: TrustedEvent | undefined = $state() let element: HTMLElement | undefined = $state() + let lastVisibleAt = now() + let newMessagesAfter = $state($checked[$page.url.pathname]) + let newMessagesBefore = $state(now()) let newMessagesSeen = false let showFixedNewMessages = $state(false) let showScrollButton = $state(false) @@ -204,9 +215,11 @@ if (events) { const lastUserEvent = $events.findLast(e => e.pubkey === $pubkey) - // Adjust last checked to account for messages that came from a different device - const adjustedLastChecked = - lastChecked && lastUserEvent ? Math.max(lastUserEvent.created_at, lastChecked) : lastChecked + // Adjust the boundary to account for messages that came from a different device + const adjustedAfter = + newMessagesAfter && lastUserEvent + ? Math.max(lastUserEvent.created_at, newMessagesAfter) + : newMessagesAfter for (const event of $events) { if (seen.has(event.id)) { @@ -217,10 +230,10 @@ if ( !newMessagesSeen && - adjustedLastChecked && + adjustedAfter && event.pubkey !== $pubkey && - event.created_at > adjustedLastChecked && - event.created_at < mounted + event.created_at > adjustedAfter && + event.created_at < newMessagesBefore ) { elements.push({type: "new-messages", id: "new-messages"}) newMessagesSeen = true @@ -301,8 +314,13 @@ onMount(() => { start() - // Wrap in a closure to avoid calling a stale cleanup function - return () => cleanup?.() + document.addEventListener("visibilitychange", onVisibilityChange) + + return () => { + // Wrap in a closure to avoid calling a stale cleanup function + cleanup?.() + document.removeEventListener("visibilitychange", onVisibilityChange) + } })