Feat/improved review process#353
Conversation
…t aren't signed in default to false
…isk detection, with associated tests and UI components
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughAdds review-risk end-to-end (submission + author profiles), pending-list risk candidate paging, admin risk-only filter and UI, shared custom-field validation and normalization, CAPTCHA client/server split and verification, query-builder tweaks for shadow-ban/NSFW, infra gating (Sentry/init), and many tests. ChangesReview Risk Filtering and Submission Risk Detection
Custom Field Validation & Normalization
CAPTCHA & Provider Changes
Query Builders & Utilities
Infra, tooling, and misc
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~45 minutes
✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/server/repositories/pc-listings.repository.ts (1)
855-882:⚠️ Potential issue | 🟠 Major | ⚡ Quick winInconsistent shadow-ban filtering between pending listings repositories.
The
buildPendingListingsWheremethod in this file applies shadow-ban filtering to pending PC listings whencanSeeBannedUsersis false, but the equivalent method inlistings.repository.tsdoes not apply any shadow-ban filtering.This creates inconsistent behavior:
- Pending PC listings from shadow-banned users are hidden by default
- Pending handheld listings from shadow-banned users are shown
Both repositories should apply the same shadow-ban filtering logic, or neither should. Without alignment, moderators reviewing different listing types will have different visibility, which could lead to confusion and inconsistent workflows.
🤖 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/server/repositories/pc-listings.repository.ts` around lines 855 - 882, The PC listings method buildPendingListingsWhere applies shadow-ban filtering when canSeeBannedUsers is false but the handheld equivalent does not; align them by applying the same logic: compute const shadowBanFilter = canSeeBannedUsers ? undefined : buildShadowBanFilter(null) and merge it into the returned where clause as ...(shadowBanFilter && { author: shadowBanFilter }) (or conversely remove that merge from buildPendingListingsWhere if you intend no shadow-ban filtering), ensuring both repositories use the same approach for canSeeBannedUsers and buildShadowBanFilter so moderator visibility is consistent.
🧹 Nitpick comments (6)
src/server/api/routers/listings/admin.test.ts (2)
21-43: 💤 Low valueConsider extracting the inline mock implementation to a shared test helper.
The inline implementation of
createExistingAuthorBansMapduplicates business logic within the mock. If the real implementation changes, this could drift out of sync. Consider either importing the real helper or creating a shared test utility that both test files can use.🤖 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/server/api/routers/listings/admin.test.ts` around lines 21 - 43, The inline mock function createExistingAuthorBansMap duplicates production logic and should be extracted: move the implementation into a shared test helper (e.g., tests/helpers or tests/utils) or import the real helper if appropriate, then replace the inline definition in admin.test.ts with an import of createExistingAuthorBansMap and update any other tests to reuse that single exported function so logic remains centralized and in-sync with production code.
131-217: ⚡ Quick winAdd test coverage for the non-risky filter path.
The test suite only covers
riskFilter: 'risky'. Consider adding test cases for:
riskFilter: 'all'to verify the standard pending listings path- Edge cases like no risky listings found, or pagination edge cases
This would ensure both code paths are exercised and prevent regressions.
🤖 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/server/api/routers/listings/admin.test.ts` around lines 131 - 217, Add tests covering the non-risky and no-risky paths: in src/server/api/routers/listings/admin.test.ts add a test calling caller.getPending({ riskFilter: 'all', page: 1, limit: 20 }) that stubs mockGetPendingListings to return expected rows and asserts mockGetPendingListings was called (and mockGetPendingListingRiskCandidates was not), verifies returned listings and pagination; also add a test for riskFilter: 'risky' when mockGetPendingListingRiskCandidates returns an empty array to assert mockGetPendingListingsByIds is not called and result.listings.length and pagination.total are 0. Ensure you reference and use the existing helpers/mocks (createCaller, mockGetPendingListingRiskCandidates, mockGetPendingListingsByIds, mockGetPendingListings) and mirror the style/assertions used in the existing 'filters risk-only listings' test.src/server/api/routers/pcListings.test.ts (2)
81-103: ⚡ Quick winExtract the inline mock implementation to a shared test utility.
This duplicates the same inline implementation pattern from
admin.test.ts. Both test files would benefit from a shared test helper forcreateExistingAuthorBansMapto avoid duplication and ensure consistency.🤖 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/server/api/routers/pcListings.test.ts` around lines 81 - 103, Extract the inline createExistingAuthorBansMap implementation into a shared test utility function and have both tests import and use it instead of duplicating the logic; specifically, create and export a helper named createExistingAuthorBansMap that accepts the same listings shape and returns a Map<string, {reason:string}[]>, then replace the inline implementations in pcListings.test.ts and admin.test.ts with imports of that helper so both tests call the single shared function and retain identical behavior.
477-561: ⚡ Quick winAdd test coverage for the non-risky filter path.
Similar to
admin.test.ts, this test suite only coversriskFilter: 'risky'. Add test cases for:
riskFilter: 'all'or undefined to verify the standard pending listings retrieval- Edge cases such as empty results or different user roles
This ensures both code paths are tested and prevents regressions.
🤖 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/server/api/routers/pcListings.test.ts` around lines 477 - 561, The suite only tests the 'risky' riskFilter path; add tests that exercise the non-risky/default path by calling caller.pending with riskFilter: 'all' and with riskFilter omitted/undefined, verifying that mockRepositoryGetPendingListings is used (not getPendingListingRiskCandidates/getPendingListingsByIds), that returned pcListings match the mocked pending listings and pagination.total reflects the count, and add an edge-case test for empty results and a test using a non-moderator role (e.g., Role.USER) to validate permission-based branches; reference mocks and helpers like createCaller, caller.pending, mockRepositoryGetPendingListings, mockRepositoryGetPendingListingRiskCandidates, mockRepositoryGetPendingListingsByIds, mockComputeAuthorRiskProfiles and mockComputeSubmissionRiskProfiles to seed expected responses and assertions.src/components/ui/ReviewRiskIndicator.test.tsx (1)
33-68: ⚡ Quick winAdd test coverage for the investigate interaction path.
The suite does not currently verify the clickable
onInvestigatebehavior (mouse + keyboard), which is core logic in the component.🧪 Suggested test addition
-import { render, screen } from '@testing-library/react' -import { describe, expect, it } from 'vitest' +import { fireEvent, render, screen } from '@testing-library/react' +import { describe, expect, it, vi } from 'vitest' @@ it('renders for submission risk without author risk', () => { @@ }) + + it('invokes investigate callback for clickable indicator', () => { + const onInvestigate = vi.fn() + render( + <ReviewRiskIndicator + authorRiskProfile={authorRiskProfile} + submissionRiskProfile={submissionRiskProfile} + onInvestigate={onInvestigate} + />, + ) + + const button = screen.getByRole('button') + fireEvent.click(button) + fireEvent.keyDown(button, { key: 'Enter' }) + fireEvent.keyDown(button, { key: ' ' }) + + expect(onInvestigate).toHaveBeenCalledWith('author-1') + expect(onInvestigate).toHaveBeenCalledTimes(3) + }) })🤖 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/ui/ReviewRiskIndicator.test.tsx` around lines 33 - 68, Add tests for the investigate interaction by rendering ReviewRiskIndicator with a jest.fn() onInvestigate prop and asserting it is called on mouse click and keyboard activation; locate the interactable element via screen.getByRole('status') (or the same role used in the component), use userEvent.click(status) to simulate mouse activation and userEvent.keyboard('{Enter}') and userEvent.keyboard(' ') after status.focus() to simulate Enter/Space keyboard activation, and assert onInvestigate was invoked for each interaction; ensure the test covers both the case with author/submission risk and the case with only submission risk so the click target exists in both scenarios.src/server/repositories/pc-listings.repository.ts (1)
890-894: 💤 Low valueConsider extracting active ban criteria to a helper method.
The inline active ban filtering (lines 891-893) duplicates the same logic used in
forListinclude at lines 86-87. While correct, extracting this to agetActiveBanWhere()helper method (similar tolistings.repository.tsline 572-577) would improve maintainability and ensure consistency across query builders.♻️ Suggested refactor
Add a private helper method:
+ private getActiveBanWhere(): Prisma.UserBanWhereInput { + return { + isActive: true, + OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }], + } + } + private getPendingListingRiskCandidateSelect() { return { id: true, authorId: true, author: { select: { userBans: { - where: { - isActive: true, - OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }], - }, + where: this.getActiveBanWhere(), select: { reason: true }, }, }, },Then reuse in the
forListinclude at line 85:author: { include: { userBans: { - where: { - isActive: true, - OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }], - }, + where: this.getActiveBanWhere(), select: { id: true, reason: true, bannedAt: true, expiresAt: true }, }, }, },🤖 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/server/repositories/pc-listings.repository.ts` around lines 890 - 894, Extract the duplicated active-ban predicate into a private helper (e.g., private getActiveBanWhere()) that returns the object { isActive: true, OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }] }, add it to the PCListingsRepository class, and replace the inline userBans.where in the forList include and the other occurrence (the block using userBans: { where: { ... } }) to call getActiveBanWhere() so both queries reuse the same logic for consistency.
🤖 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/admin/ReviewRiskFilterButton.tsx`:
- Around line 12-19: This toggle button (Button in ReviewRiskFilterButton) needs
an accessible pressed state: add aria-pressed={props.isActive} to the Button
element so screen readers announce the on/off state; ensure props.isActive is a
boolean in the component's props type and keep onToggle as the click handler.
In `@src/server/api/routers/listings/admin.ts`:
- Around line 89-109: The final by-ID fetch (repository.getPendingListingsByIds)
can return listings that no longer meet the original scope (emulatorIds/pending)
between the two queries; update the fetch to re-apply the same scope checks used
to build riskCandidates: either modify repository.getPendingListingsByIds to
accept the same scope parameters (emulatorIds and pending status/search
constraints) and use them in the DB query, or after fetching pageListings filter
them against the original riskCandidates set (or a map keyed by id) so only
listings that still match the emulatorIds/pending scope are kept before calling
attachReviewRiskProfiles; reference getPendingListingRiskCandidates,
getRiskyReviewItemIds, getPendingListingsByIds, computeReviewRiskProfiles, and
attachReviewRiskProfiles.
In `@src/server/api/routers/pcListings.ts`:
- Around line 467-489: The refetch of listings via
repository.getPendingListingsByIds creates a TOCTOU gap because the original
candidate query used emulatorIds and other auth constraints; fix by keeping the
same authorization scope when loading the final page: either change
getPendingListingsByIds to accept the same filter arguments (emulatorIds,
canSeeBannedUsers, search/sort if needed) and call it with the same parameters
used for getPendingListingRiskCandidates, or avoid the re-query entirely by
selecting full listing fields in getPendingListingRiskCandidates and using those
results (so computeReviewRiskProfiles and attachReviewRiskProfiles still work).
Update calls around getPendingListingRiskCandidates, getPendingListingsByIds,
computeReviewRiskProfiles, and attachReviewRiskProfiles to ensure emulatorIds
and canSeeBannedUsers are preserved when producing paginatedPcListings.
---
Outside diff comments:
In `@src/server/repositories/pc-listings.repository.ts`:
- Around line 855-882: The PC listings method buildPendingListingsWhere applies
shadow-ban filtering when canSeeBannedUsers is false but the handheld equivalent
does not; align them by applying the same logic: compute const shadowBanFilter =
canSeeBannedUsers ? undefined : buildShadowBanFilter(null) and merge it into the
returned where clause as ...(shadowBanFilter && { author: shadowBanFilter }) (or
conversely remove that merge from buildPendingListingsWhere if you intend no
shadow-ban filtering), ensuring both repositories use the same approach for
canSeeBannedUsers and buildShadowBanFilter so moderator visibility is
consistent.
---
Nitpick comments:
In `@src/components/ui/ReviewRiskIndicator.test.tsx`:
- Around line 33-68: Add tests for the investigate interaction by rendering
ReviewRiskIndicator with a jest.fn() onInvestigate prop and asserting it is
called on mouse click and keyboard activation; locate the interactable element
via screen.getByRole('status') (or the same role used in the component), use
userEvent.click(status) to simulate mouse activation and
userEvent.keyboard('{Enter}') and userEvent.keyboard(' ') after status.focus()
to simulate Enter/Space keyboard activation, and assert onInvestigate was
invoked for each interaction; ensure the test covers both the case with
author/submission risk and the case with only submission risk so the click
target exists in both scenarios.
In `@src/server/api/routers/listings/admin.test.ts`:
- Around line 21-43: The inline mock function createExistingAuthorBansMap
duplicates production logic and should be extracted: move the implementation
into a shared test helper (e.g., tests/helpers or tests/utils) or import the
real helper if appropriate, then replace the inline definition in admin.test.ts
with an import of createExistingAuthorBansMap and update any other tests to
reuse that single exported function so logic remains centralized and in-sync
with production code.
- Around line 131-217: Add tests covering the non-risky and no-risky paths: in
src/server/api/routers/listings/admin.test.ts add a test calling
caller.getPending({ riskFilter: 'all', page: 1, limit: 20 }) that stubs
mockGetPendingListings to return expected rows and asserts
mockGetPendingListings was called (and mockGetPendingListingRiskCandidates was
not), verifies returned listings and pagination; also add a test for riskFilter:
'risky' when mockGetPendingListingRiskCandidates returns an empty array to
assert mockGetPendingListingsByIds is not called and result.listings.length and
pagination.total are 0. Ensure you reference and use the existing helpers/mocks
(createCaller, mockGetPendingListingRiskCandidates, mockGetPendingListingsByIds,
mockGetPendingListings) and mirror the style/assertions used in the existing
'filters risk-only listings' test.
In `@src/server/api/routers/pcListings.test.ts`:
- Around line 81-103: Extract the inline createExistingAuthorBansMap
implementation into a shared test utility function and have both tests import
and use it instead of duplicating the logic; specifically, create and export a
helper named createExistingAuthorBansMap that accepts the same listings shape
and returns a Map<string, {reason:string}[]>, then replace the inline
implementations in pcListings.test.ts and admin.test.ts with imports of that
helper so both tests call the single shared function and retain identical
behavior.
- Around line 477-561: The suite only tests the 'risky' riskFilter path; add
tests that exercise the non-risky/default path by calling caller.pending with
riskFilter: 'all' and with riskFilter omitted/undefined, verifying that
mockRepositoryGetPendingListings is used (not
getPendingListingRiskCandidates/getPendingListingsByIds), that returned
pcListings match the mocked pending listings and pagination.total reflects the
count, and add an edge-case test for empty results and a test using a
non-moderator role (e.g., Role.USER) to validate permission-based branches;
reference mocks and helpers like createCaller, caller.pending,
mockRepositoryGetPendingListings, mockRepositoryGetPendingListingRiskCandidates,
mockRepositoryGetPendingListingsByIds, mockComputeAuthorRiskProfiles and
mockComputeSubmissionRiskProfiles to seed expected responses and assertions.
In `@src/server/repositories/pc-listings.repository.ts`:
- Around line 890-894: Extract the duplicated active-ban predicate into a
private helper (e.g., private getActiveBanWhere()) that returns the object {
isActive: true, OR: [{ expiresAt: null }, { expiresAt: { gt: new Date() } }] },
add it to the PCListingsRepository class, and replace the inline userBans.where
in the forList include and the other occurrence (the block using userBans: {
where: { ... } }) to call getActiveBanWhere() so both queries reuse the same
logic for consistency.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: e401bb9c-b655-46b0-ae0a-d291c6aea83a
📒 Files selected for processing (32)
next.config.tspackage.jsonsrc/app/admin/approvals/components/ApprovalModal.tsxsrc/app/admin/approvals/page.tsxsrc/app/admin/hooks/index.tssrc/app/admin/hooks/useReviewRiskFilter.tssrc/app/admin/pc-listing-approvals/components/ApprovalModal.tsxsrc/app/admin/pc-listing-approvals/page.tsxsrc/app/global-error.tsxsrc/app/testing/page.tsxsrc/components/admin/ReviewRiskFilterButton.tsxsrc/components/admin/index.tssrc/components/ui/ReviewRiskIndicator.test.tsxsrc/components/ui/ReviewRiskIndicator.tsxsrc/components/ui/ReviewRiskWarningBanner.test.tsxsrc/components/ui/ReviewRiskWarningBanner.tsxsrc/components/ui/index.tssrc/schemas/listing.tssrc/schemas/pcListing.tssrc/schemas/submissionRisk.tssrc/server/api/routers/listings/admin.test.tssrc/server/api/routers/listings/admin.tssrc/server/api/routers/pcListings.test.tssrc/server/api/routers/pcListings.tssrc/server/repositories/listings.repository.tssrc/server/repositories/pc-listings.repository.tssrc/server/services/author-risk.service.tssrc/server/services/review-risk.service.test.tssrc/server/services/review-risk.service.tssrc/server/services/submission-risk.service.test.tssrc/server/services/submission-risk.service.tssrc/server/utils/query-builders.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/lib/cors.ts (1)
30-34: 💤 Low valueOptional: use a
Setto dedupe instead of repeatedincludesscans.
addMissingOriginsdoes an O(n)includesper candidate, andgetAllowedOriginscalls it 3–4 times. The list is small so this isn't a real performance issue, but aSet-based accumulator would be both cleaner and avoid the in-place mutation pattern.♻️ Sketch
-function addMissingOrigins(origins: string[], additionalOrigins: string[]) { - for (const origin of additionalOrigins) { - if (!origins.includes(origin)) origins.push(origin) - } -} +function addMissingOrigins(origins: Set<string>, additionalOrigins: readonly string[]) { + for (const origin of additionalOrigins) origins.add(origin) +}Then build
originsas aSet<string>ingetAllowedOriginsand return[...origins]at the end.🤖 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/lib/cors.ts` around lines 30 - 34, The current addMissingOrigins uses repeated origins.includes scans and mutates an array; change the logic to use a Set for deduplication: in getAllowedOrigins create a Set<string> (e.g., allowed = new Set()), add the base origins and then add additional origins with Set.add (or refactor addMissingOrigins to accept and add into a Set rather than an array), and finally return Array.from(allowed) (or [...allowed]); update references to addMissingOrigins/getAllowedOrigins accordingly to remove O(n) includes and in-place array mutation.
🤖 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/lib/cors.test.ts`:
- Around line 21-27: The test titled "allows localhost during CI production-mode
E2E runs" does not stub NODE_ENV so it does not actually exercise
production-mode logic in getAllowedOrigins(); update the test to stub
NODE_ENV='production' (alongside CI='true', NEXT_PUBLIC_ALLOWED_ORIGINS='' and
ALLOWED_ORIGINS='') so getAllowedOrigins() hits the intended production branch
that adds LOCAL_TEST_ORIGINS, or alternatively rename the test to remove the
"production-mode" claim if you prefer not to change environment stubs.
In `@src/lib/cors.ts`:
- Around line 56-58: The current CI-only guard that calls
addMissingOrigins(origins, LOCAL_TEST_ORIGINS) should also verify NODE_ENV to
mirror defensive checks elsewhere; update the condition around addMissingOrigins
(the block that uses process.env.CI) to require process.env.NODE_ENV !==
'production' in addition to process.env.CI === 'true' so it only runs in
non-production CI/test environments and remains consistent with
threeDsGameRefreshService.ts and switchGameRefreshService.ts.
---
Nitpick comments:
In `@src/lib/cors.ts`:
- Around line 30-34: The current addMissingOrigins uses repeated
origins.includes scans and mutates an array; change the logic to use a Set for
deduplication: in getAllowedOrigins create a Set<string> (e.g., allowed = new
Set()), add the base origins and then add additional origins with Set.add (or
refactor addMissingOrigins to accept and add into a Set rather than an array),
and finally return Array.from(allowed) (or [...allowed]); update references to
addMissingOrigins/getAllowedOrigins accordingly to remove O(n) includes and
in-place array mutation.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 07cb3a01-0789-4a1e-a6d8-ad69b493edc9
📒 Files selected for processing (2)
src/lib/cors.test.tssrc/lib/cors.ts
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
src/server/utils/query-builders.test.ts (1)
146-148: ⚡ Quick winAdd explicit
nullcoverage forbuildNsfwFilter.Current tests validate omitted args but not
null. Add a case forbuildNsfwFilter(null)to lock expected behavior and prevent regressions.Suggested test addition
it('should return filter by default', () => { expect(buildNsfwFilter()).toEqual({ isErotic: false }) }) + + it('should return filter when showNsfw is null', () => { + expect(buildNsfwFilter(null)).toEqual({ isErotic: false }) + })🤖 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/server/utils/query-builders.test.ts` around lines 146 - 148, Add an explicit unit test that calls buildNsfwFilter(null) and asserts it returns the same default object as when no argument is provided (expect(buildNsfwFilter(null)).toEqual({ isErotic: false })), so the behavior for null input is locked and regressions are prevented; place this alongside the existing 'should return filter by default' test in the query-builders tests and reference the buildNsfwFilter function in the new test.tests/helpers/data-factory.ts (1)
595-609: 💤 Low valueConsider moving the click outside the retry loop.
The current implementation clicks the button inside the
toPasscallback (line 602), which may result in multiple clicks if the dialog takes longer than 2 seconds to appear. While this likely works, it could be more efficient to click once and only retry the visibility assertion:async function openUserDetailsDialog(page: Page, userRow: Locator): Promise<Locator> { const viewDetailsButton = userRow.getByRole('button', { name: 'View User Details' }) await expect(viewDetailsButton).toBeVisible() const dialog = page.locator('[role="dialog"]') // Click once outside the retry loop if (!(await dialog.isVisible())) { await viewDetailsButton.click() } // Then retry only the visibility check await expect(dialog).toBeVisible({ timeout: 10000 }) return dialog }However, the current implementation with the conditional click inside
toPassdoes provide more resilience if the button needs to be clicked multiple times, so this is optional.🤖 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 `@tests/helpers/data-factory.ts` around lines 595 - 609, The click on viewDetailsButton is currently inside the retry callback in openUserDetailsDialog which can cause multiple clicks; instead, perform a single click before entering the retry/assertion and then only retry the visibility check: check viewDetailsButton visibility, compute dialog = page.locator('[role="dialog"]'), if dialog isn't visible await viewDetailsButton.click(), then replace the toPass block with a single await expect(dialog).toBeVisible({ timeout: 10000 }) and return dialog.
🤖 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/server/utils/query-builders.ts`:
- Around line 133-136: The current filter returns undefined unless showNsfw ===
false, which lets null bypass NSFW filtering; update the condition in the
function handling parameters showNsfw and fieldName so that only an explicit
true allows NSFW (e.g., treat null like false). Concretely, change the predicate
that decides to return { [fieldName]: false } versus undefined to require
showNsfw === true to allow NSFW (or use showNsfw !== true to apply the false
filter), referencing the showNsfw parameter and the fieldName return shape.
In `@src/utils/custom-field-validation.ts`:
- Around line 33-52: The centralized schema createCustomFieldValuesSchema
currently only enforces required/non-empty checks and drops type-specific
validation; update the superRefine callback inside createCustomFieldValuesSchema
to, for each CustomFieldDefinitionWithOptions passed, fetch the fieldValue via
getFieldValue and run type-specific validations (using field.type and
field.options): e.g., for SELECT ensure the value matches one of field.options,
for RANGE ensure the value is numeric and within min/max, for NUMBER ensure
numeric shape, for URL/text enforce string and pattern/format, and for any
option-based or multi-select enforce arrays of valid option ids — when a check
fails call ctx.addIssue with code z.ZodIssueCode.custom, a clear message and
path computed by getFieldErrorPath(values, field.id, index); reuse
isCustomFieldValueEmpty to skip absent values and keep
CustomFieldValueSchema/JsonValueSchema as the base.
---
Nitpick comments:
In `@src/server/utils/query-builders.test.ts`:
- Around line 146-148: Add an explicit unit test that calls
buildNsfwFilter(null) and asserts it returns the same default object as when no
argument is provided (expect(buildNsfwFilter(null)).toEqual({ isErotic: false
})), so the behavior for null input is locked and regressions are prevented;
place this alongside the existing 'should return filter by default' test in the
query-builders tests and reference the buildNsfwFilter function in the new test.
In `@tests/helpers/data-factory.ts`:
- Around line 595-609: The click on viewDetailsButton is currently inside the
retry callback in openUserDetailsDialog which can cause multiple clicks;
instead, perform a single click before entering the retry/assertion and then
only retry the visibility check: check viewDetailsButton visibility, compute
dialog = page.locator('[role="dialog"]'), if dialog isn't visible await
viewDetailsButton.click(), then replace the toPass block with a single await
expect(dialog).toBeVisible({ timeout: 10000 }) and return dialog.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 18c3fe7c-f662-4c00-bc6c-0dcd1db36cdb
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (47)
.gitignoreduplication-audit.mdpackage.jsonpnpm-workspace.yamlsrc/app/listings/components/shared/approval/ApprovalModalSharedComponents.tsxsrc/app/listings/components/shared/utils/customFieldHelpers.tsxsrc/app/listings/new/NewListingPage.tsxsrc/app/listings/new/form-schemas/createDynamicListingSchema.tssrc/app/pc-listings/new/NewPcListingPage.tsxsrc/app/pc-listings/new/form-schemas/createDynamicPcListingSchema.tssrc/components/Providers.tsxsrc/components/admin/AdminErrorState.test.tsxsrc/components/admin/AdminErrorState.tsxsrc/components/admin/ReviewRiskFilterButton.test.tsxsrc/components/admin/ReviewRiskFilterButton.tsxsrc/components/ui/Switch.tsxsrc/components/ui/index.tssrc/instrumentation-client.tssrc/lib/captcha/config.tssrc/lib/captcha/hooks.tssrc/lib/captcha/verify.test.tssrc/lib/captcha/verify.tssrc/lib/cors.test.tssrc/lib/cors.tssrc/lib/errors.tssrc/schemas/pcListing.tssrc/server/api/routers/listings/admin.tssrc/server/api/routers/listings/core.test.tssrc/server/api/routers/listings/core.tssrc/server/api/routers/mobile/pcListings.tssrc/server/api/routers/pcListings.test.tssrc/server/api/routers/pcListings.tssrc/server/api/utils/pcListingHelpers.tssrc/server/repositories/listings.repository.tssrc/server/repositories/pc-listings.repository.test.tssrc/server/repositories/pc-listings.repository.tssrc/server/services/author-risk.service.tssrc/server/services/review-risk.service.tssrc/server/services/submission-risk.service.test.tssrc/server/utils/custom-field-values.tssrc/server/utils/query-builders.test.tssrc/server/utils/query-builders.tssrc/utils/custom-field-validation.tssrc/utils/custom-fields.tstests/filtering.spec.tstests/helpers/data-factory.tstests/pages/ListingsPage.ts
💤 Files with no reviewable changes (1)
- duplication-audit.md
✅ Files skipped from review due to trivial changes (2)
- src/server/utils/custom-field-values.ts
- .gitignore
🚧 Files skipped from review as they are similar to previous changes (4)
- src/server/services/submission-risk.service.test.ts
- src/server/services/author-risk.service.ts
- src/lib/cors.test.ts
- src/server/repositories/listings.repository.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/utils/custom-field-validation.ts (1)
57-71: 💤 Low valueConsider adding a default case for defensive coding.
The switch on
field.typelacks a default case. IfCustomFieldTypeis extended or runtime data has an unexpected type, the function returnsundefined, causing line 94 to treat valid values as invalid. While TypeScript's exhaustiveness checking mitigates compile-time risk, a defensive default prevents silent failures.🛡️ Suggested defensive default
case CustomFieldType.TEXT: case CustomFieldType.TEXTAREA: return typeof value === 'string' + default: + return true } }🤖 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/utils/custom-field-validation.ts` around lines 57 - 71, The switch in isCustomFieldValueValid can fall through and return undefined for unknown CustomFieldType values; add a defensive default branch in the switch on field.type (inside isCustomFieldValueValid) that returns false (or logs/throws if you prefer strict behavior) so the function always returns a boolean for any runtime value of CustomFieldType and avoids silent failures when new types are added or invalid data is passed.
🤖 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/server/init.ts`:
- Around line 231-233: The current initializeServerOnce sets
globalForServices.initializationPromise to
initializeServer().catch(logger.error), which swallows errors and leaves
initializationPromise fulfilled with undefined, preventing retries; change the
catch handler so that on error it clears globalForServices.initializationPromise
(set it back to undefined or delete it) and then re-throws the error (or returns
a rejected promise) so failures aren’t cached; update the initializeServerOnce
implementation (referencing initializeServerOnce,
globalForServices.initializationPromise, initializeServer, and logger.error) to
perform this reset-before-rethrow behavior.
---
Nitpick comments:
In `@src/utils/custom-field-validation.ts`:
- Around line 57-71: The switch in isCustomFieldValueValid can fall through and
return undefined for unknown CustomFieldType values; add a defensive default
branch in the switch on field.type (inside isCustomFieldValueValid) that returns
false (or logs/throws if you prefer strict behavior) so the function always
returns a boolean for any runtime value of CustomFieldType and avoids silent
failures when new types are added or invalid data is passed.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3b28834d-7bee-4335-86ed-e5e1f76dbe78
📒 Files selected for processing (33)
.github/pull_request_template.mdinstrumentation.tssrc/app/admin/approvals/components/ApprovalModal.tsxsrc/app/admin/approvals/page.tsxsrc/app/admin/layout.tsxsrc/app/admin/loading.tsxsrc/app/admin/pc-listing-approvals/components/ApprovalModal.tsxsrc/app/admin/pc-listing-approvals/page.tsxsrc/app/listings/components/shared/CustomFieldRenderer.tsxsrc/app/listings/components/shared/utils/customFieldHelpers.test.tssrc/app/listings/components/shared/utils/customFieldHelpers.tsxsrc/app/listings/new/NewListingPage.tsxsrc/app/listings/new/form-schemas/createDynamicListingSchema.test.tssrc/app/listings/new/form-schemas/createDynamicListingSchema.tssrc/app/listings/new/hooks/__tests__/useEmulatorConfigImporter.test.tsxsrc/app/listings/new/hooks/useEmulatorConfigImporter.tsxsrc/app/pc-listings/new/NewPcListingPage.tsxsrc/app/pc-listings/new/form-schemas/createDynamicPcListingSchema.test.tssrc/app/pc-listings/new/form-schemas/createDynamicPcListingSchema.tssrc/app/terms/page.tsxsrc/components/admin/index.tssrc/components/admin/review-risk/ReviewRiskIndicator.test.tsxsrc/components/admin/review-risk/ReviewRiskIndicator.tsxsrc/components/admin/review-risk/ReviewRiskWarningBanner.test.tsxsrc/components/admin/review-risk/ReviewRiskWarningBanner.tsxsrc/components/admin/review-risk/index.tssrc/components/admin/review-risk/reviewRiskDisplay.tssrc/components/ui/index.tssrc/server/init.tssrc/server/utils/query-builders.test.tssrc/server/utils/query-builders.tssrc/utils/custom-field-validation.test.tssrc/utils/custom-field-validation.ts
💤 Files with no reviewable changes (1)
- src/app/listings/components/shared/utils/customFieldHelpers.tsx
✅ Files skipped from review due to trivial changes (10)
- .github/pull_request_template.md
- src/app/listings/new/hooks/useEmulatorConfigImporter.tsx
- src/app/listings/new/form-schemas/createDynamicListingSchema.test.ts
- src/app/listings/new/hooks/tests/useEmulatorConfigImporter.test.tsx
- src/app/listings/components/shared/utils/customFieldHelpers.test.ts
- src/app/admin/loading.tsx
- src/app/admin/layout.tsx
- src/app/terms/page.tsx
- src/app/pc-listings/new/form-schemas/createDynamicPcListingSchema.test.ts
- src/app/listings/components/shared/CustomFieldRenderer.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
- src/app/listings/new/NewListingPage.tsx
- src/server/utils/query-builders.ts
- src/server/utils/query-builders.test.ts
- src/app/pc-listings/new/NewPcListingPage.tsx
- src/app/admin/approvals/components/ApprovalModal.tsx
- src/app/admin/pc-listing-approvals/components/ApprovalModal.tsx
- src/app/admin/approvals/page.tsx
- src/app/admin/pc-listing-approvals/page.tsx
…ve server initialization error handling
Description
Fixes # (issue)
Type of change
How Has This Been Tested?
Screenshots (if applicable)
Checklist
Notes for reviewers
Summary by CodeRabbit
New Features
Bug Fixes
Improvements
Documentation