feat: v0.15.0 - #469
Conversation
Fix GPU view modal close button
Add comment submit shortcut
…ection Lazy load language detection with franc-min
…ication-tags Remove unused SEO verification tags
Use pagination helper in SoCs router
…lated utilities and tests
…U and GPU entities.
…based game image validation and rendering
…outer Refactor hardware modules around feature-owned architecture
…tch lookup, and trpc caching
Server hardening
test: add handheld listing repository tests and fix search filters
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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 |
There was a problem hiding this comment.
40 issues found across 423 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/app/games/components/GameCard.tsx">
<violation number="1" location="src/app/games/components/GameCard.tsx:37">
P2: For below-the-fold cards (`eagerLoad` false) the `loading` value resolves to `undefined`, so external images render as plain `<img>` tags with no `loading` attribute and load eagerly. The previous `priority={false}` path made next/image lazy-load these images by default. Set `loading` to `'lazy'` when not eager to preserve lazy loading on the games grid.</violation>
<violation number="2" location="src/app/games/components/GameCard.tsx:37">
P2: For the four above-fold cards, replacing `priority` with `loading="eager"` removes Next's preload and high fetch-priority behavior, which can delay the first-row images. Pass the eager flag through `preload` so `ImageRenderer` preserves the previous loading priority for both Next and native images.</violation>
</file>
<file name="src/app/pc-listings/[id]/components/EditPcListingButton.tsx">
<violation number="1" location="src/app/pc-listings/[id]/components/EditPcListingButton.tsx:23">
P2: Removing the 60s refetchInterval makes the remaining-minutes countdown and the edit-time-expired transition stale while the page stays open. remainingMinutes and timeExpired are computed server-side per query, so with only `refetchOnWindowFocus: true` the button title keeps showing a stale 'N minutes remaining after approval' and the button stays enabled even after the 60-minute window passes, until the user refocuses or reloads. Restore a short polling interval (or decrement client-side) so the countdown/expiry stay accurate.</violation>
</file>
<file name="src/app/admin/performance/components/ReplacementSelectionModal.tsx">
<violation number="1" location="src/app/admin/performance/components/ReplacementSelectionModal.tsx:43">
P2: After a successful replacement or cancel, reopening this mounted modal reuses the previous `selectedReplacementId`. Reset the selection when `scaleToDelete` changes or the modal closes before sending it as the next replacement.</violation>
</file>
<file name="src/features/hardware/gpu/server/persistence/gpu.errors.ts">
<violation number="1" location="src/features/hardware/gpu/server/persistence/gpu.errors.ts:9">
P3: This GPU translator duplicates `translateCpuWriteError` line-for-line, so future Prisma error-mapping fixes can diverge between the CPU and GPU paths. Extract the shared translation logic and supply resource-specific errors as parameters.</violation>
</file>
<file name="src/features/hardware/gpu/client/admin/GpuFormModal.tsx">
<violation number="1" location="src/features/hardware/gpu/client/admin/GpuFormModal.tsx:50">
P2: When an admin adds the first GPU for a brand, this query hides that brand because `category: 'gpu'` requires an existing GPU. Query all brands here so newly created brands can receive their first GPU.</violation>
<violation number="2" location="src/features/hardware/gpu/client/admin/GpuFormModal.tsx:88">
P3: The label points to `brand`, but `Autocomplete` receives no `id`, so its input has no matching id and the label is not associated. Pass `id="brand"` to the autocomplete input.</violation>
</file>
<file name="src/app/home/components/HomeTrendingDevices.tsx">
<violation number="1" location="src/app/home/components/HomeTrendingDevices.tsx:21">
P2: After one minute, remounting the home page marks `trendingSummary` stale, and after five minutes it discards the result entirely, causing repeated network requests for a summary whose server cache lasts six hours. Restore the six-hour `staleTime` and twelve-hour `gcTime` (or add an endpoint-specific default) to avoid this unnecessary traffic.</violation>
</file>
<file name="src/app/profile/components/connections/SocialConnectionList.tsx">
<violation number="1" location="src/app/profile/components/connections/SocialConnectionList.tsx:41">
P3: Assistive technology does not receive a loading status here: a plain `div` has no semantic role, and every `Skeleton` child is `aria-hidden`. Give the wrapper `role="status"` and `aria-busy="true"` so loading is announced.</violation>
</file>
<file name="src/app/admin/games/[id]/form-schemas/updateGameSchema.ts">
<violation number="1" location="src/app/admin/games/[id]/form-schemas/updateGameSchema.ts:7">
P2: When a game still has a legacy `http://` image URL, submitting any unrelated edit now fails validation because this schema validates the defaulted image field. Preserve unchanged legacy values or migrate existing image URLs before enforcing HTTPS here.</violation>
</file>
<file name="src/app/admin/trust-logs/page.tsx">
<violation number="1" location="src/app/admin/trust-logs/page.tsx:156">
P2: The 'Trusted+ Users' stat counts every level with minScore >= 250, which includes the base 'Trusted' level (250-499), not just 'Trusted+' and above. This makes the displayed number higher than the label implies. Filter on minScore >= 500 for 'Trusted+' level and above, or rename the stat if 'Trusted and above' is intended.</violation>
<violation number="2" location="src/app/admin/trust-logs/page.tsx:157">
P2: When no users are Trusted or above, `trustedPlusUsers` is `0`, but this card displays `...` instead of `0`. Preserve zero values in `AdminStatsDisplay` so the statistic is accurate.</violation>
</file>
<file name="src/app/admin/audit-logs/page.tsx">
<violation number="1" location="src/app/admin/audit-logs/page.tsx:184">
P2: Keyboard-only and assistive-technology users cannot select another action or entity because `Dropdown` exposes click-only, non-focusable options. Restore native selects here or make `Dropdown` keyboard-accessible with listbox semantics.</violation>
</file>
<file name="src/data/storageKeys.ts">
<violation number="1" location="src/data/storageKeys.ts:40">
P3: After upgrading, existing handheld processed-report column preferences are discarded because `useColumnVisibility` reads only this new localStorage key. Keep the old key or migrate its value before switching names.</violation>
</file>
<file name="src/features/hardware/cpu/client/admin/CpuTable.tsx">
<violation number="1" location="src/features/hardware/cpu/client/admin/CpuTable.tsx:34">
P2: Keyboard-only users cannot sort the new CPU table because `SortableHeader` puts the click handler directly on a non-focusable `<th>`. Make the sortable header keyboard-accessible, such as by rendering a button inside the header or adding equivalent focus and keyboard handling.</violation>
</file>
<file name="config/image-hosts.ts">
<violation number="1" location="config/image-hosts.ts:14">
P2: When a nested Clerk subdomain reaches `ImageRenderer`, this pattern makes the shared matcher select `next/image`, but Next only allows one subdomain for `*.clerk.com`. Make the matcher and Next pattern semantics identical, or replace the wildcard with the exact Clerk image hosts, so these URLs use the correct rendering path.</violation>
</file>
<file name="src/app/api/trpc/[trpc]/route.ts">
<violation number="1" location="src/app/api/trpc/[trpc]/route.ts:21">
P2: When an anonymous cacheable lookup is stored, a shared cache can serve that object to an authenticated request before this handler runs because the response does not vary on auth headers. Add `Vary` for `Cookie`, `Authorization`, `x-api-key`, and `x-auth-token` so the private policy is enforceable at the cache boundary.</violation>
</file>
<file name="next.config.ts">
<violation number="1" location="next.config.ts:154">
P2: Uploads between 5,000,001 and 5,242,880 bytes pass `handleFileUpload` but fail when `next/image` optimizes them. Set this limit to the same 5 MiB value used by `MAX_FILE_SIZE`.</violation>
<violation number="2" location="next.config.ts:161">
P1: Removing `dangerouslyAllowSVG: true` while `getSafePlaceholderImageUrl` still returns `https://placehold.co/...` URLs breaks every placeholder/fallback image in production.</violation>
</file>
<file name="src/components/ui/form/async-multi-select/AsyncMultiSelect.tsx">
<violation number="1" location="src/components/ui/form/async-multi-select/AsyncMultiSelect.tsx:167">
P2: When an existing selection is not on the current page, adding another option while the `getByIds` query refreshes emits incomplete `selectedOptions`. The listings and PC sidebar handlers record those names in analytics, so retain previous option metadata before invoking `onChange`.</violation>
</file>
<file name="src/components/ui/ImageRenderer.tsx">
<violation number="1" location="src/components/ui/ImageRenderer.tsx:42">
P2: When an external image omits `preload` and `loading`, this native branch eagerly downloads it instead of preserving `next/image`'s default lazy loading. Pages with many external game images will fetch below-the-fold full-size images immediately; default `loading` to `lazy` unless `preload` is enabled.</violation>
</file>
<file name="src/features/hardware/gpu/shared/gpu.schemas.ts">
<violation number="1" location="src/features/hardware/gpu/shared/gpu.schemas.ts:35">
P2: The mobile GPU catalog accepts negative, zero, and fractional pagination values here. `listMobileCompatibility` passes them through to Prisma, so malformed requests reach invalid `take`/`skip` values and fail as server errors; add integer bounds while retaining the compatibility route's unbounded upper limit.</violation>
</file>
<file name="src/app/listings/[id]/components/EditListingButton.tsx">
<violation number="1" location="src/app/listings/[id]/components/EditListingButton.tsx:23">
P2: Removing `refetchInterval: 60000` stops the canEdit query from refreshing while the page is open, so the "X minutes remaining" text freezes and the Edit button stays enabled after the 60-minute window expires until the user refocuses the window. This directly undoes the interval's stated purpose ("update time remaining"). The 60-minute limit is still enforced server-side on update, so this is a UI staleness regression; either keep a polling interval or add a client-side countdown that updates remainingMinutes/timeExpired.</violation>
</file>
<file name="src/server/api/mobileContext.ts">
<violation number="1" location="src/server/api/mobileContext.ts:123">
P2: When `Authorization` contains the `ApiKey` scheme without a token, this guard returns no credential, so the request bypasses the new strict invalid-API-key path. Reject an empty `ApiKey` credential instead of falling through to anonymous access.</violation>
</file>
<file name="src/features/hardware/cpu/shared/cpu.schemas.ts">
<violation number="1" location="src/features/hardware/cpu/shared/cpu.schemas.ts:35">
P2: Malformed mobile catalog pagination reaches Prisma because this schema does not validate integer, positive, or nonnegative values. Validate `limit`, `offset`, and `page` while retaining the intentionally supported high limits such as 1000.</violation>
<violation number="2" location="src/features/hardware/cpu/shared/cpu.schemas.ts:46">
P2: The PC-listing CPU endpoint accepts fractional limits, but Prisma requires `take` to be an integer. Add `.int()` so malformed limits are rejected at the API boundary.</violation>
</file>
<file name="src/data/constants.ts">
<violation number="1" location="src/data/constants.ts:7">
P2: When the notification menu stays closed, `NotificationCenter` now refreshes the unread badge only every 10 minutes, so new notifications can remain hidden substantially longer than before. Keep a dedicated notification interval (or use `SHORT`) rather than reusing `EXTRA_LONG` for this user-facing count.</violation>
</file>
<file name="prisma/seeders/usersSeeder.ts">
<violation number="1" location="prisma/seeders/usersSeeder.ts:81">
P3: The newly added VerifiedDeveloper cleanup (handling the restricted `verifiedBy` foreign key) lives in `cleanupExistingUsers`, which is never reached: the only call site `seed.ts` invokes `usersSeeder(prisma)` with the default `shouldCleanup = false`. Wire a cleanup path (pass `true` where a reset seed is intended) or drop the unreachable branch so the FK-guard code is actually exercised.</violation>
<violation number="2" location="prisma/seeders/usersSeeder.ts:143">
P2: On every rerun of `usersSeeder`, existing developer/emulator verification rows have their audit verifier and notes replaced with the seed values. Preserve existing metadata in the upsert update so rerunning `pnpm db:seed` does not erase administrator provenance and notes.</violation>
<violation number="3" location="prisma/seeders/usersSeeder.ts:234">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
This added persistent cleanup and emulator-assignment behavior has no meaningful tests for deletion, idempotent upserts, verifier fallback, or the empty-emulator path. Add focused seeder tests before shipping this behavior.</violation>
</file>
<file name="src/lib/api.tsx">
<violation number="1" location="src/lib/api.tsx:31">
P2: After a performance scale changes, listing forms can show the old scale list for up to six hours because this procedure has no matching invalidation. Invalidate `listings.performanceScales` from performance-scale mutations or keep this query on the shorter default cache.</violation>
</file>
<file name="src/scripts/api/mobile-schema-registry.ts">
<violation number="1" location="src/scripts/api/mobile-schema-registry.ts:15">
P2: For mobile endpoints using schemas outside these six modules, `getMobileApiSchema` returns null, so the generated OpenAPI omits request parameters/bodies and response schema references. Register every schema module used by `src/server/api/routers/mobile` before generating the docs.</violation>
</file>
<file name="src/features/hardware/cpu/client/admin/CpuFormModal.tsx">
<violation number="1" location="src/features/hardware/cpu/client/admin/CpuFormModal.tsx:50">
P1: When the first CPU is added for an existing brand with no CPUs, this query omits that brand, so the form cannot create that first CPU. Load all brands here and reserve the category filter for the CPU table filter.</violation>
<violation number="2" location="src/features/hardware/cpu/client/admin/CpuFormModal.tsx:88">
P3: The Brand label is not associated with this autocomplete because the rendered input has no `id="brand"`. Pass `id="brand"` so screen readers announce the field and clicking the label focuses it.</violation>
</file>
<file name="src/hooks/useTranslation.tsx">
<violation number="1" location="src/hooks/useTranslation.tsx:29">
P2: When content changes and then returns to a previously translated value, the translation auto-applies without the user toggling it. `translatedContentKey` and `translationState` are never reset on content change (only `translationOption` is), and `showTranslated` is derived purely from content-key equality, so switching A→B→A renders the translated text on the final A automatically. Clear `translatedContentKey` whenever `content` changes so cached `translationState` can still be used on demand but never auto-shows.</violation>
</file>
<file name="src/app/admin/dashboard/components/ActivityCard/ReportActivityItem.tsx">
<violation number="1" location="src/app/admin/dashboard/components/ActivityCard/ReportActivityItem.tsx:14">
P2: The admin dashboard 'Report Activity' item now deep-links '→ Review' to the public listing page (`/listings/:id` or `/pc-listings/:id`) instead of the admin reports review surface. Moderators clicking 'Review' land on a public page with no moderation context. Either route to the admin report review flow for the target report, or if this redirect is intended, update the CTA label so it no longer implies a moderation action.</violation>
</file>
<file name="src/app/listings/components/ListingsFiltersSidebar.tsx">
<violation number="1" location="src/app/listings/components/ListingsFiltersSidebar.tsx:77">
P3: When a selected device/SoC's data hasn't loaded yet (getByIds still in flight, e.g. right after page load with filters restored from URL), `selectedOptions` omits it, so the analytics `names` array no longer matches `values`. The old code resolved names from the full preloaded lookup array instead. Track names from a source that includes all values, or accept the gap.</violation>
</file>
<file name="src/app/listings/shared/components/GameImage.tsx">
<violation number="1" location="src/app/listings/shared/components/GameImage.tsx:76">
P2: When `preload` is false, external images get no `loading` attribute and load eagerly instead of lazily. The previous `priority={false}` default lazy-loaded these via next/image. Force lazy loading when not preloaded.</violation>
</file>
<file name="src/components/ui/image-selectors/AdminImageSelectorSwitcher.tsx">
<violation number="1" location="src/components/ui/image-selectors/AdminImageSelectorSwitcher.tsx:42">
P3: This effect overwrites the user's in-progress `manualUrl` on every change of `selectedImageUrl`. If the prop updates for any reason while the user is typing a new manual URL (e.g. async re-sync or form update), their input is silently replaced with the applied value. Guard the reset so it only runs when the value actually differs, or track a dirty flag.</violation>
</file>
<file name="src/app/games/[id]/components/GameEditForm.tsx">
<violation number="1" location="src/app/games/[id]/components/GameEditForm.tsx:421">
P2: Custom agent: **Flag AI Slop and Fabricated Changes**
This adds a moderator-gated IGDB provider without tests for either side of the boundary. Add `ImageSelectorSwitcher` or `GameEditForm` tests that verify moderators see IGDB and non-moderators do not.</violation>
</file>
Note: This PR contains a large number of files. cubic selects up to 200 of the highest-priority eligible files for this review, so some files may not have been reviewed.
Re-trigger cubic
| const updateCpu = api.cpus.update.useMutation() | ||
| const deviceBrandsQuery = api.deviceBrands.get.useQuery({ | ||
| limit: PAGINATION.MAX_LIMIT, | ||
| category: 'cpu', |
There was a problem hiding this comment.
P1: When the first CPU is added for an existing brand with no CPUs, this query omits that brand, so the form cannot create that first CPU. Load all brands here and reserve the category filter for the CPU table filter.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/hardware/cpu/client/admin/CpuFormModal.tsx, line 50:
<comment>When the first CPU is added for an existing brand with no CPUs, this query omits that brand, so the form cannot create that first CPU. Load all brands here and reserve the category filter for the CPU table filter.</comment>
<file context>
@@ -0,0 +1,135 @@
+ const updateCpu = api.cpus.update.useMutation()
+ const deviceBrandsQuery = api.deviceBrands.get.useQuery({
+ limit: PAGINATION.MAX_LIMIT,
+ category: 'cpu',
+ })
+
</file context>
| { protocol: 'https', hostname: 'storage.ko-fi.com', pathname: '/**' }, | ||
| { protocol: 'https', hostname: 'ko-fi.com', pathname: '/**' }, | ||
| ], | ||
| remotePatterns: NEXT_IMAGE_REMOTE_PATTERNS, |
There was a problem hiding this comment.
P1: Removing dangerouslyAllowSVG: true while getSafePlaceholderImageUrl still returns https://placehold.co/... URLs breaks every placeholder/fallback image in production.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At next.config.ts, line 161:
<comment>Removing `dangerouslyAllowSVG: true` while `getSafePlaceholderImageUrl` still returns `https://placehold.co/...` URLs breaks every placeholder/fallback image in production.</comment>
<file context>
@@ -167,26 +149,16 @@ function createContentSecurityPolicy(): string {
- { protocol: 'https', hostname: 'storage.ko-fi.com', pathname: '/**' },
- { protocol: 'https', hostname: 'ko-fi.com', pathname: '/**' },
- ],
+ remotePatterns: NEXT_IMAGE_REMOTE_PATTERNS,
},
</file context>
| className="object-cover" | ||
| sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" | ||
| priority={props.priority ?? false} | ||
| loading={props.eagerLoad ? 'eager' : undefined} |
There was a problem hiding this comment.
P2: For below-the-fold cards (eagerLoad false) the loading value resolves to undefined, so external images render as plain <img> tags with no loading attribute and load eagerly. The previous priority={false} path made next/image lazy-load these images by default. Set loading to 'lazy' when not eager to preserve lazy loading on the games grid.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/games/components/GameCard.tsx, line 37:
<comment>For below-the-fold cards (`eagerLoad` false) the `loading` value resolves to `undefined`, so external images render as plain `<img>` tags with no `loading` attribute and load eagerly. The previous `priority={false}` path made next/image lazy-load these images by default. Set `loading` to `'lazy'` when not eager to preserve lazy loading on the games grid.</comment>
<file context>
@@ -28,13 +28,13 @@ function GameCard(props: Props) {
className="object-cover"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
- priority={props.priority ?? false}
+ loading={props.eagerLoad ? 'eager' : undefined}
unoptimized
/>
</file context>
| loading={props.eagerLoad ? 'eager' : undefined} | |
| loading={props.eagerLoad ? 'eager' : 'lazy'} |
| { | ||
| enabled: !!user?.id, | ||
| refetchInterval: 60000, // Refetch every minute to update time remaining | ||
| refetchOnWindowFocus: true, |
There was a problem hiding this comment.
P2: Removing the 60s refetchInterval makes the remaining-minutes countdown and the edit-time-expired transition stale while the page stays open. remainingMinutes and timeExpired are computed server-side per query, so with only refetchOnWindowFocus: true the button title keeps showing a stale 'N minutes remaining after approval' and the button stays enabled even after the 60-minute window passes, until the user refocuses or reloads. Restore a short polling interval (or decrement client-side) so the countdown/expiry stay accurate.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/pc-listings/[id]/components/EditPcListingButton.tsx, line 23:
<comment>Removing the 60s refetchInterval makes the remaining-minutes countdown and the edit-time-expired transition stale while the page stays open. remainingMinutes and timeExpired are computed server-side per query, so with only `refetchOnWindowFocus: true` the button title keeps showing a stale 'N minutes remaining after approval' and the button stays enabled even after the 60-minute window passes, until the user refocuses or reloads. Restore a short polling interval (or decrement client-side) so the countdown/expiry stay accurate.</comment>
<file context>
@@ -20,7 +20,7 @@ function EditPcListingButton(props: Props) {
{
enabled: !!user?.id,
- refetchInterval: 60000, // Refetch every minute to update time remaining
+ refetchOnWindowFocus: true,
},
)
</file context>
| refetchOnWindowFocus: true, | |
| refetchInterval: 60000, // Refetch every minute to update time remaining |
| // isn't implemented in the backend yet. This is marked as TODO. | ||
| await deletePerformanceScale.mutateAsync({ | ||
| id: props.scaleToDelete.id, | ||
| replacementId: selectedReplacementId, |
There was a problem hiding this comment.
P2: After a successful replacement or cancel, reopening this mounted modal reuses the previous selectedReplacementId. Reset the selection when scaleToDelete changes or the modal closes before sending it as the next replacement.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/admin/performance/components/ReplacementSelectionModal.tsx, line 43:
<comment>After a successful replacement or cancel, reopening this mounted modal reuses the previous `selectedReplacementId`. Reset the selection when `scaleToDelete` changes or the modal closes before sending it as the next replacement.</comment>
<file context>
@@ -33,15 +33,14 @@ function ReplacementSelectionModal(props: Props) {
- // isn't implemented in the backend yet. This is marked as TODO.
await deletePerformanceScale.mutateAsync({
id: props.scaleToDelete.id,
+ replacementId: selectedReplacementId,
} satisfies RouterInput['performanceScales']['delete'])
} catch (err) {
</file context>
| adminListings: `${PREFIX}admin_listings_column_visibility`, | ||
| adminPerformance: `${PREFIX}admin_performance_column_visibility`, | ||
| adminProcessedListings: `${PREFIX}admin_processed_listings_column_visibility`, | ||
| adminProcessedListings: `${PREFIX}admin_processed_reports_column_visibility`, |
There was a problem hiding this comment.
P3: After upgrading, existing handheld processed-report column preferences are discarded because useColumnVisibility reads only this new localStorage key. Keep the old key or migrate its value before switching names.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/data/storageKeys.ts, line 40:
<comment>After upgrading, existing handheld processed-report column preferences are discarded because `useColumnVisibility` reads only this new localStorage key. Keep the old key or migrate its value before switching names.</comment>
<file context>
@@ -39,7 +37,8 @@ const storageKeys = {
adminListings: `${PREFIX}admin_listings_column_visibility`,
adminPerformance: `${PREFIX}admin_performance_column_visibility`,
- adminProcessedListings: `${PREFIX}admin_processed_listings_column_visibility`,
+ adminProcessedListings: `${PREFIX}admin_processed_reports_column_visibility`,
+ adminPcProcessedListings: `${PREFIX}admin_pc_processed_listings_column_visibility`,
adminSoCs: `${PREFIX}admin_socs_column_visibility`,
</file context>
| <label htmlFor="brand" className="block mb-2 font-medium text-gray-700 dark:text-gray-300"> | ||
| Brand | ||
| </label> | ||
| <Autocomplete |
There was a problem hiding this comment.
P3: The Brand label is not associated with this autocomplete because the rendered input has no id="brand". Pass id="brand" so screen readers announce the field and clicking the label focuses it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/features/hardware/cpu/client/admin/CpuFormModal.tsx, line 88:
<comment>The Brand label is not associated with this autocomplete because the rendered input has no `id="brand"`. Pass `id="brand"` so screen readers announce the field and clicking the label focuses it.</comment>
<file context>
@@ -0,0 +1,135 @@
+ <label htmlFor="brand" className="block mb-2 font-medium text-gray-700 dark:text-gray-300">
+ Brand
+ </label>
+ <Autocomplete
+ value={brandId}
+ onChange={(value) => setBrandId(value ?? '')}
</file context>
|
|
||
| if (seedUsers.length > 0) { | ||
| const seedUserIds = seedUsers.map((user) => user.id) | ||
| await prisma.verifiedDeveloper.deleteMany({ |
There was a problem hiding this comment.
P3: The newly added VerifiedDeveloper cleanup (handling the restricted verifiedBy foreign key) lives in cleanupExistingUsers, which is never reached: the only call site seed.ts invokes usersSeeder(prisma) with the default shouldCleanup = false. Wire a cleanup path (pass true where a reset seed is intended) or drop the unreachable branch so the FK-guard code is actually exercised.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At prisma/seeders/usersSeeder.ts, line 81:
<comment>The newly added VerifiedDeveloper cleanup (handling the restricted `verifiedBy` foreign key) lives in `cleanupExistingUsers`, which is never reached: the only call site `seed.ts` invokes `usersSeeder(prisma)` with the default `shouldCleanup = false`. Wire a cleanup path (pass `true` where a reset seed is intended) or drop the unreachable branch so the FK-guard code is actually exercised.</comment>
<file context>
@@ -64,11 +63,27 @@ const users: UserData[] = [
+
+ if (seedUsers.length > 0) {
+ const seedUserIds = seedUsers.map((user) => user.id)
+ await prisma.verifiedDeveloper.deleteMany({
+ where: {
+ OR: [{ userId: { in: seedUserIds } }, { verifiedBy: { in: seedUserIds } }],
</file context>
| const handleDeviceChange = (values: string[], selectedOptions: Option[]) => { | ||
| props.onDeviceChange(values) | ||
| const names = getDeviceNames(props.devices, values) | ||
| const names = selectedOptions.map((option) => option.name) |
There was a problem hiding this comment.
P3: When a selected device/SoC's data hasn't loaded yet (getByIds still in flight, e.g. right after page load with filters restored from URL), selectedOptions omits it, so the analytics names array no longer matches values. The old code resolved names from the full preloaded lookup array instead. Track names from a source that includes all values, or accept the gap.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/app/listings/components/ListingsFiltersSidebar.tsx, line 77:
<comment>When a selected device/SoC's data hasn't loaded yet (getByIds still in flight, e.g. right after page load with filters restored from URL), `selectedOptions` omits it, so the analytics `names` array no longer matches `values`. The old code resolved names from the full preloaded lookup array instead. Track names from a source that includes all values, or accept the gap.</comment>
<file context>
@@ -79,18 +72,28 @@ function ListingsFiltersSidebar(props: FiltersProps) {
+ const handleDeviceChange = (values: string[], selectedOptions: Option[]) => {
props.onDeviceChange(values)
- const names = getDeviceNames(props.devices, values)
+ const names = selectedOptions.map((option) => option.name)
filterAnalytics.devices(values, names)
}
</file context>
|
|
||
| useEffect(() => { | ||
| const selectedImageUrl = props.selectedImageUrl ?? '' | ||
| setManualUrl(selectedImageUrl) |
There was a problem hiding this comment.
P3: This effect overwrites the user's in-progress manualUrl on every change of selectedImageUrl. If the prop updates for any reason while the user is typing a new manual URL (e.g. async re-sync or form update), their input is silently replaced with the applied value. Guard the reset so it only runs when the value actually differs, or track a dirty flag.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/ui/image-selectors/AdminImageSelectorSwitcher.tsx, line 42:
<comment>This effect overwrites the user's in-progress `manualUrl` on every change of `selectedImageUrl`. If the prop updates for any reason while the user is typing a new manual URL (e.g. async re-sync or form update), their input is silently replaced with the applied value. Guard the reset so it only runs when the value actually differs, or track a dirty flag.</comment>
<file context>
@@ -42,10 +37,16 @@ export function AdminImageSelectorSwitcher(props: Props) {
+ useEffect(() => {
+ const selectedImageUrl = props.selectedImageUrl ?? ''
+ setManualUrl(selectedImageUrl)
+ setIsValidUrl(selectedImageUrl ? !getGameImageUrlValidationError(selectedImageUrl) : false)
+ }, [props.selectedImageUrl])
</file context>
Description
Releases v0.15.0 from
stagingto production.Full user, moderator, and developer notes are included in
RELEASE_NOTES.md.Type of change
How Has This Been Tested?
Screenshots (if applicable)
N/A
Checklist
Notes for reviewers
The experimental
/v2/listingsroute has been removed.The permissive legacy
x-api-keyfallback remains temporary compatibility behavior for shipped mobile clients. Explicit invalidAuthorization: ApiKey ...credentials are rejected.Summary by cubic
Ships v0.15.0: aligns PC report moderation with handheld, hardens public/mobile tRPC caching and CORS, and centralizes remote image handling while removing unused analytics and realtime code. Notable behavior changes: handheld report search now combines text and role visibility correctly; game images must be HTTPS from approved hosts; the image proxy route is removed.
x-api-keyfor public endpoints; Bearer remains for protected endpoints.config/image-hosts.ts; introduces sharedImageRenderer.@vercel/analyticsand@vercel/speed-insightsintegrations and related envs/CSP entries./api/notifications/stream,/api/proxy-image, and the experimental/v2/listings.db:backup.Bolded sections are not requested per constraints.
Written for commit fdf80a7. Summary will update on new commits.