From dd8190f506370d258d5e88eb7fd0eea9dd73f0f4 Mon Sep 17 00:00:00 2001 From: Lotus Bot Date: Fri, 22 May 2026 18:52:12 -0400 Subject: [PATCH] fix: sent messages not appearing + add Lobby/Auth skeleton loaders - Fix timelineSegments useMemo stale cache: the Perf-5 optimization used timeline.linkedTimelines as its only dep, but that reference never changes when events are added in-place; adding eventsLength as a dep makes it recompute on every new live event so the binary search always finds the new item - Add LobbySkeleton: shimmer placeholder for space lobby (header + hero + room list rows) shown while the Lobby chunk lazy-loads - Add AuthSkeleton: shimmer placeholder for auth pages (logo + server picker + form fields) shown while AuthLayout chunk lazy-loads - Wire both into Router.tsx fallback props Co-Authored-By: Claude Sonnet 4.6 --- src/app/components/AuthSkeleton.tsx | 69 +++++++++++++++ src/app/components/LobbySkeleton.tsx | 111 +++++++++++++++++++++++++ src/app/features/room/RoomTimeline.tsx | 2 +- src/app/pages/Router.tsx | 6 +- 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 src/app/components/AuthSkeleton.tsx create mode 100644 src/app/components/LobbySkeleton.tsx diff --git a/src/app/components/AuthSkeleton.tsx b/src/app/components/AuthSkeleton.tsx new file mode 100644 index 000000000..1ba659ccc --- /dev/null +++ b/src/app/components/AuthSkeleton.tsx @@ -0,0 +1,69 @@ +import React, { useId } from 'react'; + +export function AuthSkeleton() { + const id = useId().replace(/:/g, ''); + const shimmerKeyframes = ` + @keyframes shimmer-${id} { + 0% { background-position: -400px 0; } + 100% { background-position: 400px 0; } + } + `; + + const shimmer = { + background: + 'linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-shine) 50%, var(--skeleton-base) 75%)', + backgroundSize: '800px 100%', + animation: `shimmer-${id} 1.6s ease-in-out infinite`, + borderRadius: '4px', + } as React.CSSProperties; + + return ( + <> + +
+ {/* Card */} +
+ {/* Logo + app name */} +
+
+
+
+ + {/* Server picker */} +
+
+
+
+ + {/* Form fields */} +
+
+
+
+
+
+
+ + ); +} diff --git a/src/app/components/LobbySkeleton.tsx b/src/app/components/LobbySkeleton.tsx new file mode 100644 index 000000000..85eeac014 --- /dev/null +++ b/src/app/components/LobbySkeleton.tsx @@ -0,0 +1,111 @@ +import React, { useId } from 'react'; + +const ROOM_ROWS = [ + { w: '160px', indent: false }, + { w: '120px', indent: true }, + { w: '140px', indent: true }, + { w: '130px', indent: true }, + { w: '150px', indent: false }, + { w: '110px', indent: true }, +]; + +export function LobbySkeleton() { + const id = useId().replace(/:/g, ''); + const shimmerKeyframes = ` + @keyframes shimmer-${id} { + 0% { background-position: -400px 0; } + 100% { background-position: 400px 0; } + } + `; + + const shimmer = { + background: + 'linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-shine) 50%, var(--skeleton-base) 75%)', + backgroundSize: '800px 100%', + animation: `shimmer-${id} 1.6s ease-in-out infinite`, + borderRadius: '4px', + } as React.CSSProperties; + + return ( + <> + +
+ {/* Header — matches LobbyHeader (56px) */} +
+
+
+
+
+
+
+ +
+ {/* Hero — matches PageHero with large avatar + title + subtitle */} +
+
+
+
+
+ + {/* Room list rows */} +
+ {ROOM_ROWS.map((row, i) => ( + // eslint-disable-next-line react/no-array-index-key +
+
+
+
+ ))} +
+
+
+ + ); +} diff --git a/src/app/features/room/RoomTimeline.tsx b/src/app/features/room/RoomTimeline.tsx index fd3aa826d..89f1e73f3 100644 --- a/src/app/features/room/RoomTimeline.tsx +++ b/src/app/features/room/RoomTimeline.tsx @@ -557,7 +557,7 @@ export function RoomTimeline({ room, eventId, roomInputRef, editor }: RoomTimeli base += len; return seg; }); - }, [timeline.linkedTimelines]); + }, [timeline.linkedTimelines, eventsLength]); const liveTimelineLinked = timeline.linkedTimelines[timeline.linkedTimelines.length - 1] === getLiveTimeline(room); const canPaginateBack = diff --git a/src/app/pages/Router.tsx b/src/app/pages/Router.tsx index a70faa521..66b7340fb 100644 --- a/src/app/pages/Router.tsx +++ b/src/app/pages/Router.tsx @@ -8,6 +8,8 @@ import { redirect, } from 'react-router-dom'; import { RoomSkeleton } from '../components/RoomSkeleton'; +import { LobbySkeleton } from '../components/LobbySkeleton'; +import { AuthSkeleton } from '../components/AuthSkeleton'; import { ClientConfig } from '../hooks/useClientConfig'; import { @@ -124,7 +126,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) return null; }} element={ - + }> @@ -309,7 +311,7 @@ export const createRouter = (clientConfig: ClientConfig, screenSize: ScreenSize) + }> }