fix(board-header): keep banner stable during directory route resolution

Key the banner image by route identifier instead of the resolved community address so directory candidates do not reshuffle the banner mid-load.
This commit is contained in:
Tommaso Casaburi
2026-08-01 18:51:28 +02:00
parent a67c66de0d
commit 660cdf8a2d
2 changed files with 30 additions and 2 deletions
@@ -19,6 +19,7 @@ const testState = vi.hoisted(() => ({
offlineIconClass: 'offline',
offlineTitle: 'Board offline',
resolvedAddress: 'music-posting.eth' as string | undefined,
resolvedAddressListeners: [] as Array<() => void>,
shouldShowSnow: false,
stableCommunity: {
address: 'music-posting.eth',
@@ -89,7 +90,17 @@ vi.mock('../../../hooks/use-community-identifiers', () => ({
}));
vi.mock('../../../hooks/use-resolved-community-address', () => ({
useResolvedCommunityAddress: () => testState.resolvedAddress,
useResolvedCommunityAddress: () =>
React.useSyncExternalStore(
(listener) => {
testState.resolvedAddressListeners.push(listener);
return () => {
testState.resolvedAddressListeners = testState.resolvedAddressListeners.filter((candidate) => candidate !== listener);
};
},
() => testState.resolvedAddress,
() => testState.resolvedAddress,
),
}));
vi.mock('../../../hooks/use-is-mobile', () => ({
@@ -136,6 +147,7 @@ describe('BoardHeader', () => {
testState.offlineIconClass = 'offline';
testState.offlineTitle = 'Board offline';
testState.resolvedAddress = 'music-posting.eth';
testState.resolvedAddressListeners = [];
testState.shouldShowSnow = false;
testState.stableCommunity = {
address: 'music-posting.eth',
@@ -203,6 +215,21 @@ describe('BoardHeader', () => {
expect(container.querySelector('img')?.getAttribute('src')).toBe('banner-a.png');
});
it('keeps the banner stable while a directory route resolves different board candidates', async () => {
mathRandomSpy.mockReturnValueOnce(0).mockReturnValue(0.75);
await renderHeader('/biz');
expect(container.querySelector('img')?.getAttribute('src')).toBe('banner-a.png');
await act(async () => {
testState.resolvedAddress = 'bizraelis.bso';
testState.resolvedAddressListeners.forEach((listener) => listener());
});
expect(container.querySelector('img')?.getAttribute('src')).toBe('banner-a.png');
expect(mathRandomSpy).toHaveBeenCalledTimes(1);
});
it('renders hidden special board metadata without a directory entry', async () => {
testState.directories = [];
testState.resolvedAddress = TRASH_BOARD_ADDRESS;
+2 -1
View File
@@ -55,6 +55,7 @@ const BoardHeader = memo(() => {
const isInModView = isModView(location.pathname);
const isInArchiveView = isArchiveRoute(location.pathname);
const isInDirectoryListView = isDirectoryListRoute(location.pathname);
const bannerKey = isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : isInModView ? 'mod' : params.boardIdentifier || 'board';
const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress;
@@ -98,7 +99,7 @@ const BoardHeader = memo(() => {
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
{!useIsMobile() && (
<div className={styles.bannerCnt}>
<ImageBanner key={isInAllView ? 'all' : isInSubscriptionsView ? 'subscriptions' : communityAddress} />
<ImageBanner key={bannerKey} />
</div>
)}
<div className={styles.boardTitle}>