Merge branch 'codex/fix/direct-board-community-loading'

This commit is contained in:
Tommaso Casaburi
2026-05-21 15:27:24 +07:00
7 changed files with 214 additions and 49 deletions
+4
View File
@@ -333,6 +333,10 @@ describe('App', () => {
testState.account = { author: { address: '0x123' } };
testState.accountComments = {};
testState.accountCommunityAddresses = [];
testState.directories = [
{ address: 'music-posting.eth', title: '/mu/ - Music', nsfw: false },
{ address: 'tech-posting.eth', title: '/g/ - Technology', nsfw: false },
];
testState.isMobile = false;
testState.isSpecialEnabled = false;
testState.replyModalState = {
@@ -0,0 +1,132 @@
import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useCommunityIdentifier, useCommunityIdentifiers } from '../use-community-identifiers';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
type TestDirectoryCommunity = {
address: string;
name?: string;
publicKey?: string;
directoryCode?: string;
title?: string;
};
const testState = vi.hoisted(() => ({
address: undefined as string | undefined,
addresses: [] as Array<string | undefined>,
directories: [] as TestDirectoryCommunity[],
}));
vi.mock('../use-directories', () => ({
useDirectories: () => testState.directories,
findDirectoryByAddress: (directories: TestDirectoryCommunity[], address: string | undefined) =>
address
? directories.find((directory) =>
[directory.address, directory.name, directory.publicKey, directory.directoryCode, directory.title].some((identifier) => identifier === address),
)
: undefined,
}));
let latestIdentifier: ReturnType<typeof useCommunityIdentifier>;
let latestIdentifiers: ReturnType<typeof useCommunityIdentifiers>;
let container: HTMLDivElement;
let root: Root;
const SingleHarness = () => {
latestIdentifier = useCommunityIdentifier(testState.address);
return null;
};
const MultiHarness = () => {
latestIdentifiers = useCommunityIdentifiers(testState.addresses);
return null;
};
const renderHarness = async (element: React.ReactElement) => {
await act(async () => {
root.render(element);
});
};
describe('useCommunityIdentifier', () => {
beforeEach(() => {
testState.address = undefined;
testState.addresses = [];
testState.directories = [];
latestIdentifier = undefined;
latestIdentifiers = [];
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
});
it('uses vendored directory public keys before the main directory list has hydrated', async () => {
testState.address = 'animals-and-nature.bso';
await renderHarness(createElement(SingleHarness));
expect(latestIdentifier).toEqual({
name: 'animals-and-nature.bso',
publicKey: '12D3KooWSpKszPM2c17KBgbnoRrkWPCHJosGGFg3bKnzarYhHeSc',
});
});
it('uses candidate public keys for non-primary boards in a directory list', async () => {
testState.address = 'bizraelis.bso';
testState.directories = [
{
address: 'business-and-finance.bso',
directoryCode: 'biz',
publicKey: '12D3KooWNMybS8JqELi38ZBX897PrjWbCrGoMKfw3bgoqzC2n1Dh',
title: '/biz/ - Business & Finance',
},
];
await renderHarness(createElement(SingleHarness));
expect(latestIdentifier).toEqual({
name: 'bizraelis.bso',
publicKey: '12D3KooWR7nTdKZqZ1twGWMfVsXYDGp1XAKUrnYznKP651jFrizE',
});
});
it('keeps unlisted domain routes as direct community names', async () => {
testState.address = 'unlisted-board.bso';
await renderHarness(createElement(SingleHarness));
expect(latestIdentifier).toEqual({ name: 'unlisted-board.bso' });
});
it('keeps raw IPNS route identifiers as public keys', async () => {
testState.address = '12D3KooWExamplePublicKey';
await renderHarness(createElement(SingleHarness));
expect(latestIdentifier).toEqual({ publicKey: '12D3KooWExamplePublicKey' });
});
it('applies the same resolution to feed community arrays', async () => {
testState.addresses = ['bizraelis.bso', 'unlisted-board.bso', undefined, '12D3KooWExamplePublicKey'];
await renderHarness(createElement(MultiHarness));
expect(latestIdentifiers).toEqual([
{
name: 'bizraelis.bso',
publicKey: '12D3KooWR7nTdKZqZ1twGWMfVsXYDGp1XAKUrnYznKP651jFrizE',
},
{ name: 'unlisted-board.bso' },
{ publicKey: '12D3KooWExamplePublicKey' },
]);
});
});
+12 -7
View File
@@ -1,6 +1,7 @@
import { useMemo } from 'react';
import type { CommunityIdentifier } from '@bitsocial/bitsocial-react-hooks';
import { findDirectoryByAddress, type DirectoryCommunity, useDirectories } from './use-directories';
import { getDirectoryCandidateBoardByAddress } from '../lib/utils/directory-list-lookup-utils';
const isLikelyCommunityName = (value: string) => value.includes('.');
@@ -10,20 +11,24 @@ const getCommunityIdentifier = (communityAddress: string | undefined, directorie
}
const directory = findDirectoryByAddress(directories, communityAddress);
if (directory?.name && directory.publicKey) {
const directoryCandidate = getDirectoryCandidateBoardByAddress(communityAddress);
const name = directory?.name ?? directory?.address ?? directoryCandidate?.address;
const publicKey = directory?.publicKey ?? directoryCandidate?.publicKey;
if (name && publicKey) {
return {
name: directory.name,
publicKey: directory.publicKey,
name,
publicKey,
};
}
if (directory?.publicKey) {
if (publicKey) {
return {
publicKey: directory.publicKey,
publicKey,
};
}
if (directory?.name) {
if (name) {
return {
name: directory.name,
name,
};
}
+2 -12
View File
@@ -13,8 +13,10 @@ import {
type DirectoryCommunity,
type DirectoryList,
} from '../lib/utils/directory-list-utils';
import { normalizeBoardAddress } from '../lib/utils/directory-list-lookup-utils';
export type { DirectoriesData, DirectoryCommunity } from '../lib/utils/directory-list-utils';
export { normalizeBoardAddress };
interface DirectoriesMetadata {
title: string;
@@ -42,8 +44,6 @@ let cacheMetadata: DirectoriesMetadata | null = null;
let inFlightGitHubFetch: Promise<DirectoriesData> | null = null;
let lastSuccessfulGitHubFetchAt: number | null = null;
let lastGitHubFetchAttemptAt: number | null = null;
const DIRECTORY_ALIAS_SUFFIXES = ['.bso', '.eth'] as const;
// Exposed for deterministic unit tests around module-level cache state.
export const __resetDirectoriesModuleStateForTests = () => {
cacheCommunities = null;
@@ -120,16 +120,6 @@ const adaptV2Directories = (value: Record<string, unknown>): DirectoryCommunity[
return dedupeCommunities(communities);
};
export const normalizeBoardAddress = (address: string): string => {
for (const suffix of DIRECTORY_ALIAS_SUFFIXES) {
if (address.endsWith(suffix)) {
return address.slice(0, -suffix.length);
}
}
return address;
};
export const findDirectoryByAddress = (directories: DirectoryCommunity[], address: string | undefined): DirectoryCommunity | undefined => {
if (!address) {
return undefined;
+3 -29
View File
@@ -1,9 +1,10 @@
import { useEffect, useMemo, useState } from 'react';
import { type DirectoryCommunity, normalizeBoardAddress, useDirectories } from './use-directories';
import { type DirectoryCommunity, useDirectories } from './use-directories';
import { type DirectoryList, type DirectoryListBoard, normalizeDirectoryList, sortDirectoryBoardsByRank } from '../lib/utils/directory-list-utils';
import directoryListsData from '../data/5chan-directory-lists.json';
import { getDirectoryCodeForBoardAddress, getVendoredDirectoryList } from '../lib/utils/directory-list-lookup-utils';
export type { DirectoryListBoard } from '../lib/utils/directory-list-utils';
export { getDirectoryCodeForBoardAddress };
interface DirectoryListState {
list: DirectoryList | null;
@@ -29,33 +30,6 @@ const moduleCaches = new Map<string, DirectoryList>();
const inFlightFetches = new Map<string, Promise<DirectoryList | null>>();
const lastFetchSuccessAt = new Map<string, number>();
const lastFetchAttemptAt = new Map<string, number>();
let vendoredDirectoryListsCache: DirectoryList[] | null = null;
const getVendoredDirectoryLists = (): DirectoryList[] => {
if (vendoredDirectoryListsCache) return vendoredDirectoryListsCache;
const directories = Array.isArray(directoryListsData.directories) ? directoryListsData.directories : [];
vendoredDirectoryListsCache = directories.flatMap((directory) => {
const directoryCode = typeof directory.directoryCode === 'string' ? directory.directoryCode : undefined;
if (!directoryCode) return [];
const normalized = normalizeDirectoryList(directory, directoryCode);
return normalized ? [normalized] : [];
});
return vendoredDirectoryListsCache;
};
const getVendoredDirectoryList = (directoryCode: string): DirectoryList | null =>
getVendoredDirectoryLists().find((directory) => directory.directoryCode === directoryCode) ?? null;
export const getDirectoryCodeForBoardAddress = (address: string | undefined): string | undefined => {
if (!address) return undefined;
const normalizedAddress = normalizeBoardAddress(address);
return getVendoredDirectoryLists().find((directory) =>
directory.boards.some((board) => normalizeBoardAddress(board.address) === normalizedAddress || board.publicKey === address),
)?.directoryCode;
};
const synthesizeFromMainDirectory = (directoryCode: string, directories: DirectoryCommunity[]): DirectoryList | null => {
const match = directories.find((community) => community.directoryCode === directoryCode);
@@ -0,0 +1,55 @@
import directoryListsData from '../../data/5chan-directory-lists.json';
import { normalizeDirectoryList, type DirectoryList, type DirectoryListBoard } from './directory-list-utils';
const DIRECTORY_ALIAS_SUFFIXES = ['.bso', '.eth'] as const;
let vendoredDirectoryListsCache: DirectoryList[] | null = null;
export const normalizeBoardAddress = (address: string): string => {
for (const suffix of DIRECTORY_ALIAS_SUFFIXES) {
if (address.endsWith(suffix)) {
return address.slice(0, -suffix.length);
}
}
return address;
};
export const getVendoredDirectoryLists = (): DirectoryList[] => {
if (vendoredDirectoryListsCache) return vendoredDirectoryListsCache;
const directories = Array.isArray(directoryListsData.directories) ? directoryListsData.directories : [];
vendoredDirectoryListsCache = directories.flatMap((directory) => {
const directoryCode = typeof directory.directoryCode === 'string' ? directory.directoryCode : undefined;
if (!directoryCode) return [];
const normalized = normalizeDirectoryList(directory, directoryCode);
return normalized ? [normalized] : [];
});
return vendoredDirectoryListsCache;
};
export const getVendoredDirectoryList = (directoryCode: string): DirectoryList | null =>
getVendoredDirectoryLists().find((directory) => directory.directoryCode === directoryCode) ?? null;
const findBoardInList = (list: DirectoryList, address: string): DirectoryListBoard | undefined => {
const normalizedAddress = normalizeBoardAddress(address);
return list.boards.find((board) => normalizeBoardAddress(board.address) === normalizedAddress || board.publicKey === address);
};
export const getDirectoryCandidateBoardByAddress = (address: string | undefined): DirectoryListBoard | undefined => {
if (!address) return undefined;
for (const directory of getVendoredDirectoryLists()) {
const board = findBoardInList(directory, address);
if (board) return board;
}
return undefined;
};
export const getDirectoryCodeForBoardAddress = (address: string | undefined): string | undefined => {
if (!address) return undefined;
return getVendoredDirectoryLists().find((directory) => findBoardInList(directory, address))?.directoryCode;
};
+6 -1
View File
@@ -396,7 +396,12 @@ describe('Board', () => {
routePath: '/:boardIdentifier/*',
});
expect(testState.feedOptionsCalls.map((call) => call.communities)).toContainEqual([{ name: 'bizraelis.bso' }]);
expect(testState.feedOptionsCalls.map((call) => call.communities)).toContainEqual([
{
name: 'bizraelis.bso',
publicKey: '12D3KooWR7nTdKZqZ1twGWMfVsXYDGp1XAKUrnYznKP651jFrizE',
},
]);
});
it('renders the current page feed, inserts recent account comments, and wires footer actions', async () => {