mirror of
https://github.com/block/buzz.git
synced 2026-11-09 02:41:57 +00:00
perf(desktop): bound warm timeline work
Skip redundant fresh-window catch-up on warm navigation, memoize stable rows, and narrow the retained DOM to the active viewport and tail. Strengthen the warm-switch and scroll regression gates, including buffered live-tail recovery. Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com>
This commit is contained in:
@@ -229,6 +229,7 @@ export function resolveThreadReplyTarget(
|
||||
}
|
||||
|
||||
export const CHANNEL_TIMELINE_GC_TIME_MS = 60 * 60 * 1_000;
|
||||
export const CHANNEL_TIMELINE_STALE_TIME_MS = 5 * 60 * 1_000;
|
||||
|
||||
export function useChannelWindowQuery(channel: Channel | null) {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -372,7 +373,7 @@ export function useChannelMessagesQuery(channel: Channel | null) {
|
||||
signal,
|
||||
);
|
||||
},
|
||||
staleTime: 5 * 60 * 1_000,
|
||||
staleTime: CHANNEL_TIMELINE_STALE_TIME_MS,
|
||||
gcTime: CHANNEL_TIMELINE_GC_TIME_MS,
|
||||
});
|
||||
}
|
||||
@@ -498,19 +499,27 @@ export function useChannelSubscription(channel: Channel | null) {
|
||||
}
|
||||
|
||||
cleanup = dispose;
|
||||
// The live subscription starts at "now", so it cannot close the gap
|
||||
// between the last page snapshot and subscription establishment. Always
|
||||
// refresh after the subscription is active; freshness alone is not a
|
||||
// proof that no relay events landed in that interval.
|
||||
void refreshNewestWindow().catch((error) => {
|
||||
if (!isDisposed) {
|
||||
console.error(
|
||||
"Failed to refresh channel window after subscribing",
|
||||
channelId,
|
||||
error,
|
||||
);
|
||||
}
|
||||
});
|
||||
// A fresh cached window already covers ordinary warm navigation. Only
|
||||
// close the page-to-live gap when the snapshot is absent or old;
|
||||
// reconnects remain an explicit unconditional catch-up above.
|
||||
const queryState = queryClient.getQueryState<RelayEvent[]>(
|
||||
channelMessagesKey(channelId),
|
||||
);
|
||||
const snapshotIsFresh =
|
||||
queryState?.data !== undefined &&
|
||||
Date.now() - queryState.dataUpdatedAt <
|
||||
CHANNEL_TIMELINE_STALE_TIME_MS;
|
||||
if (!snapshotIsFresh) {
|
||||
void refreshNewestWindow().catch((error) => {
|
||||
if (!isDisposed) {
|
||||
console.error(
|
||||
"Failed to refresh channel window after subscribing",
|
||||
channelId,
|
||||
error,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to subscribe to channel", channelId, error);
|
||||
@@ -523,7 +532,7 @@ export function useChannelSubscription(channel: Channel | null) {
|
||||
void cleanup();
|
||||
}
|
||||
};
|
||||
}, [channelId, channelType]);
|
||||
}, [channelId, channelType, queryClient]);
|
||||
}
|
||||
|
||||
export function useSendMessageMutation(
|
||||
|
||||
@@ -739,7 +739,9 @@ const MessageTimelineBase = React.forwardRef<
|
||||
{useTimelineVirtualizer && timelineList ? (
|
||||
<div
|
||||
className="h-full min-h-0 w-full"
|
||||
data-live-message-count={messages.length}
|
||||
data-render-pending={isRenderPending ? "true" : undefined}
|
||||
data-rendered-message-count={renderedMessages.length}
|
||||
>
|
||||
{timelineList}
|
||||
</div>
|
||||
@@ -844,7 +846,7 @@ const MessageTimelineBase = React.forwardRef<
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!isAtBottom ? (
|
||||
{!isAtBottom || bufferedTimeline.pendingCount > 0 ? (
|
||||
<div
|
||||
className={cn(
|
||||
"pointer-events-none absolute inset-x-0 bottom-4 z-50 flex justify-center px-4",
|
||||
|
||||
@@ -91,7 +91,7 @@ type MessageRowItemProps = {
|
||||
videoReviewContext: ReturnType<typeof buildVideoReviewContextForMessage>;
|
||||
};
|
||||
|
||||
export function MessageRowItem({
|
||||
export const MessageRowItem = React.memo(function MessageRowItem({
|
||||
channelId,
|
||||
currentPubkey,
|
||||
entry,
|
||||
@@ -122,6 +122,13 @@ export function MessageRowItem({
|
||||
unfollowThreadById,
|
||||
videoReviewContext,
|
||||
}: MessageRowItemProps) {
|
||||
if (import.meta.env.MODE === "e2e" && typeof window !== "undefined") {
|
||||
const probe = window as unknown as {
|
||||
__TIMELINE_ROW_RENDER_COUNT__?: number;
|
||||
};
|
||||
probe.__TIMELINE_ROW_RENDER_COUNT__ =
|
||||
(probe.__TIMELINE_ROW_RENDER_COUNT__ ?? 0) + 1;
|
||||
}
|
||||
const { message, summary } = entry;
|
||||
const canManage = canManageMessageForCurrentUser(
|
||||
message,
|
||||
@@ -225,4 +232,4 @@ export function MessageRowItem({
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,11 +14,11 @@ export function nextRetainedTimelineKeys(
|
||||
const offset = list.scrollOffset;
|
||||
const indexAt = (target: number) =>
|
||||
list.findItemIndex(Math.min(list.scrollSize, Math.max(0, target)));
|
||||
const admissionStart = indexAt(offset - viewportSize * 8);
|
||||
const admissionEnd = indexAt(offset + viewportSize * 9);
|
||||
const evictionStart = indexAt(offset - viewportSize * 12);
|
||||
const evictionEnd = indexAt(offset + viewportSize * 13);
|
||||
const tailStart = indexAt(list.scrollSize - viewportSize * 3);
|
||||
const admissionStart = indexAt(offset - viewportSize * 2);
|
||||
const admissionEnd = indexAt(offset + viewportSize * 2);
|
||||
const evictionStart = indexAt(offset - viewportSize * 3);
|
||||
const evictionEnd = indexAt(offset + viewportSize * 3);
|
||||
const tailStart = indexAt(list.scrollSize - viewportSize);
|
||||
const next = new Set<string>();
|
||||
|
||||
for (let index = evictionStart; index <= evictionEnd; index += 1) {
|
||||
|
||||
@@ -67,20 +67,26 @@ it("does not keep the full timeline mounted before the viewport is measured", as
|
||||
const root = createRoot(document.getElementById("root"));
|
||||
await act(async () => root.render(React.createElement(Harness)));
|
||||
|
||||
assert.equal(retention.retainedIndices.length, 100);
|
||||
assert.equal(retention.retainedIndices[0], 9_900);
|
||||
assert.equal(retention.retainedIndices.length, 8);
|
||||
assert.equal(retention.retainedIndices[0], 9_992);
|
||||
assert.equal(retention.retainedIndices.at(-1), 9_999);
|
||||
|
||||
await act(async () => initialRefresh());
|
||||
assert.ok(retention.retainedIndices.length > 0);
|
||||
assert.ok(retention.retainedIndices.length < 500);
|
||||
assert.equal(retention.retainedIndices.length, 51);
|
||||
assert.ok(retention.retainedIndices.includes(4_980));
|
||||
assert.ok(retention.retainedIndices.includes(5_000));
|
||||
assert.ok(retention.retainedIndices.includes(5_020));
|
||||
assert.ok(!retention.retainedIndices.includes(4_979));
|
||||
assert.ok(!retention.retainedIndices.includes(5_021));
|
||||
assert.ok(retention.retainedIndices.includes(9_990));
|
||||
assert.ok(retention.retainedIndices.includes(9_999));
|
||||
|
||||
await act(async () => retention.onScrollEnd());
|
||||
assert.ok(retention.retainedIndices.length > 0);
|
||||
assert.ok(retention.retainedIndices.length < 500);
|
||||
assert.equal(retention.retainedIndices.length, 51);
|
||||
assert.ok(retention.retainedIndices.includes(4_980));
|
||||
assert.ok(retention.retainedIndices.includes(5_000));
|
||||
assert.ok(retention.retainedIndices.includes(5_020));
|
||||
assert.ok(retention.retainedIndices.includes(9_990));
|
||||
assert.ok(retention.retainedIndices.includes(9_999));
|
||||
|
||||
await act(async () => root.unmount());
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import type { VListHandle } from "virtua";
|
||||
import { nextRetainedTimelineKeys } from "./timelineRetention";
|
||||
|
||||
const INITIAL_RETAINED_TAIL_SIZE = 100;
|
||||
const INITIAL_RETAINED_TAIL_SIZE = 8;
|
||||
|
||||
export function useTimelineRetention(
|
||||
keys: readonly string[],
|
||||
|
||||
@@ -5139,10 +5139,6 @@ async function handleGetChannelWindow(
|
||||
return relayQuery(config, [filter]);
|
||||
};
|
||||
|
||||
if (!args.cursor) {
|
||||
return execute();
|
||||
}
|
||||
|
||||
const probe = window as unknown as {
|
||||
__CHANNEL_WINDOW_FETCH_COUNT__?: number;
|
||||
__CHANNEL_WINDOW_INFLIGHT__?: number;
|
||||
@@ -5151,6 +5147,10 @@ async function handleGetChannelWindow(
|
||||
probe.__CHANNEL_WINDOW_FETCH_COUNT__ =
|
||||
(probe.__CHANNEL_WINDOW_FETCH_COUNT__ ?? 0) + 1;
|
||||
|
||||
if (!args.cursor) {
|
||||
return execute();
|
||||
}
|
||||
|
||||
const delayMs = getConfig()?.mock?.channelWindowDelayMs ?? 0;
|
||||
if (delayMs <= 0) {
|
||||
return execute();
|
||||
|
||||
Reference in New Issue
Block a user