diff --git a/README.md b/README.md index f45433c6..2baef35d 100644 --- a/README.md +++ b/README.md @@ -964,6 +964,7 @@ for (const accountComment of accountComments) { console.log("comment", accountComment.index, "is status", accountComment.state); } // `state` becomes `failed` as soon as a pending local publish records terminal failure (`publishingState === "failed"` and `state === "stopped"`) or a publish error, instead of waiting for the 20-minute fallback. +// local account comments returned by useAccountComment and useAccountComments include the account author's address and shortAddress when an older cached account comment is missing author identity fields. // note: accountComment.index can change after deletions; prefer commentCid for stable identifiers // all my own votes diff --git a/llms-full.txt b/llms-full.txt index 2dd796ea..e96541ad 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -994,6 +994,7 @@ for (const accountComment of accountComments) { console.log("comment", accountComment.index, "is status", accountComment.state); } // `state` becomes `failed` as soon as a pending local publish records terminal failure (`publishingState === "failed"` and `state === "stopped"`) or a publish error, instead of waiting for the 20-minute fallback. +// local account comments returned by useAccountComment and useAccountComments include the account author's address and shortAddress when an older cached account comment is missing author identity fields. // note: accountComment.index can change after deletions; prefer commentCid for stable identifiers // all my own votes @@ -3163,6 +3164,19 @@ Avoid GitHub MCP and browser MCP servers for this project because they add signi Source: https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/CHANGELOG.md ```markdown +## [0.1.19](https://github.com/bitsocialnet/bitsocial-react-hooks/compare/v0.1.18...v0.1.19) (2026-06-18) + + +### Bug Fixes + +* resolve community name lookups ([ffef83c](https://github.com/bitsocialnet/bitsocial-react-hooks/commit/ffef83cc7b27a59fca4b0788732ff0bfc213496e)) + + + +## [0.1.18](https://github.com/bitsocialnet/bitsocial-react-hooks/compare/v0.1.17...v0.1.18) (2026-06-15) + + + ## [0.1.17](https://github.com/bitsocialnet/bitsocial-react-hooks/compare/v0.1.16...v0.1.17) (2026-06-12) diff --git a/llms.txt b/llms.txt index 0e509194..6fa50335 100644 --- a/llms.txt +++ b/llms.txt @@ -38,5 +38,5 @@ This file is generated by `scripts/generate-llms-files.mjs`. Do not hand-edit it ## Optional -- [Changelog](https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/CHANGELOG.md): * **actions:** adapt challenge answers for pkc-js ([20d89f0](https://github.com/bitsocialnet/bitsocial-react-hooks/commit/20d89f07363f54c38b83e0f9425bd77174854f9f)) * **deps:** upgrade pkc-js and kubo ([9cb56c2](https... +- [Changelog](https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/CHANGELOG.md): * resolve community name lookups ([ffef83c](https://github.com/bitsocialnet/bitsocial-react-hooks/commit/ffef83cc7b27a59fca4b0788732ff0bfc213496e)) - [TODO](https://github.com/bitsocialnet/bitsocial-react-hooks/blob/master/docs/TODO.md): - e2e test to publish to an electron sub - async useAuthorAddress hook (because resolving ETH address synchronously is too slow) - implement sort by active - implement showing your own pending replies in a comment (wh... diff --git a/package.json b/package.json index aac9292f..3c83b113 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "publishConfig": { "access": "public" }, - "version": "0.1.19", + "version": "0.1.20", "packageManager": "yarn@4.13.0", "files": [ "CHANGELOG.md", diff --git a/src/hooks/accounts/accounts.test.ts b/src/hooks/accounts/accounts.test.ts index d8cdf161..b67c36aa 100644 --- a/src/hooks/accounts/accounts.test.ts +++ b/src/hooks/accounts/accounts.test.ts @@ -1655,6 +1655,75 @@ describe("accounts", () => { expect(rendered2.result.current.index).toBe(undefined); }); + test("account comment hooks restore missing local author identity from the owning account", async () => { + const accountId = accountsStore.getState().activeAccountId!; + accountsStore.setState((state: any) => ({ + ...state, + accounts: { + ...state.accounts, + [accountId]: { + ...state.accounts[accountId], + author: { + ...state.accounts[accountId].author, + displayName: "Account Author", + avatar: "account-avatar.png", + flair: { label: "account flair" }, + }, + }, + }, + accountsComments: { + ...state.accountsComments, + [accountId]: state.accountsComments[accountId].map( + (accountComment: any, index: number) => { + if (index === 0) { + return { + ...accountComment, + author: { displayName: "Anonymous", shortAddress: "stale-short-address" }, + }; + } + if (index === 1) { + return { + ...accountComment, + author: { address: "existing author address", displayName: "Existing Author" }, + }; + } + return accountComment; + }, + ), + }, + })); + + const accountAuthor = accountsStore.getState().accounts[accountId].author; + const rendered2 = renderHook(() => { + const accountComment = useAccountComment({ commentIndex: 0 }); + const accountComments = useAccountComments({ commentIndices: [0, 1] }); + return { accountComment, accountComments }; + }); + const waitForRestoredAuthor = testUtils.createWaitFor(rendered2); + + await waitForRestoredAuthor( + () => rendered2.result.current.accountComment.author?.address === accountAuthor.address, + ); + + const restoredComment = rendered2.result.current.accountComment; + expect(restoredComment.author.address).toBe(accountAuthor.address); + expect(typeof restoredComment.author.shortAddress).toBe("string"); + expect(restoredComment.author.shortAddress).not.toBe("stale-short-address"); + expect(restoredComment.author.displayName).toBe("Anonymous"); + expect(restoredComment.author.avatar).toBe("account-avatar.png"); + expect(restoredComment.author.flair).toEqual({ label: "account flair" }); + + const [restoredListComment, existingAuthorComment] = + rendered2.result.current.accountComments.accountComments; + expect(restoredListComment.author.address).toBe(accountAuthor.address); + expect(restoredListComment.author.displayName).toBe("Anonymous"); + expect(existingAuthorComment.author.address).toBe("existing author address"); + expect(existingAuthorComment.author.displayName).toBe("Existing Author"); + expect( + accountsStore.getState().accountsComments[accountId][0].author.address, + ).toBeUndefined(); + }); + test(`get all account comments`, async () => { expect(rendered.result.current.accountComments.length).toBe(3); expect(rendered.result.current.accountComments[0].content).toBe("content 1"); @@ -3927,6 +3996,21 @@ describe("accounts", () => { expect(rendered.result.current.errors).toEqual([]); }); + test("useAccountComment ignores negative and fractional indexes", async () => { + const rendered = renderHook((props) => useAccountComment(props)); + const waitFor = testUtils.createWaitFor(rendered); + + rendered.rerender({ commentIndex: -1 }); + await waitFor(() => rendered.result.current.state !== undefined); + expect(rendered.result.current.content).toBeUndefined(); + expect(rendered.result.current.errors).toEqual([]); + + rendered.rerender({ commentIndex: 0.5 }); + await waitFor(() => rendered.result.current.state !== undefined); + expect(rendered.result.current.content).toBeUndefined(); + expect(rendered.result.current.errors).toEqual([]); + }); + test("useAccountComment with nonexistent account has undefined accountComments", async () => { const rendered = renderHook(() => useAccountComment({ commentIndex: 0, accountName: "NonExistentAccount" }), diff --git a/src/hooks/accounts/accounts.ts b/src/hooks/accounts/accounts.ts index b72d0f98..fd7ff11d 100644 --- a/src/hooks/accounts/accounts.ts +++ b/src/hooks/accounts/accounts.ts @@ -51,6 +51,7 @@ import { } from "../../lib/community-address"; import { addCommentModeration } from "../../lib/utils/comment-moderation"; import useInterval from "../utils/use-interval"; +import PkcJs from "../../lib/pkc-js"; const getCommentEditPropertyValue = (comment: any, propertyName: string) => { if (propertyName !== COMMENT_MODERATION_AUTHOR_SUMMARY_KEY) { @@ -419,6 +420,35 @@ const getAccountHistorySortType = ( return order === "desc" ? "new" : "old"; }; +const getAccountCommentWithAccountAuthor = ( + accountComment: AccountComment, + account?: Account, + accountId?: string, +): AccountComment => { + const accountAuthor = account?.author; + if ( + !accountId || + accountComment.accountId !== accountId || + !accountAuthor?.address || + accountComment.author?.address + ) { + return accountComment; + } + + const accountShortAddress = + accountAuthor.shortAddress || PkcJs.PKC.getShortAddress({ address: accountAuthor.address }); + + return { + ...accountComment, + author: { + ...accountAuthor, + ...accountComment.author, + address: accountAuthor.address, + ...(accountShortAddress ? { shortAddress: accountShortAddress } : {}), + }, + }; +}; + export function useAccountComments(options?: UseAccountCommentsOptions): UseAccountCommentsResult { assert( !options || typeof options === "object", @@ -448,6 +478,7 @@ export function useAccountComments(options?: UseAccountCommentsOptions): UseAcco const commentCidToAccountComment = useAccountsStore( (state) => state.commentCidsToAccountsComments[commentCid || ""], ); + const account = useAccountsStore((state) => state.accounts[accountId || ""]); const accountComments = useAccountsStore((state) => state.accountsComments[accountId || ""]); const [accountCommentStates, setAccountCommentStates] = useState([]); const accountHistorySortType = getAccountHistorySortType(sortType, order); @@ -538,10 +569,10 @@ export function useAccountComments(options?: UseAccountCommentsOptions): UseAcco const filteredAccountCommentsWithStates = useMemo(() => { const states = getAccountCommentsStates(filteredAccountComments); return filteredAccountComments.map((comment, i) => ({ - ...comment, + ...getAccountCommentWithAccountAuthor(comment, account, accountId || undefined), state: states[i], })); - }, [filteredAccountComments, accountCommentStates]); + }, [filteredAccountComments, accountCommentStates, account, accountId]); if (options) { log("useAccountComments", { @@ -587,10 +618,13 @@ export function useAccountComment(options?: UseAccountCommentOptions): UseAccoun const commentCidToAccountComment = useAccountsStore( (state) => state.commentCidsToAccountsComments[commentCid || ""], ); + const account = useAccountsStore((state) => state.accounts[accountId || ""]); const accountComments = useAccountsStore((state) => state.accountsComments[accountId || ""]); const normalizedCommentIndex = commentIndex === undefined ? undefined : Number(commentIndex); const resolvedCommentIndex = - typeof normalizedCommentIndex === "number" && !Number.isNaN(normalizedCommentIndex) + typeof normalizedCommentIndex === "number" && + Number.isInteger(normalizedCommentIndex) && + normalizedCommentIndex >= 0 ? normalizedCommentIndex : commentCidToAccountComment?.accountId === accountId ? commentCidToAccountComment.accountCommentIndex @@ -601,24 +635,26 @@ export function useAccountComment(options?: UseAccountCommentOptions): UseAccoun } return accountComments?.[resolvedCommentIndex]; }, [accountComments, resolvedCommentIndex]); - const accountComment = (storedAccountComment || {}) as Partial & { - error?: Error; - errors?: Error[]; - }; const state = storedAccountComment ? getAccountCommentsStates([storedAccountComment])[0] : "initializing"; - return useMemo( - () => - ({ - ...accountComment, - state, - error: accountComment.error, - errors: accountComment.errors || [], - }) as UseAccountCommentResult, - [accountComment, state], - ); + return useMemo(() => { + const accountComment = ( + storedAccountComment + ? getAccountCommentWithAccountAuthor(storedAccountComment, account, accountId || undefined) + : {} + ) as Partial & { + error?: Error; + errors?: Error[]; + }; + return { + ...accountComment, + state, + error: accountComment.error, + errors: accountComment.errors || [], + } as UseAccountCommentResult; + }, [storedAccountComment, account, accountId, state]); } /**