feat(mod queue): transfer posts across boards#1182
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughAdds moderator transfer-post utilities, a draggable transfer modal, transferred-tag rendering, edit-menu and mod-queue wiring, related tests, and new translation keys across locale files. ChangesPost Transfer Feature
Estimated code review effort: 5 (Critical) | ~120 minutes ChangesTransfer Feature Translations
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Addressed the valid Cursor Bugbot findings in the latest commit.
Local verification after the review fix: yarn test, yarn lint, yarn type-check, yarn build, and yarn doctor --verbose --scope changed. |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
src/components/post-transferred-tag.tsx (1)
2-3: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep this shared component's styles out of
src/views/.Importing
../views/post/post.module.csscouples reusable UI to a route stylesheet. Move.transferredTaginto a component-local/shared CSS module so this stays reusable outside the post view. As per coding guidelines, "Keep route composition insrc/views/, reusable UI insrc/components/."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/post-transferred-tag.tsx` around lines 2 - 3, The shared `PostTransferredTag` component is importing styling from the post route stylesheet, which ties reusable UI to `src/views/`. Move the `.transferredTag` rules into a component-local or shared CSS module for `post-transferred-tag.tsx`, then update the `styles` import in that component to use the new module so it stays reusable outside the post view.Source: Coding guidelines
src/views/post/__tests__/post.test.tsx (1)
200-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicated
data-transferredderivation across desktop/mobile mocks.The
flair.text === '5chan:transferred'lookup logic is repeated verbatim in both mock presenters. Extracting a tiny shared helper would avoid drift if the marker key or logic changes.Also applies to: 230-245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/views/post/__tests__/post.test.tsx` around lines 200 - 215, The `data-transferred` value is derived by repeating the same `post.commentModeration.flairs.some(flair => flair.text === '5chan:transferred')` logic in both mock presenters. Extract this check into a small shared helper used by the desktop and mobile presenter code in `post.test.tsx`, so both render paths stay consistent if the marker text or matching logic changes. Keep the helper close to the existing presenter setup and update the `data-transferred` attribute to call it from both places.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@public/translations/hu/default.json`:
- Line 456: The translation for modQueue.transferTarget is inconsistent with the
surrounding transfer strings that use the board term. Update the Hungarian copy
in the default translations so it uses the same “tábla” wording as the related
transfer labels, keeping the terminology aligned across the modal.
In `@public/translations/id/default.json`:
- Around line 454-461: The transfer modal strings in the Indonesian translations
are only partially localized, leaving English terms in the moderator flow.
Update the affected keys in default.json—especially modQueue.transferTitle,
modQueue.transferTitleWithNumber, modQueue.transferTarget, and
modQueue.transferSuccess—to use consistent Indonesian wording, and align the
target term with the locale’s existing “papan” usage instead of “forum” or
“board.”
In `@public/translations/it/default.json`:
- Around line 456-459: The transfer strings in the Italian translation file are
only partially localized, with “board” still left in English in the modQueue
transfer messages. Update the affected translation entries in the modQueue block
so the board term is fully and consistently translated in Italian, keeping the
wording aligned with the surrounding strings and the same terminology used
across the file.
In `@src/components/post-transfer-modal/post-transfer-modal.tsx`:
- Around line 111-333: Add a test file for PostTransferModal covering the
transfer state machine and lifecycle behavior, since this logic is reasonably
testable and currently unverified. Focus tests on PostTransferModal’s
handleSubmit flow, challenge abandonment cleanup, success path finalization, and
failure recovery by mocking the store actions and transfer helpers. Include
cases for selecting fields/target gating canSubmit, temporary account
creation/deletion, pending comment cleanup, and dispatching
publishSucceeded/publishFailed on the reducer.
- Around line 134-155: The target-board selection logic in the post transfer
modal is defaulting to boardOptions[0], which makes a board appear chosen before
the moderator explicitly picks one. Update the resolved target board handling in
post-transfer-modal.tsx so it only uses targetBoardAddress and does not
auto-select the first eligible board, and keep canSubmit disabled until an
explicit target board address is present. Use the existing symbols
resolvedTargetBoardAddress, resolvedTargetBoard, and canSubmit to locate and
adjust the selection flow consistently for the transfer action.
In `@src/lib/comment-transfer.ts`:
- Around line 53-66: `getTransferPublishPayload()` is trimming transferred
comment bodies by routing `comment.content` through `getTextField()`. Update the
payload-building logic in `getTransferPublishPayload` so body text is copied
verbatim after separately checking it isn’t blank, and keep the existing
handling for `displayName`, `title`, `link`, and `getTransferableCommentFlairs`.
In `@src/views/mod-queue/__tests__/mod-queue.test.tsx`:
- Around line 27-55: The mod-queue tests only cover the compact path, so the new
transfer wiring in ModQueueFeedPost is still unverified. Update
src/views/mod-queue/__tests__/mod-queue.test.tsx to add a feed-mode case by
setting testState.viewMode to 'feed' and exercising the transfer flow through
ModQueueFeedPost. Assert that the transfer action still opens and closes
correctly in feed mode, using the existing mod-queue helpers and mocks to locate
the behavior.
In `@src/views/post/post.tsx`:
- Line 209: The post transfer handler is being forwarded to reply posts, but
transfer should only be available for top-level posts. Update the Post rendering
flow in post.tsx so the handler is derived once for top-level usage and not
passed into child/reply Post instances; adjust the relevant Post props and the
rendering paths that currently forward onTransfer so replies never receive it.
---
Nitpick comments:
In `@src/components/post-transferred-tag.tsx`:
- Around line 2-3: The shared `PostTransferredTag` component is importing
styling from the post route stylesheet, which ties reusable UI to `src/views/`.
Move the `.transferredTag` rules into a component-local or shared CSS module for
`post-transferred-tag.tsx`, then update the `styles` import in that component to
use the new module so it stays reusable outside the post view.
In `@src/views/post/__tests__/post.test.tsx`:
- Around line 200-215: The `data-transferred` value is derived by repeating the
same `post.commentModeration.flairs.some(flair => flair.text ===
'5chan:transferred')` logic in both mock presenters. Extract this check into a
small shared helper used by the desktop and mobile presenter code in
`post.test.tsx`, so both render paths stay consistent if the marker text or
matching logic changes. Keep the helper close to the existing presenter setup
and update the `data-transferred` attribute to call it from both places.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b6addd2b-84bb-4288-8001-9dcb2317b0ca
📒 Files selected for processing (55)
public/translations/ar/default.jsonpublic/translations/bn/default.jsonpublic/translations/cs/default.jsonpublic/translations/da/default.jsonpublic/translations/de/default.jsonpublic/translations/el/default.jsonpublic/translations/en/default.jsonpublic/translations/es/default.jsonpublic/translations/fa/default.jsonpublic/translations/fi/default.jsonpublic/translations/fil/default.jsonpublic/translations/fr/default.jsonpublic/translations/he/default.jsonpublic/translations/hi/default.jsonpublic/translations/hu/default.jsonpublic/translations/id/default.jsonpublic/translations/it/default.jsonpublic/translations/ja/default.jsonpublic/translations/ko/default.jsonpublic/translations/mr/default.jsonpublic/translations/nl/default.jsonpublic/translations/no/default.jsonpublic/translations/pl/default.jsonpublic/translations/pt/default.jsonpublic/translations/ro/default.jsonpublic/translations/ru/default.jsonpublic/translations/sq/default.jsonpublic/translations/sv/default.jsonpublic/translations/te/default.jsonpublic/translations/th/default.jsonpublic/translations/tr/default.jsonpublic/translations/uk/default.jsonpublic/translations/ur/default.jsonpublic/translations/vi/default.jsonpublic/translations/zh/default.jsonsrc/components/__tests__/post-transferred-tag.test.tsxsrc/components/comment-content/__tests__/comment-content.test.tsxsrc/components/edit-menu/__tests__/edit-menu.test.tsxsrc/components/edit-menu/edit-menu.module.csssrc/components/edit-menu/edit-menu.tsxsrc/components/post-author-flags.tsxsrc/components/post-desktop/post-desktop.tsxsrc/components/post-mobile/post-mobile.tsxsrc/components/post-transfer-modal/post-transfer-modal.module.csssrc/components/post-transfer-modal/post-transfer-modal.tsxsrc/components/post-transferred-tag.tsxsrc/lib/__tests__/comment-transfer.test.tssrc/lib/comment-flags.tssrc/lib/comment-transfer.tssrc/views/mod-queue/__tests__/mod-queue.test.tsxsrc/views/mod-queue/mod-queue.module.csssrc/views/mod-queue/mod-queue.tsxsrc/views/post/__tests__/post.test.tsxsrc/views/post/post.module.csssrc/views/post/post.tsx
| "transfer": "Átvitel", | ||
| "modQueue.transferTitle": "Bejegyzés áthelyezése", | ||
| "modQueue.transferSource": "Honnan", | ||
| "modQueue.transferTarget": "Fórumra", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Use the board term consistently.
Fórumra shifts the meaning from the rest of the locale copy, which refers to the target as a tábla in the surrounding transfer strings. That makes the modal inconsistent and a bit misleading.
♻️ Suggested fix
- "modQueue.transferTarget": "Fórumra",
+ "modQueue.transferTarget": "Táblára",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "modQueue.transferTarget": "Fórumra", | |
| "modQueue.transferTarget": "Táblára", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/hu/default.json` at line 456, The translation for
modQueue.transferTarget is inconsistent with the surrounding transfer strings
that use the board term. Update the Hungarian copy in the default translations
so it uses the same “tábla” wording as the related transfer labels, keeping the
terminology aligned across the modal.
| "modQueue.transferTitle": "Transfer posting", | ||
| "modQueue.transferSource": "Dari", | ||
| "modQueue.transferTarget": "Ke forum", | ||
| "modQueue.transferFields": "Salin bidang", | ||
| "modQueue.transferNoFields": "Pilih setidaknya satu bidang untuk disalin.", | ||
| "modQueue.transferSuccess": "Ditransfer. Postingan asli sekarang mengarah ke board baru.", | ||
| "transferred": "Ditransfer", | ||
| "modQueue.transferTitleWithNumber": "Transfer postingan No.{{number}}", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fully localize the transfer modal strings.
Transfer posting, Transfer postingan, and board leak English into the Indonesian UI, and Ke forum disagrees with the papan wording used elsewhere in the locale. That leaves the moderator flow half-translated.
♻️ Suggested fix
- "modQueue.transferTitle": "Transfer posting",
+ "modQueue.transferTitle": "Pindahkan postingan",
...
- "modQueue.transferTarget": "Ke forum",
+ "modQueue.transferTarget": "Ke papan",
...
- "modQueue.transferSuccess": "Ditransfer. Postingan asli sekarang mengarah ke board baru.",
+ "modQueue.transferSuccess": "Dipindahkan. Posting asli sekarang mengarah ke papan baru.",
...
- "modQueue.transferTitleWithNumber": "Transfer postingan No.{{number}}",
+ "modQueue.transferTitleWithNumber": "Pindahkan postingan No.{{number}}",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "modQueue.transferTitle": "Transfer posting", | |
| "modQueue.transferSource": "Dari", | |
| "modQueue.transferTarget": "Ke forum", | |
| "modQueue.transferFields": "Salin bidang", | |
| "modQueue.transferNoFields": "Pilih setidaknya satu bidang untuk disalin.", | |
| "modQueue.transferSuccess": "Ditransfer. Postingan asli sekarang mengarah ke board baru.", | |
| "transferred": "Ditransfer", | |
| "modQueue.transferTitleWithNumber": "Transfer postingan No.{{number}}", | |
| "modQueue.transferTitle": "Pindahkan postingan", | |
| "modQueue.transferSource": "Dari", | |
| "modQueue.transferTarget": "Ke papan", | |
| "modQueue.transferFields": "Salin bidang", | |
| "modQueue.transferNoFields": "Pilih setidaknya satu bidang untuk disalin.", | |
| "modQueue.transferSuccess": "Dipindahkan. Posting asli sekarang mengarah ke papan baru.", | |
| "transferred": "Ditransfer", | |
| "modQueue.transferTitleWithNumber": "Pindahkan postingan No.{{number}}", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/id/default.json` around lines 454 - 461, The transfer
modal strings in the Indonesian translations are only partially localized,
leaving English terms in the moderator flow. Update the affected keys in
default.json—especially modQueue.transferTitle,
modQueue.transferTitleWithNumber, modQueue.transferTarget, and
modQueue.transferSuccess—to use consistent Indonesian wording, and align the
target term with the locale’s existing “papan” usage instead of “forum” or
“board.”
| "modQueue.transferTarget": "Al board", | ||
| "modQueue.transferFields": "Copia campi", | ||
| "modQueue.transferNoFields": "Scegli almeno un campo da copiare.", | ||
| "modQueue.transferSuccess": "Trasferito. Il post originale ora punta alla nuova board.", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Translate the board labels fully.
Al board / nuova board leave English in the Italian UI, so the transfer flow reads half-localized next to the surrounding Italian strings. Use the same Italian board term consistently.
♻️ Suggested fix
- "modQueue.transferTarget": "Al board",
+ "modQueue.transferTarget": "Alla bacheca",
...
- "modQueue.transferSuccess": "Trasferito. Il post originale ora punta alla nuova board.",
+ "modQueue.transferSuccess": "Trasferito. Il post originale ora punta alla nuova bacheca.",📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "modQueue.transferTarget": "Al board", | |
| "modQueue.transferFields": "Copia campi", | |
| "modQueue.transferNoFields": "Scegli almeno un campo da copiare.", | |
| "modQueue.transferSuccess": "Trasferito. Il post originale ora punta alla nuova board.", | |
| "modQueue.transferTarget": "Alla bacheca", | |
| "modQueue.transferFields": "Copia campi", | |
| "modQueue.transferNoFields": "Scegli almeno un campo da copiare.", | |
| "modQueue.transferSuccess": "Trasferito. Il post originale ora punta alla nuova bacheca.", |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@public/translations/it/default.json` around lines 456 - 459, The transfer
strings in the Italian translation file are only partially localized, with
“board” still left in English in the modQueue transfer messages. Update the
affected translation entries in the modQueue block so the board term is fully
and consistently translated in Italian, keeping the wording aligned with the
surrounding strings and the same terminology used across the file.
| const PostTransferModal = ({ comment, onClose }: PostTransferModalProps) => { | ||
| const { t } = useTranslation(); | ||
| const nodeRef = useRef<HTMLDivElement>(null); | ||
| const finalizedTransferRef = useRef(false); | ||
| const bodySelectionStyleBeforeDragRef = useRef<{ userSelect: string; webkitUserSelect: string } | null>(null); | ||
| const directories = useDirectories(); | ||
| const createAccount = useAccountsStore((state) => state.accountsActions.createAccount) as CreateAccountAction; | ||
| const publishComment = useAccountsStore((state) => state.accountsActions.publishComment) as PublishCommentAction; | ||
| const publishCommentModeration = useAccountsStore((state) => state.accountsActions.publishCommentModeration) as PublishCommentModerationAction; | ||
| const deleteComment = useAccountsStore((state) => state.accountsActions.deleteComment) as DeleteCommentAction; | ||
| const deleteAccount = useAccountsStore((state) => state.accountsActions.deleteAccount) as DeleteAccountAction; | ||
| const sourceCommunityAddress = getCommentCommunityAddress(comment); | ||
| const sourceCommentCid = comment.cid; | ||
| const boardOptions = useMemo( | ||
| () => | ||
| directories | ||
| .filter((community) => community.address && (!sourceCommunityAddress || !areSameBoardAddress(community.address, sourceCommunityAddress))) | ||
| .sort((left, right) => getTransferBoardLabel(left).localeCompare(getTransferBoardLabel(right), undefined, { sensitivity: 'base' })), | ||
| [directories, sourceCommunityAddress], | ||
| ); | ||
| const [modalState, dispatchModalState] = useReducer(transferModalReducer, comment, getInitialTransferModalState); | ||
| const { targetBoardAddress, selectedFields, transferState, transferError, transferredIndex } = modalState; | ||
|
|
||
| const resolvedTargetBoardAddress = targetBoardAddress || boardOptions[0]?.address || ''; | ||
| const resolvedTargetBoard = boardOptions.find((community) => community.address === resolvedTargetBoardAddress); | ||
| const availableFields = useMemo(() => getAvailableTransferFields(comment), [comment]); | ||
| const sourceBoard = useMemo( | ||
| () => (sourceCommunityAddress ? directories.find((community) => areSameBoardAddress(community.address, sourceCommunityAddress)) : undefined), | ||
| [directories, sourceCommunityAddress], | ||
| ); | ||
| const sourceBoardLabel = useMemo(() => { | ||
| return sourceBoard ? getTransferBoardLabel(sourceBoard) : sourceCommunityAddress || 'N/A'; | ||
| }, [sourceBoard, sourceCommunityAddress]); | ||
| const transferTitle = comment.number !== undefined ? t('modQueue.transferTitleWithNumber', { number: comment.number }) : t('modQueue.transferTitle'); | ||
| const isPublishingTransfer = transferState === 'publishing'; | ||
| const canSubmit = | ||
| !isPublishingTransfer && | ||
| Boolean(resolvedTargetBoardAddress) && | ||
| Boolean(sourceCommunityAddress) && | ||
| Boolean(sourceCommentCid) && | ||
| typeof createAccount === 'function' && | ||
| typeof publishComment === 'function' && | ||
| typeof publishCommentModeration === 'function' && | ||
| typeof deleteAccount === 'function' && | ||
| hasSelectedTransferFields(selectedFields, availableFields); | ||
|
|
||
| const [{ left, top }, api] = useSpring( | ||
| () => ({ | ||
| from: getInitialTransferModalPosition(), | ||
| }), | ||
| [], | ||
| ); | ||
|
|
||
| const disableBodyTextSelection = () => { | ||
| if (!bodySelectionStyleBeforeDragRef.current) { | ||
| bodySelectionStyleBeforeDragRef.current = { | ||
| userSelect: document.body.style.userSelect, | ||
| webkitUserSelect: document.body.style.webkitUserSelect, | ||
| }; | ||
| } | ||
| Object.assign(document.body.style, { userSelect: 'none', webkitUserSelect: 'none' }); | ||
| }; | ||
|
|
||
| const restoreBodyTextSelection = () => { | ||
| const previousStyle = bodySelectionStyleBeforeDragRef.current; | ||
| Object.assign(document.body.style, { | ||
| userSelect: previousStyle?.userSelect ?? '', | ||
| webkitUserSelect: previousStyle?.webkitUserSelect ?? '', | ||
| }); | ||
| bodySelectionStyleBeforeDragRef.current = null; | ||
| }; | ||
|
|
||
| const bind = useDrag( | ||
| ({ active, event, offset: [ox, oy] }) => { | ||
| const nextLeft = Math.round(ox); | ||
| const nextTop = Math.round(oy); | ||
|
|
||
| if (active) { | ||
| event.preventDefault(); | ||
| disableBodyTextSelection(); | ||
| } else { | ||
| restoreBodyTextSelection(); | ||
| } | ||
| api.start({ left: nextLeft, top: nextTop, immediate: true }); | ||
| }, | ||
| { | ||
| from: () => [left.get(), top.get()], | ||
| filterTaps: true, | ||
| bounds: undefined, | ||
| }, | ||
| ); | ||
|
|
||
| const closeTransferModalOnEscape = useEffectEvent(() => { | ||
| if (!isPublishingTransfer) { | ||
| onClose(); | ||
| } | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| const handleTransferModalKeydown = (event: KeyboardEvent) => { | ||
| if (event.key === 'Escape') { | ||
| closeTransferModalOnEscape(); | ||
| } | ||
| }; | ||
|
|
||
| document.addEventListener('keydown', handleTransferModalKeydown); | ||
| return () => { | ||
| document.removeEventListener('keydown', handleTransferModalKeydown); | ||
| restoreBodyTextSelection(); | ||
| }; | ||
| }, []); | ||
|
|
||
| const getTransferModerationCallbacks = (message: string) => ({ | ||
| onChallenge: async (...args: any[]) => { | ||
| useChallengesStore.getState().addChallenge([...args, comment]); | ||
| }, | ||
| onChallengeVerification: async (challengeVerification: ChallengeVerification, moderation: Comment) => { | ||
| alertChallengeVerificationFailed(challengeVerification, moderation); | ||
| }, | ||
| onError: (error: Error & { details?: unknown }) => { | ||
| console.error(message, error, error.details); | ||
| }, | ||
| }); | ||
|
|
||
| const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { | ||
| event.preventDefault(); | ||
| if (!canSubmit) return; | ||
|
|
||
| let pendingIndex: number | undefined; | ||
| let temporaryAccountCreated = false; | ||
| const temporaryAccountName = getTemporaryTransferAccountName(sourceCommentCid); | ||
| const cleanupTemporaryAccount = async ({ deletePendingComment = false } = {}) => { | ||
| if (!temporaryAccountCreated) return; | ||
| if (deletePendingComment && pendingIndex !== undefined && typeof deleteComment === 'function') { | ||
| try { | ||
| await deleteComment(pendingIndex, temporaryAccountName); | ||
| } catch (error) { | ||
| console.error('Transfer pending comment cleanup failed:', error); | ||
| } | ||
| } | ||
| try { | ||
| await deleteAccount(temporaryAccountName); | ||
| temporaryAccountCreated = false; | ||
| } catch (error) { | ||
| console.error('Transfer temporary account cleanup failed:', error); | ||
| } | ||
| }; | ||
| finalizedTransferRef.current = false; | ||
| dispatchModalState({ type: 'publishStarted' }); | ||
|
|
||
| try { | ||
| await createAccount(temporaryAccountName); | ||
| temporaryAccountCreated = true; | ||
| const payload = getTransferPublishPayload(comment, selectedFields, resolvedTargetBoardAddress); | ||
| await publishComment( | ||
| { | ||
| ...payload, | ||
| _onPendingCommentIndex: (index: number) => { | ||
| pendingIndex = index; | ||
| }, | ||
| onChallenge: async (...args: any[]) => { | ||
| useChallengesStore.getState().addChallenge([...args, comment], async () => { | ||
| await cleanupTemporaryAccount({ deletePendingComment: true }); | ||
| dispatchModalState({ type: 'publishFailed', error: new Error('Transfer challenge was abandoned.') }); | ||
| }); | ||
| }, | ||
| onChallengeVerification: async (challengeVerification: ChallengeVerification, challengeComment: Comment) => { | ||
| try { | ||
| alertChallengeVerificationFailed(challengeVerification, challengeComment); | ||
| if (challengeVerification?.challengeSuccess !== true) { | ||
| return; | ||
| } | ||
| if (finalizedTransferRef.current) return; | ||
| finalizedTransferRef.current = true; | ||
|
|
||
| const targetCommentCid = getTransferredCommentCid(challengeVerification, challengeComment); | ||
| if (!targetCommentCid) { | ||
| throw new Error('Transferred post was accepted, but no target CID was returned.'); | ||
| } | ||
|
|
||
| // Queue the target marker first so a target moderation failure does not remove the original post. | ||
| await publishCommentModeration({ | ||
| commentCid: targetCommentCid, | ||
| communityAddress: resolvedTargetBoardAddress, | ||
| commentModeration: { | ||
| flairs: getTargetTransferModerationFlairs(comment, selectedFields), | ||
| }, | ||
| ...getTransferModerationCallbacks('Transfer target moderation failed:'), | ||
| }); | ||
| await publishCommentModeration({ | ||
| commentCid: sourceCommentCid, | ||
| communityAddress: sourceCommunityAddress, | ||
| commentModeration: getTransferSourceModeration( | ||
| comment, | ||
| getTransferBoardReference(resolvedTargetBoard, resolvedTargetBoardAddress), | ||
| getTransferSourceBoardReference(sourceBoard, sourceCommunityAddress), | ||
| getTransferSourceBoardRulesLink(sourceBoard), | ||
| ), | ||
| ...getTransferModerationCallbacks('Transfer source moderation failed:'), | ||
| }); | ||
|
|
||
| await cleanupTemporaryAccount(); | ||
| dispatchModalState({ type: 'publishSucceeded', transferredIndex: pendingIndex }); | ||
| } catch (error) { | ||
| console.error('Transfer finalization failed:', error); | ||
| await cleanupTemporaryAccount(); | ||
| dispatchModalState({ type: 'publishFailed', error }); | ||
| } | ||
| }, | ||
| onError: (error: Error & { details?: unknown }) => { | ||
| console.error('Transfer failed:', error, error.details); | ||
| void cleanupTemporaryAccount({ deletePendingComment: true }); | ||
| dispatchModalState({ type: 'publishFailed', error }); | ||
| }, | ||
| }, | ||
| temporaryAccountName, | ||
| ); | ||
| } catch (error) { | ||
| console.error('Transfer failed:', error); | ||
| await cleanupTemporaryAccount({ deletePendingComment: true }); | ||
| dispatchModalState({ type: 'publishFailed', error }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift
Add tests for this transfer flow before merge.
This modal now owns a fairly intricate state machine: field selection, explicit target selection, temp-account lifecycle, challenge abandonment cleanup, and success/failure recovery. I don't see a companion test file in this layer, and this is well within the repo's "reasonably testable" bar. As per coding guidelines, "**/*.test.{ts,tsx}: Add or update tests for bug fixes and non-trivial logic changes when the code is reasonably testable."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/components/post-transfer-modal/post-transfer-modal.tsx` around lines 111
- 333, Add a test file for PostTransferModal covering the transfer state machine
and lifecycle behavior, since this logic is reasonably testable and currently
unverified. Focus tests on PostTransferModal’s handleSubmit flow, challenge
abandonment cleanup, success path finalization, and failure recovery by mocking
the store actions and transfer helpers. Include cases for selecting
fields/target gating canSubmit, temporary account creation/deletion, pending
comment cleanup, and dispatching publishSucceeded/publishFailed on the reducer.
Source: Coding guidelines
|
Addressed the valid CodeRabbit correctness findings in the latest commit. Fixed:
Deferred/declined:
Local verification after this batch: yarn test, yarn lint, yarn type-check, yarn build, yarn doctor --verbose --scope changed, and yarn knip. |
|
Addressed the latest Cursor Bugbot finding in ff6841e: transfer eligibility is now centralized in canTransferComment and unavailable source posts (deleted, removed, moderation-removed, purged, archived) no longer expose transfer from either the edit menu or mod queue. Added helper, edit-menu, and mod-queue coverage. |
|
Addressed the latest Cursor Bugbot finding in b1c59ac: after a transfer succeeds, the modal now treats success as a terminal state. Target/field controls and the submit button stay disabled, so the moderator can close the modal but cannot publish a second copied post from the same transfer dialog. Added workflow coverage for the repeat-submit guard. |
|
Addressed the latest Cursor Bugbot queue-state findings in a18e121. Mod-queue transfer state now flows back to the queue item: approve/reject/transfer actions are locked while transfer is publishing, success locally marks the item as rejected and remembers the rejected queue snapshot, and abandoned/failed transfer releases the publishing lock. Added compact and feed-mode coverage for those states. |
|
Addressed the latest Cursor Bugbot finding in b04694f: the transfer success message no longer appends the temporary account pending-comment index, since that is not the destination post number. The modal state no longer stores that internal index, and the mod-queue workflow test now guards against showing it. |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/post-transfer-modal/post-transfer-modal.tsx`:
- Around line 328-332: The catch block in post-transfer finalization is mapping
every error to the generic failed state, which re-enables submit after the
target post has already been accepted. Update the post-transfer-modal flow in
the finalize/acceptance path to use a distinct post-acceptance partial-failure
state or otherwise keep resubmission disabled, and make sure
`onTransferStateChange` and `dispatchModalState` in the transfer finalization
logic do not allow retry from the same dialog after target acceptance.
- Around line 335-338: The transfer failure handler in post-transfer-modal.tsx
reports the state as failed before the rollback cleanup finishes, which can let
retries race with temporary account deletion. Update the onError path in the
transfer flow to await cleanupTemporaryAccount({ deletePendingComment: true })
before calling onTransferStateChange?.('failed'), and keep the sequencing inside
the same onError callback so retries are only re-enabled after rollback
completes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 100b1cbf-b5c6-45bf-bd0f-6373890b6be2
📒 Files selected for processing (3)
src/components/post-transfer-modal/post-transfer-modal.tsxsrc/views/mod-queue/__tests__/mod-queue.test.tsxsrc/views/mod-queue/mod-queue.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/views/mod-queue/tests/mod-queue.test.tsx
- src/views/mod-queue/mod-queue.tsx
|
Addressed the latest Cursor Bugbot/CodeRabbit transfer-failure findings in 359ee04. Failures before target acceptance still clean up and allow retry, but failures after the target post may exist now enter a terminal finalization-failed state so the same dialog cannot publish another copy. The publish error path now awaits pending-post/account cleanup before re-enabling retry, and the mod queue now allows only one transfer modal to be open at a time. Added regression coverage for all three cases. |
|
Addressed the latest Cursor Bugbot hidden-lock finding in bb80e71. The mod queue now keeps the raw active transfer id in the shared transfer controls, so an in-flight transfer continues to block other transfer modals even if that active row drops out of the visible feed. Added a regression test for the active item leaving the visible queue while the lock remains held. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit bb80e71. Configure here.
|
Addressed the latest transfer-lock review note. The active transfer lock now stays while a hidden queue item is still publishing, then clears from the async terminal state even after the modal unmounts. I also removed the transfer modal centering transform so the close sprite renders on whole-pixel coordinates while keeping the button styling identical to the reply modal. |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/post-transfer-modal/post-transfer-modal.tsx (1)
165-175: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winGate transfer on
deleteCommentavailability too.
cleanupTemporaryAccount({ deletePendingComment: true })relies ondeleteComment, butcanSubmitcan still enable transfer when that action is unavailable. That would leave the pending temp-account post behind on challenge abandonment or publish failure.Proposed fix
typeof publishComment === 'function' && typeof publishCommentModeration === 'function' && typeof deleteAccount === 'function' && + typeof deleteComment === 'function' && hasSelectedTransferFields(selectedFields, availableFields);Also applies to: 266-274
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/post-transfer-modal/post-transfer-modal.tsx` around lines 165 - 175, `canSubmit` in `post-transfer-modal.tsx` does not gate submission on `deleteComment`, even though `cleanupTemporaryAccount({ deletePendingComment: true })` depends on it. Update the `canSubmit` check to require `typeof deleteComment === 'function'` alongside the existing action availability checks, and make sure any related submit/cleanup path that relies on `cleanupTemporaryAccount` uses the same guard so transfers cannot start when pending comment deletion is unavailable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/components/post-transfer-modal/post-transfer-modal.tsx`:
- Around line 165-175: `canSubmit` in `post-transfer-modal.tsx` does not gate
submission on `deleteComment`, even though `cleanupTemporaryAccount({
deletePendingComment: true })` depends on it. Update the `canSubmit` check to
require `typeof deleteComment === 'function'` alongside the existing action
availability checks, and make sure any related submit/cleanup path that relies
on `cleanupTemporaryAccount` uses the same guard so transfers cannot start when
pending comment deletion is unavailable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b59e6390-6fa1-45e0-9b81-4f6f60bfbece
📒 Files selected for processing (4)
src/components/post-transfer-modal/post-transfer-modal.module.csssrc/components/post-transfer-modal/post-transfer-modal.tsxsrc/views/mod-queue/__tests__/mod-queue.test.tsxsrc/views/mod-queue/mod-queue.tsx
💤 Files with no reviewable changes (1)
- src/components/post-transfer-modal/post-transfer-modal.module.css
🚧 Files skipped from review as they are similar to previous changes (1)
- src/views/mod-queue/mod-queue.tsx

Summary
5chan:transferredmoderation marker, render[Transferred], and suppress author flags on transferred posts.yotsuba-blight native controls, and reply-modal-matched close icon sizing.Verification
corepack yarn testcorepack yarn lintcorepack yarn type-checkcorepack yarn buildcorepack yarn doctor --verbose --scope changed(1 non-blocking maintainability warning: largePostTransferModal)playwright-clicomputed-style probe in Chrome, Firefox, and WebKit at desktop and mobile viewports: centered modal, lightyotsuba-bselect/buttons, reply-style header, no footer divider, and transfer close icon matching reply close icon at18px x 18px.Note
Medium Risk
Moderation paths that remove/reject posts and publish cross-board content; failures are partially handled (finalization errors, challenge abandon cleanup) but a mistaken transfer still deletes replies with the original.
Overview
Adds a moderator transfer flow for eligible top-level posts (mod queue and edit menu): pick a target board, choose which fields to copy, then recreate the post on the destination—not a protocol-level move.
The client publishes via a one-use temporary account, applies a
5chan:transferredmoderation flair on the new post, and rejects or removes the source with a markdown reason (e.g. moved to>>>/g/). UI copy states that replies are not moved and are lost with the original.PostTransferModalis draggable and non-blocking; mod queue enforces a single open transfer and locks actions while publishing.[Transferred]renders from moderation flairs only; author flags are hidden on transferred posts. NewmodQueue.*/transferstrings are added across locale files.Reviewed by Cursor Bugbot for commit d6265f9. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by CodeRabbit