fix(accounts): adapt 5chan to compact account history hooks (#1118)

* chore(cursor): use composer-2 for subagents

* fix(accounts): adapt 5chan to compact account history hooks

* fix(accounts): address 5chan account-history review findings
This commit is contained in:
Tommaso Casaburi
2026-03-20 21:25:57 +08:00
committed by GitHub
parent 4ec74bc85e
commit c2e8e169c2
26 changed files with 499 additions and 136 deletions
@@ -19,11 +19,13 @@ type TestComment = {
const testState = vi.hoisted(() => ({
account: {
id: 'account-1',
author: {
address: '0xme',
},
},
accountComments: [] as Array<{ cid?: string }>,
} as { id?: string; author?: { address?: string } },
accountCommentByCid: {} as Record<string, { cid?: string }>,
accountCommentCalls: [] as Array<{ commentCid?: string } | undefined>,
directories: [{ address: 'music-posting.eth', title: '/mu/ - Music' }] as Array<{ address: string; title?: string }>,
isMobile: false,
locationPath: '/mu/thread/thread-cid',
@@ -59,7 +61,10 @@ vi.mock('react-router-dom', async () => {
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
useAccount: () => testState.account,
useAccountComments: () => ({ accountComments: testState.accountComments }),
useAccountComment: (options?: { commentCid?: string }) => {
testState.accountCommentCalls.push(options);
return (options?.commentCid && testState.accountCommentByCid[options.commentCid]) || {};
},
}));
vi.mock('@floating-ui/react', () => ({
@@ -171,11 +176,13 @@ describe('ReplyQuotePreview', () => {
beforeEach(() => {
vi.clearAllMocks();
testState.account = {
id: 'account-1',
author: {
address: '0xme',
},
};
testState.accountComments = [];
testState.accountCommentByCid = {};
testState.accountCommentCalls = [];
testState.directories = [{ address: 'music-posting.eth', title: '/mu/ - Music' }];
testState.isMobile = false;
testState.locationPath = '/mu/thread/thread-cid';
@@ -394,6 +401,58 @@ describe('ReplyQuotePreview', () => {
outOfView.remove();
});
it('marks quotelinks as your own via the direct account comment lookup before author fallback', async () => {
testState.account = {
id: 'account-1',
author: {
address: '0xsomeone-else',
},
};
testState.accountCommentByCid = {
'reply-cid': {
cid: 'reply-cid',
},
};
await renderPreview({
isQuotelinkReply: true,
quotelinkReply: {
author: {
address: '0xother',
},
cid: 'reply-cid',
number: 11,
communityAddress: 'music-posting.eth',
},
});
expect(container.textContent).toContain('>>11 (You)');
expect(testState.accountCommentCalls).toContainEqual({ commentCid: 'reply-cid' });
});
it('does not mark quotelinks as your own when the lookup misses and no author address matches', async () => {
testState.account = {
id: 'account-1',
author: { address: undefined } as { address?: string },
};
testState.accountCommentByCid = {
'reply-cid': {
cid: 'different-cid',
},
};
await renderPreview({
isQuotelinkReply: true,
quotelinkReply: {
cid: 'reply-cid',
number: 12,
communityAddress: 'music-posting.eth',
},
});
expect(container.textContent).not.toContain('(You)');
});
it('renders unavailable desktop quotelinks without navigation and includes OP/You labels', async () => {
testState.quoteAvailability = 'unavailable';
@@ -1,9 +1,10 @@
import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Comment, useAccount, useAccountComments } from '@bitsocialnet/bitsocial-react-hooks';
import { Comment, useAccount } from '@bitsocialnet/bitsocial-react-hooks';
import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react';
import { useDirectories } from '../../hooks/use-directories';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { getBoardPath } from '../../lib/utils/route-utils';
import { formatQuoteNumber, getQuoteTargetAvailability, shouldShowFloatingQuotePreview } from '../../lib/utils/quote-link-utils';
import { findPreferredScrollTarget, getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
@@ -80,6 +81,17 @@ const scrollToReplyOnPage = (cid: string) => {
return true;
};
const useIsOwnQuotelink = (quotelinkReply?: Comment) => {
const account = useAccount();
const ownQuotelink = useSafeAccountComment({ commentCid: quotelinkReply?.cid });
const quotedAuthorAddress = quotelinkReply?.author?.address;
const accountAuthorAddress = account?.author?.address;
return Boolean(
(quotelinkReply?.cid && ownQuotelink?.cid === quotelinkReply.cid) || (quotedAuthorAddress && accountAuthorAddress && quotedAuthorAddress === accountAuthorAddress),
);
};
const DesktopQuotePreview = ({
backlinkReply,
quotelinkReply,
@@ -204,10 +216,6 @@ const DesktopQuotePreview = ({
)}
</>
);
const account = useAccount();
const { accountComments } = useAccountComments();
const resolvedQuotelinkNumber = normalizedQuotelinkReply?.number ?? quotelinkNumber;
const resolvedQuotelinkCid = normalizedQuotelinkReply?.cid;
const resolvedQuotelinkCommunityAddress = getCommentCommunityAddress(normalizedQuotelinkReply);
@@ -227,9 +235,7 @@ const DesktopQuotePreview = ({
quoteCid: resolvedQuotelinkCid,
isUnavailable: quotelinkUnavailable,
});
const isOwnQuotelink =
(resolvedQuotelinkCid ? accountComments.some((comment) => comment.cid === resolvedQuotelinkCid) : false) ||
normalizedQuotelinkReply?.author?.address === account?.author?.address;
const isOwnQuotelink = useIsOwnQuotelink(normalizedQuotelinkReply);
const quotelinkLabel = (
<>
{formatQuoteNumber(resolvedQuotelinkNumber)}
@@ -374,9 +380,6 @@ const MobileQuotePreview = ({
)}
</>
);
const account = useAccount();
const { accountComments } = useAccountComments();
const resolvedQuotelinkNumber = normalizedQuotelinkReply?.number ?? quotelinkNumber;
const resolvedQuotelinkCid = normalizedQuotelinkReply?.cid;
const resolvedQuotelinkCommunityAddress = getCommentCommunityAddress(normalizedQuotelinkReply);
@@ -390,9 +393,7 @@ const MobileQuotePreview = ({
quoteCid: resolvedQuotelinkCid,
isUnavailable: quotelinkUnavailable,
});
const isOwnQuotelink =
(resolvedQuotelinkCid ? accountComments.some((comment) => comment.cid === resolvedQuotelinkCid) : false) ||
normalizedQuotelinkReply?.author?.address === account?.author?.address;
const isOwnQuotelink = useIsOwnQuotelink(normalizedQuotelinkReply);
const replyQuotelink = (
<>