-
Notifications
You must be signed in to change notification settings - Fork 106
Add game buying guide cookbook example #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add game buying guide cookbook example #23
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughAdds a Next.js app that performs parallel, SSE-driven analysis of game store pages via the Mino automation API. New server routes: POST /api/discover-platforms (generates platform search URLs), POST /api/analyze-platform (streams per-platform analysis via Mino SSE), and POST /api/steamdb-price-history (streams SteamDB price-history via Mino SSE). Introduces a client hook (useGameSearch) to orchestrate concurrent SSE analyses, many UI components and theming (CSS variables, Tailwind), type definitions and utilities (cn), project config (tsconfig, next.config, package.json), and Mino integration docs and env example. Sequence Diagram(s)sequenceDiagram
actor User
participant Browser as Client Browser
participant DiscoverAPI as /api/discover-platforms
participant Coordinator as useGameSearch (Client)
participant AnalyzeAPI as /api/analyze-platform
participant SteamDBAPI as /api/steamdb-price-history
participant Mino as mino.ai /automation/run-sse
User->>Browser: Submit game title
Browser->>DiscoverAPI: POST { gameTitle }
DiscoverAPI-->>Browser: { platforms: [ { name, url }, ... ] }
Browser->>Coordinator: initialize agents (platforms + steamDB)
par Parallel analyses
Coordinator->>AnalyzeAPI: POST { platformName, url, gameTitle }
AnalyzeAPI->>Mino: POST automation/run-sse (SSE)
Mino-->>AnalyzeAPI: STREAMING_URL / STATUS / COMPLETE (SSE)
AnalyzeAPI-->>Coordinator: forward SSE events (STREAMING_URL, STATUS, COMPLETE, ERROR)
Coordinator->>SteamDBAPI: POST { gameTitle }
SteamDBAPI->>Mino: POST automation/run-sse (SSE)
Mino-->>SteamDBAPI: STREAMING_URL / STATUS / COMPLETE (SSE)
SteamDBAPI-->>Coordinator: forward SSE events (STREAMING_URL, STATUS, COMPLETE, ERROR)
end
Coordinator->>Browser: aggregate agent states & results
Browser->>User: display live previews, statuses, and final recommendations
🚥 Pre-merge checks | ✅ 1 | ❌ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🤖 Fix all issues with AI agents
In `@game-buying-guide/components/live-browser-preview.tsx`:
- Around line 45-55: The icon-only Buttons (the toggle using
isExpanded/setIsExpanded with Minimize2/Maximize2 and the close Button using
onClose with X) lack accessible labels; add descriptive aria-label attributes to
both Buttons (e.g., aria-label="Expand preview" / "Minimize preview" for the
toggle based on isExpanded and aria-label="Close preview" for the close button)
and consider adding aria-pressed or aria-expanded on the toggle Button to
reflect state via setIsExpanded/isExpanded so screen readers get the current
state.
In `@game-buying-guide/components/ui/carousel.tsx`:
- Around line 96-105: The effect in React.useEffect subscribes api.on('reInit',
onSelect) and api.on('select', onSelect) but only removes the 'select' listener
on cleanup; update the cleanup to also call api?.off('reInit', onSelect) so both
subscriptions are removed, keeping the current checks for api and preserving
onSelect as the handler to avoid memory leaks and stale callbacks.
In `@game-buying-guide/components/ui/collapsible.tsx`:
- Around line 1-9: The component uses React.ComponentProps for typing but React
is not imported, causing a compile error; fix by importing React (e.g., import
React from 'react') or import the type directly (e.g., import { ComponentProps }
from 'react') and update the type usage accordingly so the Collapsible function
signature (using React.ComponentProps or ComponentProps<typeof
CollapsiblePrimitive.Root>) resolves; ensure CollapsiblePrimitive.Root remains
unchanged.
In `@game-buying-guide/components/ui/progress.tsx`:
- Around line 8-28: The Progress component destructures value but never forwards
it to ProgressPrimitive.Root, breaking Radix's aria-valuenow; update the
Progress function to pass the value prop into ProgressPrimitive.Root (e.g.,
include value={value} on ProgressPrimitive.Root) so screen readers receive the
current progress while leaving the translateX Indicator calculation as-is.
In `@game-buying-guide/next.config.mjs`:
- Around line 3-5: The config currently suppresses TypeScript build errors by
setting the typescript.ignoreBuildErrors flag to true; remove this flag or set
typescript.ignoreBuildErrors to false and instead fix the underlying TypeScript
issues causing build failures (run tsc or next build locally, address reported
errors in the codebase), so that types are enforced during CI/build rather than
ignored.
In `@game-buying-guide/package.json`:
- Line 62: Remove the unused dependency "tailwindcss-animate" from package.json
(it is not referenced anywhere) and ensure the project relies on
"tw-animate-css" instead; after removing the entry for tailwindcss-animate,
update the lockfile by running the package manager (npm/yarn/pnpm install) and
verify no imports or references to tailwindcss-animate remain in the repo
(search for "tailwindcss-animate"); commit the package.json and lockfile
changes.
In `@game-buying-guide/styles/globals.css`:
- Around line 1-125: There are two conflicting theme files causing Next.js to
auto-import the wrong one; remove or consolidate the duplicate theme so only one
authoritative CSS defines the theme variables. Delete the weaker file or merge
its content into the primary theme by keeping the working `@theme` inline block,
the :root and .dark variable sets (including contrast-correct values), and add
any missing tokens such as --success and --warning; ensure the final file
retains the `@custom-variant` dark (&:is(.dark *)); rule and the base layer rules
(* { `@apply` border-border outline-ring/50; } and body { `@apply` bg-background
text-foreground; }) so Next.js imports a single, complete theme.
🟡 Minor comments (25)
game-buying-guide/components/ui/empty.tsx-71-82 (1)
71-82: Type/element mismatch: typed as<p>but renders<div>.The component is typed with
React.ComponentProps<'p'>but renders a<div>element. This inconsistency can cause confusion and potential runtime issues if paragraph-specific props are passed.🐛 Proposed fix
Either change the type to match the element:
-function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { +function EmptyDescription({ className, ...props }: React.ComponentProps<'div'>) { return ( <divOr change the element to match the type (if paragraph semantics are intended):
function EmptyDescription({ className, ...props }: React.ComponentProps<'p'>) { return ( - <div + <p data-slot="empty-description" className={cn( 'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', className, )} {...props} /> ) }game-buying-guide/components/ui/input-otp.tsx-17-24 (1)
17-24: Update Tailwind selector fromhas-disabledtohas-[:disabled]to match valid:has()syntax.The
has-disabled:opacity-50class is not valid Tailwind v4 syntax. Tailwind's:has()support requires the formhas-[SELECTOR]with an actual CSS selector inside the brackets. The codebase consistently uses this pattern elsewhere (e.g.,has-[>svg],has-[>[data-align=...]]). Update tohas-[:disabled]:opacity-50to target disabled elements within the OTP input container.🔧 Suggested fix
- 'flex items-center gap-2 has-disabled:opacity-50', + 'flex items-center gap-2 has-[:disabled]:opacity-50',game-buying-guide/components/ui/kbd.tsx-18-26 (1)
18-26: Type mismatch:KbdGroupacceptsdivprops but renders a<kbd>element.The component is typed with
React.ComponentProps<'div'>but renders a<kbd>element. This inconsistency could cause confusion and potential type errors.🔧 Proposed fix
-function KbdGroup({ className, ...props }: React.ComponentProps<'div'>) { +function KbdGroup({ className, ...props }: React.ComponentProps<'kbd'>) { return ( <kbd data-slot="kbd-group" className={cn('inline-flex items-center gap-1', className)} {...props} /> ) }game-buying-guide/package.json-12-12 (1)
12-12: Pin@emotion/is-prop-validto a specific version instead of"latest".Using
"latest"breaks reproducibility across environments and exposes the build to unexpected breaking changes in future releases. Pin to the current version"1.4.0"(e.g.,"^1.4.0"or"1.4.0"). Note that all other dependencies in this file use pinned or caret-constrained versions.game-buying-guide/components/results-summary.tsx-26-33 (1)
26-33: Price parsing may fail for non-USD currency formats.The regex
/[^0-9.]/gstrips everything except digits and dots, which works for$19.99but produces incorrect results for formats like€19,99(European) which would parse as1999. If multi-region pricing is expected, consider a more robust parsing approach.game-buying-guide/components/ui/use-toast.ts-171-189 (1)
171-189: Removestatefrom the useEffect dependency array.The effect registers and unregisters the
setStatelistener, which doesn't depend on the currentstatevalue. Includingstatein the dependency array causes the effect to re-run on every state change, unnecessarily removing and re-adding the listener.🐛 Proposed fix
function useToast() { const [state, setState] = React.useState<State>(memoryState) React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } - }, [state]) + }, []) return { ...state, toast, dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }), } }game-buying-guide/components/ui/button-group.tsx-1-6 (1)
1-6: Add missing React import.The file uses
React.ComponentPropsbut doesn't import React. While this may work in some bundler configurations, explicitly importing React ensures compatibility.🐛 Proposed fix
+import * as React from 'react' import { Slot } from '@radix-ui/react-slot' import { cva, type VariantProps } from 'class-variance-authority' import { cn } from '@/lib/utils' import { Separator } from '@/components/ui/separator'game-buying-guide/components/ui/button-group.tsx-60-76 (1)
60-76: Update important modifier syntax for Tailwind v4.In Tailwind v4, the
!modifier should be placed at the end of the utility class (m-0!), not at the beginning (!m-0).🔧 Proposed fix
<Separator data-slot="button-group-separator" orientation={orientation} className={cn( - 'bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto', + 'bg-input relative m-0! self-stretch data-[orientation=vertical]:h-auto', className, )} {...props} />game-buying-guide/components/ui/breadcrumb.tsx-52-59 (1)
52-59: Avoidrole="link"on the current page crumb.It’s not interactive, so announcing it as a link is misleading.
aria-current="page"is sufficient.✅ Remove misleading link semantics
<span data-slot="breadcrumb-page" - role="link" - aria-disabled="true" aria-current="page" className={cn('text-foreground font-normal', className)} {...props} />game-buying-guide/components/ui/input-group.tsx-59-75 (1)
59-75: Addon click should also focus textareas.The current query only targets
input, soInputGroupTextareawon't receive focus when clicking the addon. BothInputGroupInputandInputGroupTextareause the shareddata-slot="input-group-control"attribute—use that selector instead.🔧 Broaden focus target
- e.currentTarget.parentElement?.querySelector('input')?.focus() + e.currentTarget.parentElement + ?.querySelector<HTMLElement>('[data-slot="input-group-control"]') + ?.focus()game-buying-guide/components/live-browser-preview.tsx-16-75 (1)
16-75: Handle iframe load failures to avoid a stuck spinner.If the iframe fails to load,
isLoadingnever clears. Add an error state andonErrorhandler to reset the loading state and display an error message to the user.🛠️ Suggested error handling
- const [isLoading, setIsLoading] = useState(true) + const [isLoading, setIsLoading] = useState(true) + const [hasError, setHasError] = useState(false) @@ useEffect(() => { setIsLoading(true) + setHasError(false) }, [streamingUrl]) @@ - {isLoading && ( + {isLoading && !hasError && ( <div className="absolute inset-0 flex items-center justify-center bg-muted/50"> <div className="flex flex-col items-center gap-2"> <div className="w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" /> <span className="text-sm text-muted-foreground">Connecting to browser...</span> </div> </div> )} + {hasError && ( + <div className="absolute inset-0 flex items-center justify-center bg-muted/50"> + <span className="text-sm text-muted-foreground">Unable to load preview.</span> + </div> + )} <iframe src={streamingUrl} className="w-full h-full border-0" - onLoad={() => setIsLoading(false)} + onLoad={() => { + setIsLoading(false) + setHasError(false) + }} + onError={() => { + setIsLoading(false) + setHasError(true) + }} title={`Live browser preview for ${platformName}`} sandbox="allow-scripts allow-same-origin" />game-buying-guide/components/ui/pagination.tsx-102-115 (1)
102-115: Expose the ellipsis label to screen readers.
aria-hiddenon the wrapper hides thesr-onlylabel, so assistive tech won’t announce “More pages.” Prefer hiding only the icon.♿ Proposed fix
- <span - aria-hidden + <span data-slot="pagination-ellipsis" className={cn('flex size-9 items-center justify-center', className)} {...props} > - <MoreHorizontalIcon className="size-4" /> + <MoreHorizontalIcon className="size-4" aria-hidden="true" /> <span className="sr-only">More pages</span> </span>game-buying-guide/components/ui/command.tsx-32-61 (1)
32-61: Move theDialogHeaderwith sr-only content insideDialogContent.The
DialogHeadercurrently renders outside the dialog portal. WhenDialogContentis portaled intodocument.body, the sr-only header remains in the original DOM tree and persists even when the dialog is closed. Per Radix UI's documented structure,DialogTitleandDialogDescriptionshould be nested insideDialogContentto ensure they're properly mounted/unmounted with the dialog and maintain correct ARIA wiring (aria-labelledby/aria-describedby).Fix
<Dialog {...props}> - <DialogHeader className="sr-only"> - <DialogTitle>{title}</DialogTitle> - <DialogDescription>{description}</DialogDescription> - </DialogHeader> <DialogContent className={cn('overflow-hidden p-0', className)} showCloseButton={showCloseButton} > + <DialogHeader className="sr-only"> + <DialogTitle>{title}</DialogTitle> + <DialogDescription>{description}</DialogDescription> + </DialogHeader> <Command className="[&_[cmdk-group-heading]]:text-muted-foreground **:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"> {children} </Command> </DialogContent> </Dialog>game-buying-guide/docs/mino-api-integration.md-9-54 (1)
9-54: Add languages to fenced code blocks (MD040).
This satisfies markdownlint and improves readability.📝 Fence language fixes
-``` +```text ┌─────────────────────────────────────────────────────────────────────────────┐ │ USER INTERFACE │ @@ └─────────────────────────────────────────────────────────────────────────────┘ -``` +``` @@ -``` +```text You are analyzing a game store page to help a user decide whether to buy "Elden Ring" now or wait. @@ Be accurate with prices and factual with observations. If you cannot find certain information, use null for that field. -``` +``` @@ -``` +```text event: message data: {"streamingUrl": "https://live.mino.ai/session/abc123"} @@ event: message data: {"type": "COMPLETE", "result": {...}} -``` +```Also applies to: 301-346, 354-375
game-buying-guide/docs/mino-api-integration.md-58-63 (1)
58-63: Fix table separator spacing to satisfy MD060.
The separator rows are missing spacing around pipes.🧾 Table formatting
-|-----|---------|------------------|-----------| +| --- | --- | --- | --- | @@ -|----------|-------------|----------| +| --- | --- | --- |Also applies to: 429-433
game-buying-guide/app/api/analyze-platform/route.ts-100-115 (1)
100-115: Clear the timeout before early returns.
Otherwise the timeout fires later and logs an abort for a request that already ended.🧹 Cleanup on early exit
if (!response.ok) { const errorText = await response.text() console.error('Mino API error:', errorText) controller.enqueue( encoder.encode( `data: ${JSON.stringify({ type: 'ERROR', error: 'Failed to start browser agent' })}\n\n` ) ) controller.close() + clearTimeout(timeoutId) return } if (!response.body) { controller.enqueue(encoder.encode(`data: ${JSON.stringify({ type: 'ERROR', error: 'No response body' })}\n\n`)) controller.close() + clearTimeout(timeoutId) return }game-buying-guide/app/api/analyze-platform/route.ts-48-55 (1)
48-55: Fix the malformeddiscount_percentageexample string.
The mismatched quotes can confuse the agent about expected JSON.✏️ Prompt fix
- "discount_percentage": "XX%" if on sale, null otherwise", + "discount_percentage": "XX% if on sale, null otherwise",game-buying-guide/components/steamdb-price-card.tsx-53-67 (1)
53-67: Make the preview toggle keyboard-accessible.
A clickable<div>without role/tabIndex prevents keyboard users from expanding/collapsing the preview.♿ Accessible toggle
<div className="relative rounded-lg overflow-hidden border border-primary/30 cursor-pointer hover:border-primary/50 transition-colors" onClick={() => setExpanded(!expanded)} + role="button" + tabIndex={0} + aria-expanded={expanded} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + setExpanded(!expanded) + } + }} >game-buying-guide/hooks/use-game-search.ts-91-94 (1)
91-94: Greedy regex may match unintended JSON.The pattern
/\{[\s\S]*\}/is greedy and will match from the first{to the last}in the string. If the result contains multiple JSON objects or nested braces in strings, this could produce invalid JSON.Consider a more robust approach or validate the parsed result:
- const jsonMatch = result.match(/\{[\s\S]*\}/) - if (jsonMatch) { - result = JSON.parse(jsonMatch[0]) - } + // Try direct parse first, fall back to extraction + try { + result = JSON.parse(result) + } catch { + const jsonMatch = result.match(/\{[\s\S]*\}/) + if (jsonMatch) { + result = JSON.parse(jsonMatch[0]) + } + }Also applies to: 133-136, 243-246
game-buying-guide/components/ui/chart.tsx-235-239 (1)
235-239: Zero values are hidden in tooltip.The condition
item.value &&will hide values that are0, which may be valid data points worth displaying.- {item.value && ( + {item.value !== undefined && item.value !== null && ( <span className="text-foreground font-mono font-medium tabular-nums"> {item.value.toLocaleString()} </span> )}game-buying-guide/components/ui/sidebar.tsx-8-8 (1)
8-8: Remove unuseduseIsMobileduplicate ingame-buying-guide/components/ui/use-mobile.tsx.The file
game-buying-guide/components/ui/use-mobile.tsxcontains an identical copy of theuseIsMobilehook that already exists ingame-buying-guide/hooks/use-mobile.ts. The duplicate is not imported anywhere in the codebase and should be removed. The import in sidebar.tsx correctly references the@/hooks/use-mobilepath.game-buying-guide/components/agent-grid.tsx-41-47 (1)
41-47: Use a more stable unique key thanplatformName.
If two agents share the same platform name (regional variants, duplicates), React key collisions can cause UI mismatches.✅ Suggested key stabilization
- {agents.map((agent) => ( - <AgentCard - key={agent.platformName} + {agents.map((agent) => ( + <AgentCard + key={`${agent.platformName}-${agent.url}`} agent={agent} onExpandPreview={(a) => setExpandedAgent(a)} /> ))}game-buying-guide/components/search-form.tsx-66-75 (1)
66-75: Disable quick-action chips while loading (and fix a label typo).
The input is disabled during loading, but chips still mutate the query, which can feel inconsistent. Also, “Baldurs Gate 3” should be “Baldur’s Gate 3”.✅ Suggested adjustment
- <div className="mt-4 flex flex-wrap justify-center gap-2"> - {['Elden Ring', 'Cyberpunk 2077', 'Baldurs Gate 3', 'Red Dead Redemption 2'].map((game) => ( + <div className="mt-4 flex flex-wrap justify-center gap-2"> + {['Elden Ring', 'Cyberpunk 2077', "Baldur's Gate 3", 'Red Dead Redemption 2'].map((game) => ( <button key={game} type="button" onClick={() => setQuery(game)} - className="px-3 py-1.5 text-sm rounded-full bg-secondary text-secondary-foreground hover:bg-secondary/80 transition-colors" + disabled={isLoading} + aria-disabled={isLoading} + className="px-3 py-1.5 text-sm rounded-full bg-secondary text-secondary-foreground hover:bg-secondary/80 transition-colors disabled:pointer-events-none disabled:opacity-50" > {game} </button> ))} </div>game-buying-guide/hooks/use-toast.ts-171-189 (1)
171-189: Removestatefrom the useEffect dependency array.The
useEffectat Line 182 includesstatein its dependency array, but the effect only needs to subscribe once on mount. Includingstatecauses the subscription to be removed and re-added on every state change, which is unnecessary overhead.🔧 Proposed fix
React.useEffect(() => { listeners.push(setState) return () => { const index = listeners.indexOf(setState) if (index > -1) { listeners.splice(index, 1) } } - }, [state]) + }, [])game-buying-guide/components/ui/form.tsx-45-66 (1)
45-66: ThefieldContextcheck is ineffective due to ordering.The null check on Line 52 occurs after
fieldContext.nameis already accessed on Line 49. IffieldContextwere actually undefined, Line 49 would throw first. Additionally,useContextreturns the default value ({} as FormFieldContextValue), so!fieldContextwill never be true.To properly detect usage outside
<FormField>, check for missingname:🔧 Proposed fix
const useFormField = () => { const fieldContext = React.useContext(FormFieldContext) const itemContext = React.useContext(FormItemContext) + + if (!fieldContext.name) { + throw new Error('useFormField should be used within <FormField>') + } + const { getFieldState } = useFormContext() const formState = useFormState({ name: fieldContext.name }) const fieldState = getFieldState(fieldContext.name, formState) - if (!fieldContext) { - throw new Error('useFormField should be used within <FormField>') - } - const { id } = itemContext return {
🧹 Nitpick comments (25)
game-buying-guide/components/ui/use-mobile.tsx (1)
1-19: Deduplicate the hook by re-exporting the shared implementation.
This file duplicatesgame-buying-guide/hooks/use-mobile.ts. Prefer a single source of truth to avoid drift.♻️ Suggested refactor (re-export)
-import * as React from 'react' - -const MOBILE_BREAKPOINT = 768 - -export function useIsMobile() { - const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined) - - React.useEffect(() => { - const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) - const onChange = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - } - mql.addEventListener('change', onChange) - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) - return () => mql.removeEventListener('change', onChange) - }, []) - - return !!isMobile -} +export { useIsMobile } from '../../hooks/use-mobile'game-buying-guide/components/ui/scroll-area.tsx (1)
8-55: Forward refs to preserve Radix interoperability and ref typing.These wrappers currently prevent ref forwarding because they're plain functions using
ComponentProps. Radix UI's official composition guidance recommends usingforwardRef+ComponentPropsWithoutRef+ElementRefand settingdisplayNameon wrapper components to maintain composability, enable DevTools debugging, and allow consumers to access the underlying DOM nodes. This is standard practice for design system wrappers.♻️ Proposed refactor
-function ScrollArea({ - className, - children, - ...props -}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { - return ( - <ScrollAreaPrimitive.Root - data-slot="scroll-area" - className={cn('relative', className)} - {...props} - > - <ScrollAreaPrimitive.Viewport - data-slot="scroll-area-viewport" - className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" - > - {children} - </ScrollAreaPrimitive.Viewport> - <ScrollBar /> - <ScrollAreaPrimitive.Corner /> - </ScrollAreaPrimitive.Root> - ) -} +const ScrollArea = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root> +>(({ className, children, ...props }, ref) => ( + <ScrollAreaPrimitive.Root + ref={ref} + data-slot="scroll-area" + className={cn('relative', className)} + {...props} + > + <ScrollAreaPrimitive.Viewport + data-slot="scroll-area-viewport" + className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" + > + {children} + </ScrollAreaPrimitive.Viewport> + <ScrollBar /> + <ScrollAreaPrimitive.Corner /> + </ScrollAreaPrimitive.Root> +)) +ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName-function ScrollBar({ - className, - orientation = 'vertical', - ...props -}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { - return ( - <ScrollAreaPrimitive.ScrollAreaScrollbar - data-slot="scroll-area-scrollbar" - orientation={orientation} - className={cn( - 'flex touch-none p-px transition-colors select-none', - orientation === 'vertical' && - 'h-full w-2.5 border-l border-l-transparent', - orientation === 'horizontal' && - 'h-2.5 flex-col border-t border-t-transparent', - className, - )} - {...props} - > - <ScrollAreaPrimitive.ScrollAreaThumb - data-slot="scroll-area-thumb" - className="bg-border relative flex-1 rounded-full" - /> - </ScrollAreaPrimitive.ScrollAreaScrollbar> - ) -} +const ScrollBar = React.forwardRef< + React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, + React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar> +>(({ className, orientation = 'vertical', ...props }, ref) => ( + <ScrollAreaPrimitive.ScrollAreaScrollbar + ref={ref} + data-slot="scroll-area-scrollbar" + orientation={orientation} + className={cn( + 'flex touch-none p-px transition-colors select-none', + orientation === 'vertical' && + 'h-full w-2.5 border-l border-l-transparent', + orientation === 'horizontal' && + 'h-2.5 flex-col border-t border-t-transparent', + className, + )} + {...props} + > + <ScrollAreaPrimitive.ScrollAreaThumb + data-slot="scroll-area-thumb" + className="bg-border relative flex-1 rounded-full" + /> + </ScrollAreaPrimitive.ScrollAreaScrollbar> +)) +ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayNamegame-buying-guide/components/ui/avatar.tsx (1)
8-49: Consider forwarding refs on the Radix Avatar wrappers.These wrappers currently drop ref support, which is commonly expected with Radix primitives and helps with focus management/integration. The codebase pattern established in toast.tsx shows all Radix primitive wrappers use
forwardRef+displayNamefor consistency. A small refactor aligns Avatar with this established pattern.♻️ Suggested refactor (forwardRef + displayName)
-function Avatar({ - className, - ...props -}: React.ComponentProps<typeof AvatarPrimitive.Root>) { - return ( - <AvatarPrimitive.Root - data-slot="avatar" - className={cn( - 'relative flex size-8 shrink-0 overflow-hidden rounded-full', - className, - )} - {...props} - /> - ) -} +const Avatar = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Root + ref={ref} + data-slot="avatar" + className={cn( + 'relative flex size-8 shrink-0 overflow-hidden rounded-full', + className, + )} + {...props} + /> +)) +Avatar.displayName = AvatarPrimitive.Root.displayName -function AvatarImage({ - className, - ...props -}: React.ComponentProps<typeof AvatarPrimitive.Image>) { - return ( - <AvatarPrimitive.Image - data-slot="avatar-image" - className={cn('aspect-square size-full', className)} - {...props} - /> - ) -} +const AvatarImage = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Image>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Image + ref={ref} + data-slot="avatar-image" + className={cn('aspect-square size-full', className)} + {...props} + /> +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName -function AvatarFallback({ - className, - ...props -}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { - return ( - <AvatarPrimitive.Fallback - data-slot="avatar-fallback" - className={cn( - 'bg-muted flex size-full items-center justify-center rounded-full', - className, - )} - {...props} - /> - ) -} +const AvatarFallback = React.forwardRef< + React.ElementRef<typeof AvatarPrimitive.Fallback>, + React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback> +>(({ className, ...props }, ref) => ( + <AvatarPrimitive.Fallback + ref={ref} + data-slot="avatar-fallback" + className={cn( + 'bg-muted flex size-full items-center justify-center rounded-full', + className, + )} + {...props} + /> +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayNamegame-buying-guide/next.config.mjs (1)
6-8: Consider documenting why image optimization is disabled.
images.unoptimized: truedisables Next.js image optimization. If this is intentional (e.g., for static export or simplicity in this example), a brief comment explaining the rationale would help readers understand this choice.game-buying-guide/package.json (2)
2-2: Project name mismatch.The package name
"my-v0-project"doesn't match the directory namegame-buying-guide. Consider updating for consistency.Suggested fix
- "name": "my-v0-project", + "name": "game-buying-guide",
43-43: Removeautoprefixerdependency.Tailwind CSS v4 (used in this project) includes built-in Lightning CSS integration that handles vendor prefixes automatically. The
autoprefixerpackage is unnecessary and can be removed. Note that it's unusually placed independenciesrather thandevDependencies.game-buying-guide/components/ui/slider.tsx (1)
16-24: Default value creates a range slider unexpectedly.When neither
valuenordefaultValueis provided,_valuesdefaults to[min, max], which renders two thumbs (a range slider). Most use cases expect a single-thumb slider by default. Consider defaulting to[min]instead if a single-value slider is the common case.💡 Suggested change for single-thumb default
const _values = React.useMemo( () => Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue - : [min, max], + : [min], [value, defaultValue, min, max], )game-buying-guide/components/ui/button.tsx (1)
1-6: Consider adding'use client'directive for consistency.Other UI components in this PR include the
'use client'directive. While Button doesn't strictly require it (no hooks or state), adding it ensures consistent behavior when the button handles events and maintains uniformity across the component library.💡 Add client directive
+'use client' + import * as React from 'react' import { Slot } from '@radix-ui/react-slot'game-buying-guide/components/ui/hover-card.tsx (1)
22-42: Remove ineffectivedata-slotfrom Portal.
HoverCardPrimitive.Portaldoesn't render a DOM element—it only controls where children are mounted. Thedata-slot="hover-card-portal"attribute on Line 29 will be silently ignored.🔧 Proposed fix
function HoverCardContent({ className, align = 'center', sideOffset = 4, ...props }: React.ComponentProps<typeof HoverCardPrimitive.Content>) { return ( - <HoverCardPrimitive.Portal data-slot="hover-card-portal"> + <HoverCardPrimitive.Portal> <HoverCardPrimitive.Contentgame-buying-guide/components/ui/toggle-group.tsx (1)
43-71: Consider adjusting prop priority to allow item-level overrides.The current logic
context.variant || variantmeans item-level props are ignored when context has default values. If an item needs a different variant than the group, it can't override it. Consider usingvariant ?? context.variantto prefer explicit item props.🔧 Proposed change to allow item overrides
return ( <ToggleGroupPrimitive.Item data-slot="toggle-group-item" - data-variant={context.variant || variant} - data-size={context.size || size} + data-variant={variant ?? context.variant} + data-size={size ?? context.size} className={cn( toggleVariants({ - variant: context.variant || variant, - size: context.size || size, + variant: variant ?? context.variant, + size: size ?? context.size, }),game-buying-guide/components/ui/tooltip.tsx (2)
8-19: Remove ineffectivedata-slotfrom TooltipProvider.
TooltipPrimitive.Providerdoesn't render a DOM element—it only provides context. Thedata-slot="tooltip-provider"attribute on Line 14 will be ignored.🔧 Proposed fix
function TooltipProvider({ delayDuration = 0, ...props }: React.ComponentProps<typeof TooltipPrimitive.Provider>) { return ( <TooltipPrimitive.Provider - data-slot="tooltip-provider" delayDuration={delayDuration} {...props} /> ) }
21-29: EachTooltipcreates a newTooltipProvider.This is convenient for isolated usage but inefficient if many tooltips are rendered—each gets its own provider. Consider documenting that apps with many tooltips should wrap them in a single
<TooltipProvider>and use<TooltipPrimitive.Root>directly, or export both patterns.game-buying-guide/components/ui/tabs.tsx (1)
8-64: Forward refs in the wrapper components to preserve ref access.Without
forwardRef, consumers can’t attach refs to the underlying Radix elements (e.g., for focus management or integrations). Consider forwarding refs on all four wrappers.♻️ Suggested ref-forwarding update
-function Tabs({ - className, - ...props -}: React.ComponentProps<typeof TabsPrimitive.Root>) { - return ( - <TabsPrimitive.Root - data-slot="tabs" - className={cn('flex flex-col gap-2', className)} - {...props} - /> - ) -} +const Tabs = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Root + ref={ref} + data-slot="tabs" + className={cn('flex flex-col gap-2', className)} + {...props} + /> +)) +Tabs.displayName = TabsPrimitive.Root.displayName @@ -function TabsList({ - className, - ...props -}: React.ComponentProps<typeof TabsPrimitive.List>) { - return ( - <TabsPrimitive.List - data-slot="tabs-list" - className={cn( - 'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]', - className, - )} - {...props} - /> - ) -} +const TabsList = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.List>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.List> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.List + ref={ref} + data-slot="tabs-list" + className={cn( + 'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]', + className, + )} + {...props} + /> +)) +TabsList.displayName = TabsPrimitive.List.displayName @@ -function TabsTrigger({ - className, - ...props -}: React.ComponentProps<typeof TabsPrimitive.Trigger>) { - return ( - <TabsPrimitive.Trigger - data-slot="tabs-trigger" - className={cn( - "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - className, - )} - {...props} - /> - ) -} +const TabsTrigger = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Trigger + ref={ref} + data-slot="tabs-trigger" + className={cn( + "data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className, + )} + {...props} + /> +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName @@ -function TabsContent({ - className, - ...props -}: React.ComponentProps<typeof TabsPrimitive.Content>) { - return ( - <TabsPrimitive.Content - data-slot="tabs-content" - className={cn('flex-1 outline-none', className)} - {...props} - /> - ) -} +const TabsContent = React.forwardRef< + React.ElementRef<typeof TabsPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content> +>(({ className, ...props }, ref) => ( + <TabsPrimitive.Content + ref={ref} + data-slot="tabs-content" + className={cn('flex-1 outline-none', className)} + {...props} + /> +)) +TabsContent.displayName = TabsPrimitive.Content.displayNamegame-buying-guide/components/ui/popover.tsx (1)
14-46: Forward refs through Popover wrappers.Ref pass-through is commonly expected for these primitives;
forwardRefkeeps the wrapper API transparent.♻️ Suggested ref-forwarding update
-function PopoverTrigger({ - ...props -}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) { - return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} /> -} +const PopoverTrigger = React.forwardRef< + React.ElementRef<typeof PopoverPrimitive.Trigger>, + React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Trigger> +>((props, ref) => ( + <PopoverPrimitive.Trigger ref={ref} data-slot="popover-trigger" {...props} /> +)) +PopoverTrigger.displayName = PopoverPrimitive.Trigger.displayName @@ -function PopoverContent({ - className, - align = 'center', - sideOffset = 4, - ...props -}: React.ComponentProps<typeof PopoverPrimitive.Content>) { - return ( - <PopoverPrimitive.Portal> - <PopoverPrimitive.Content - data-slot="popover-content" - align={align} - sideOffset={sideOffset} - className={cn( - 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', - className, - )} - {...props} - /> - </PopoverPrimitive.Portal> - ) -} +const PopoverContent = React.forwardRef< + React.ElementRef<typeof PopoverPrimitive.Content>, + React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content> +>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => ( + <PopoverPrimitive.Portal> + <PopoverPrimitive.Content + ref={ref} + data-slot="popover-content" + align={align} + sideOffset={sideOffset} + className={cn( + 'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', + className, + )} + {...props} + /> + </PopoverPrimitive.Portal> +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName @@ -function PopoverAnchor({ - ...props -}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) { - return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} /> -} +const PopoverAnchor = React.forwardRef< + React.ElementRef<typeof PopoverPrimitive.Anchor>, + React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Anchor> +>((props, ref) => ( + <PopoverPrimitive.Anchor ref={ref} data-slot="popover-anchor" {...props} /> +)) +PopoverAnchor.displayName = PopoverPrimitive.Anchor.displayNamegame-buying-guide/components/ui/alert-dialog.tsx (1)
121-143: Missingdata-slotattributes for consistency.
AlertDialogActionandAlertDialogCancelare missingdata-slotattributes, while all other components in this file and across the UI system include them. Consider adding for consistency with the slot-based composition pattern.♻️ Suggested fix
function AlertDialogAction({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Action>) { return ( <AlertDialogPrimitive.Action + data-slot="alert-dialog-action" className={cn(buttonVariants(), className)} {...props} /> ) } function AlertDialogCancel({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Cancel>) { return ( <AlertDialogPrimitive.Cancel + data-slot="alert-dialog-cancel" className={cn(buttonVariants({ variant: 'outline' }), className)} {...props} /> ) }game-buying-guide/app/api/discover-platforms/route.ts (1)
57-69: Trim and validategameTitlebefore building URLs.
Whitespace-only or non-string inputs currently pass and yield meaningless searches.♻️ Suggested input tightening
- const { gameTitle } = await request.json() + const body = await request.json() + const gameTitle = + typeof body?.gameTitle === 'string' ? body.gameTitle.trim() : '' if (!gameTitle) { return NextResponse.json({ error: 'Game title is required' }, { status: 400 }) }game-buying-guide/app/api/steamdb-price-history/route.ts (1)
110-118: Normalize line parsing for CRLF anddata:without a space.
If a line ends with\ror comes asdata:(no space), the current check skips it.🧩 More robust parsing
- for (const line of lines) { - if (line.startsWith('data: ')) { + for (const line of lines) { + const trimmed = line.trimEnd() + if (trimmed.startsWith('data:')) { try { - const jsonStr = line.slice(6).trim() + const jsonStr = trimmed.slice(5).trim() if (!jsonStr || jsonStr === '[DONE]') continuegame-buying-guide/app/layout.tsx (1)
7-8: Unused font configurations.The
_geistand_geistMonofont configurations are loaded but never applied. The body usesfont-sanswhich references Tailwind's default sans-serif stack, not these Google fonts. Either apply the font classes (e.g.,_geist.className) or remove these imports to avoid unnecessary network requests for unused fonts.♻️ Option 1: Apply the loaded fonts
- <body className={`font-sans antialiased`}> + <body className={`${_geist.className} antialiased`}>♻️ Option 2: Remove unused imports
import React from "react" import type { Metadata } from 'next' -import { Geist, Geist_Mono } from 'next/font/google' import { Analytics } from '@vercel/analytics/next' import './globals.css' -const _geist = Geist({ subsets: ["latin"] }); -const _geistMono = Geist_Mono({ subsets: ["latin"] }); -game-buying-guide/hooks/use-game-search.ts (2)
20-20: Inconsistent function naming.
analyzeplatformshould beanalyzePlatformto match camelCase convention used elsewhere (e.g.,analyzeSteamDB).- const analyzeplatform = useCallback( + const analyzePlatform = useCallback(Also update references on lines 339 and 348.
62-120: Duplicate SSE parsing logic.The SSE message parsing in
analyzeplatform(lines 62-120) andanalyzeSteamDB(lines 215-272) are nearly identical. Consider extracting a shared helper function to reduce duplication and simplify maintenance.♻️ Suggested refactor
type SSEHandler = { onStreamingUrl?: (url: string) => void; onStatus?: (message: string) => void; onComplete?: (result: unknown) => void; onError?: (error: string) => void; }; function parseSSELine(line: string, handler: SSEHandler): boolean { if (!line.startsWith('data: ')) return false; try { const jsonStr = line.slice(6).trim(); if (!jsonStr || jsonStr === '[DONE]') return false; const data = JSON.parse(jsonStr); if (data.type === 'STREAMING_URL' || data.streamingUrl) { handler.onStreamingUrl?.(data.streamingUrl || data.url); } if (data.type === 'STATUS' || data.status) { handler.onStatus?.(data.message || data.status); } if (data.type === 'COMPLETE' || data.result || data.resultJson) { let result = data.result || data.resultJson; if (typeof result === 'string') { const jsonMatch = result.match(/\{[\s\S]*\}/); if (jsonMatch) result = JSON.parse(jsonMatch[0]); } if (result && typeof result === 'object') { handler.onComplete?.(result); return true; // hasReceivedComplete } } if (data.type === 'ERROR' || data.error) { handler.onError?.(data.error || data.message || 'Unknown error'); } } catch { // Skip malformed JSON } return false; }Also applies to: 215-272
game-buying-guide/components/agent-grid.tsx (1)
13-59: Consider storing an identifier instead of the whole agent object.
Keeping the fullAgentStatusin state can go stale if theagentsarray is refreshed with new objects. Storing an id (e.g., platformName) and deriving the agent avoids stale data.♻️ Example refactor to derive expanded agent
-export function AgentGrid({ agents }: AgentGridProps) { - const [expandedAgent, setExpandedAgent] = useState<AgentStatus | null>(null) +export function AgentGrid({ agents }: AgentGridProps) { + const [expandedPlatform, setExpandedPlatform] = useState<string | null>(null) + const expandedAgent = expandedPlatform + ? agents.find((a) => a.platformName === expandedPlatform) ?? null + : null @@ - {agents.map((agent) => ( + {agents.map((agent) => ( <AgentCard key={agent.platformName} agent={agent} - onExpandPreview={(a) => setExpandedAgent(a)} + onExpandPreview={(a) => setExpandedPlatform(a.platformName)} /> ))} @@ - {expandedAgent && expandedAgent.streamingUrl && ( + {expandedAgent && expandedAgent.streamingUrl && ( <LiveBrowserPreview streamingUrl={expandedAgent.streamingUrl} platformName={expandedAgent.platformName} - onClose={() => setExpandedAgent(null)} + onClose={() => setExpandedPlatform(null)} /> )}game-buying-guide/components/ui/label.tsx (1)
8-20: Add React.forwardRef to preserve ref support for Label.The current wrapper drops ref support at runtime, even though
React.ComponentPropsincludes it in the type. This creates a mismatch where TypeScript permitsrefusage but it fails at runtime. Per Radix UI's composition guidelines, wrapper components should forward refs for compatibility with form libraries and focus management patterns.♻️ Suggested implementation
-function Label({ - className, - ...props -}: React.ComponentProps<typeof LabelPrimitive.Root>) { - return ( - <LabelPrimitive.Root - data-slot="label" - className={cn( - 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', - className, - )} - {...props} - /> - ) -} +const Label = React.forwardRef< + React.ElementRef<typeof LabelPrimitive.Root>, + React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> +>(({ className, ...props }, ref) => ( + <LabelPrimitive.Root + ref={ref} + data-slot="label" + className={cn( + 'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', + className, + )} + {...props} + /> +)) +Label.displayName = LabelPrimitive.Root.displayNamegame-buying-guide/components/ui/context-menu.tsx (1)
56-234: Consider forwardRef to preserve ref access on public wrappers.These wrapper components drop
ref, limiting ref-based focus management and use withasChildcomposition. Per Radix-UI best practices, applyReact.forwardRefwithdisplayNameto enable proper ref forwarding across all exported components (ContextMenuTrigger, ContextMenuContent, ContextMenuItem, ContextMenuCheckboxItem, ContextMenuRadioItem, ContextMenuLabel, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSubTrigger, ContextMenuSubContent).♻️ Example ref-forwarding pattern (apply across wrappers)
- function ContextMenuItem({ - className, - inset, - variant = 'default', - ...props - }: React.ComponentProps<typeof ContextMenuPrimitive.Item> & { - inset?: boolean - variant?: 'default' | 'destructive' - }) { - return ( - <ContextMenuPrimitive.Item - data-slot="context-menu-item" - data-inset={inset} - data-variant={variant} - className={cn( - "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", - className, - )} - {...props} - /> - ) - } +const ContextMenuItem = React.forwardRef< + React.ElementRef<typeof ContextMenuPrimitive.Item>, + React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & { + inset?: boolean + variant?: 'default' | 'destructive' + } +>(({ className, inset, variant = 'default', ...props }, ref) => ( + <ContextMenuPrimitive.Item + ref={ref} + data-slot="context-menu-item" + data-inset={inset} + data-variant={variant} + className={cn( + "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + className, + )} + {...props} + /> +)) +ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayNamegame-buying-guide/components/agent-card.tsx (1)
118-125: Note: iframeonErrormay not catch content loading failures.The
onErrorevent on<iframe>fires when the frame itself fails to load (e.g., network error), but won't trigger for many content-related issues (e.g., HTTP errors, blocked content). The current fallback loader is a reasonable UX for this limitation.game-buying-guide/components/ui/field.tsx (1)
128-139:FieldTitleuses the samedata-slotasFieldLabel.Both
FieldTitle(Line 131) andFieldLabel(Line 116) usedata-slot="field-label". If these components serve different purposes, consider using distinct slot names (e.g.,"field-title") to avoid styling selector conflicts and improve clarity.♻️ Proposed change
function FieldTitle({ className, ...props }: React.ComponentProps<'div'>) { return ( <div - data-slot="field-label" + data-slot="field-title" className={cn(
| <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => setIsExpanded(!isExpanded)}> | ||
| {isExpanded ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | ||
| </Button> | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| className="h-7 w-7 hover:bg-destructive/20 hover:text-destructive" | ||
| onClick={onClose} | ||
| > | ||
| <X className="w-4 h-4" /> | ||
| </Button> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add accessible labels to the icon-only controls.
Icon-only buttons aren’t announced by screen readers without an explicit label.
✅ Add aria-labels
- <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => setIsExpanded(!isExpanded)}>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="h-7 w-7"
+ onClick={() => setIsExpanded(!isExpanded)}
+ aria-label={isExpanded ? 'Minimize preview' : 'Maximize preview'}
+ >
{isExpanded ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
</Button>
<Button
variant="ghost"
size="icon"
className="h-7 w-7 hover:bg-destructive/20 hover:text-destructive"
onClick={onClose}
+ aria-label="Close preview"
>📝 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.
| <Button variant="ghost" size="icon" className="h-7 w-7" onClick={() => setIsExpanded(!isExpanded)}> | |
| {isExpanded ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="h-7 w-7 hover:bg-destructive/20 hover:text-destructive" | |
| onClick={onClose} | |
| > | |
| <X className="w-4 h-4" /> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="h-7 w-7" | |
| onClick={() => setIsExpanded(!isExpanded)} | |
| aria-label={isExpanded ? 'Minimize preview' : 'Maximize preview'} | |
| > | |
| {isExpanded ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />} | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="icon" | |
| className="h-7 w-7 hover:bg-destructive/20 hover:text-destructive" | |
| onClick={onClose} | |
| aria-label="Close preview" | |
| > | |
| <X className="w-4 h-4" /> | |
| </Button> |
🤖 Prompt for AI Agents
In `@game-buying-guide/components/live-browser-preview.tsx` around lines 45 - 55,
The icon-only Buttons (the toggle using isExpanded/setIsExpanded with
Minimize2/Maximize2 and the close Button using onClose with X) lack accessible
labels; add descriptive aria-label attributes to both Buttons (e.g.,
aria-label="Expand preview" / "Minimize preview" for the toggle based on
isExpanded and aria-label="Close preview" for the close button) and consider
adding aria-pressed or aria-expanded on the toggle Button to reflect state via
setIsExpanded/isExpanded so screen readers get the current state.
| React.useEffect(() => { | ||
| if (!api) return | ||
| onSelect(api) | ||
| api.on('reInit', onSelect) | ||
| api.on('select', onSelect) | ||
|
|
||
| return () => { | ||
| api?.off('select', onSelect) | ||
| } | ||
| }, [api, onSelect]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing cleanup for reInit event listener.
The effect subscribes to both reInit and select events but only removes the select listener on cleanup. This will cause a memory leak and potential stale callback issues.
🐛 Proposed fix to clean up both event listeners
React.useEffect(() => {
if (!api) return
onSelect(api)
api.on('reInit', onSelect)
api.on('select', onSelect)
return () => {
+ api?.off('reInit', onSelect)
api?.off('select', onSelect)
}
}, [api, onSelect])🤖 Prompt for AI Agents
In `@game-buying-guide/components/ui/carousel.tsx` around lines 96 - 105, The
effect in React.useEffect subscribes api.on('reInit', onSelect) and
api.on('select', onSelect) but only removes the 'select' listener on cleanup;
update the cleanup to also call api?.off('reInit', onSelect) so both
subscriptions are removed, keeping the current checks for api and preserving
onSelect as the handler to avoid memory leaks and stale callbacks.
| function Progress({ | ||
| className, | ||
| value, | ||
| ...props | ||
| }: React.ComponentProps<typeof ProgressPrimitive.Root>) { | ||
| return ( | ||
| <ProgressPrimitive.Root | ||
| data-slot="progress" | ||
| className={cn( | ||
| 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', | ||
| className, | ||
| )} | ||
| {...props} | ||
| > | ||
| <ProgressPrimitive.Indicator | ||
| data-slot="progress-indicator" | ||
| className="bg-primary h-full w-full flex-1 transition-all" | ||
| style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | ||
| /> | ||
| </ProgressPrimitive.Root> | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing value prop on ProgressPrimitive.Root breaks accessibility.
The value is destructured from props but never passed to ProgressPrimitive.Root. Radix UI uses this prop to set aria-valuenow, so screen readers won't announce the current progress percentage.
♿ Proposed fix to restore accessibility
<ProgressPrimitive.Root
data-slot="progress"
className={cn(
'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full',
className,
)}
+ value={value}
{...props}
>📝 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.
| function Progress({ | |
| className, | |
| value, | |
| ...props | |
| }: React.ComponentProps<typeof ProgressPrimitive.Root>) { | |
| return ( | |
| <ProgressPrimitive.Root | |
| data-slot="progress" | |
| className={cn( | |
| 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', | |
| className, | |
| )} | |
| {...props} | |
| > | |
| <ProgressPrimitive.Indicator | |
| data-slot="progress-indicator" | |
| className="bg-primary h-full w-full flex-1 transition-all" | |
| style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | |
| /> | |
| </ProgressPrimitive.Root> | |
| ) | |
| function Progress({ | |
| className, | |
| value, | |
| ...props | |
| }: React.ComponentProps<typeof ProgressPrimitive.Root>) { | |
| return ( | |
| <ProgressPrimitive.Root | |
| data-slot="progress" | |
| className={cn( | |
| 'bg-primary/20 relative h-2 w-full overflow-hidden rounded-full', | |
| className, | |
| )} | |
| value={value} | |
| {...props} | |
| > | |
| <ProgressPrimitive.Indicator | |
| data-slot="progress-indicator" | |
| className="bg-primary h-full w-full flex-1 transition-all" | |
| style={{ transform: `translateX(-${100 - (value || 0)}%)` }} | |
| /> | |
| </ProgressPrimitive.Root> | |
| ) | |
| } |
🤖 Prompt for AI Agents
In `@game-buying-guide/components/ui/progress.tsx` around lines 8 - 28, The
Progress component destructures value but never forwards it to
ProgressPrimitive.Root, breaking Radix's aria-valuenow; update the Progress
function to pass the value prop into ProgressPrimitive.Root (e.g., include
value={value} on ProgressPrimitive.Root) so screen readers receive the current
progress while leaving the translateX Indicator calculation as-is.
| typescript: { | ||
| ignoreBuildErrors: true, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid ignoring TypeScript build errors.
Setting ignoreBuildErrors: true suppresses TypeScript errors during the build, which can mask real bugs and type issues. This is particularly problematic for a cookbook example that others may reference as a best practice.
🔧 Recommended fix
- typescript: {
- ignoreBuildErrors: true,
- },If there are specific TypeScript errors causing build failures, consider fixing them rather than suppressing all errors.
📝 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.
| typescript: { | |
| ignoreBuildErrors: true, | |
| }, |
🤖 Prompt for AI Agents
In `@game-buying-guide/next.config.mjs` around lines 3 - 5, The config currently
suppresses TypeScript build errors by setting the typescript.ignoreBuildErrors
flag to true; remove this flag or set typescript.ignoreBuildErrors to false and
instead fix the underlying TypeScript issues causing build failures (run tsc or
next build locally, address reported errors in the codebase), so that types are
enforced during CI/build rather than ignored.
| "recharts": "2.15.4", | ||
| "sonner": "^1.7.4", | ||
| "tailwind-merge": "^3.3.1", | ||
| "tailwindcss-animate": "^1.0.7", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's look at the package.json file to confirm both packages are listed
cd game-buying-guide 2>/dev/null || cd .
if [ -f "package.json" ]; then
echo "=== Lines around animation libraries in package.json ==="
cat -n package.json | sed -n '55,80p'
else
find . -name "package.json" -type f | head -5
fiRepository: tinyfish-io/tinyfish-cookbook
Length of output: 841
🏁 Script executed:
#!/bin/bash
# Check if both animation libraries are actually used in the codebase
echo "=== Checking tailwindcss-animate usage ==="
rg "tailwindcss-animate" -l --type-list | head -20
echo ""
echo "=== Checking tw-animate-css usage ==="
rg "tw-animate-css" -l --type-list | head -20
echo ""
echo "=== Checking for animation imports and usage patterns ==="
rg "import.*animate" -i --type js --type ts --type tsx | head -20Repository: tinyfish-io/tinyfish-cookbook
Length of output: 1038
🏁 Script executed:
#!/bin/bash
# Better search for actual usage
echo "=== Checking for direct require/import of packages ==="
rg "require.*tailwindcss-animate|import.*tailwindcss-animate"
echo ""
echo "=== Checking for require/import of tw-animate-css ==="
rg "require.*tw-animate-css|import.*tw-animate-css"
echo ""
echo "=== Checking tailwind.config files ==="
find . -name "tailwind.config.*" -type f -exec cat {} \;
echo ""
echo "=== Checking CSS files for animate references ==="
rg "animate-" --type css | head -30
echo ""
echo "=== Checking HTML/JSX for animation class usage ==="
rg "animate-\w+" --type html --type jsx | head -20Repository: tinyfish-io/tinyfish-cookbook
Length of output: 979
🌐 Web query:
What is the difference between tailwindcss-animate and tw-animate-css npm packages?
💡 Result:
-
Integration model
tailwindcss-animateis a Tailwind plugin that you register intailwind.config.js(legacy JS plugin system). It generates utilities likeanimate-in,animate-out,fade-in,zoom-in, plus parameter utilities such asduration-*,delay-*,ease-*, etc. [1]tw-animate-cssis a CSS-first package intended for Tailwind CSS v4: you import a CSS file (e.g.@import "tw-animate-css";) rather than registering a JS plugin. It explicitly positions itself as a replacement fortailwindcss-animateunder Tailwind v4’s CSS-first architecture. [2][3]
-
Tailwind version focus
tailwindcss-animatetargets the traditional Tailwind plugin workflow (commonly Tailwind v3-era configs). [1]tw-animate-csstargets Tailwind v4 compatibility and avoids relying on the “legacy JavaScript plugin system.” [2][3]
-
What you get out of the box
- Both aim to provide a similar “beautiful animations” utility approach (enter/exit, fade/slide/zoom/spin patterns).
tw-animate-cssalso calls out bundled presets likeaccordion-down,accordion-up, andcaret-blink. [2][3]
- Both aim to provide a similar “beautiful animations” utility approach (enter/exit, fade/slide/zoom/spin patterns).
Sources: tailwindcss-animate npm page [1]; tw-animate-css npm page [2] and GitHub README [3].
Remove unused tailwindcss-animate dependency.
tailwindcss-animate (line 62) is not used anywhere in the codebase, while tw-animate-css (line 73) is actively used via @import 'tw-animate-css' in CSS files. Since the project uses Tailwind CSS v4, tw-animate-css is the correct choice—it's a CSS-first package designed as a v4 replacement for the legacy tailwindcss-animate plugin system. Remove tailwindcss-animate from dependencies to eliminate unnecessary bundle size.
🤖 Prompt for AI Agents
In `@game-buying-guide/package.json` at line 62, Remove the unused dependency
"tailwindcss-animate" from package.json (it is not referenced anywhere) and
ensure the project relies on "tw-animate-css" instead; after removing the entry
for tailwindcss-animate, update the lockfile by running the package manager
(npm/yarn/pnpm install) and verify no imports or references to
tailwindcss-animate remain in the repo (search for "tailwindcss-animate");
commit the package.json and lockfile changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@game-buying-guide/README.md`:
- Line 14: Wrap the bare URLs on the README.md at the referenced occurrences in
angle brackets so Markdown treats them as links: replace the plain URL on the
line around the image/asset reference (the bare
"https://github.com/user-attachments/assets/61c22b80-2cfc-40a6-bc3a-7d5917cf71a9"
at line 14) and the other bare URL at line 126 with <https://...> respectively
to ensure consistent markdown rendering.
- Line 103: Replace the bolded text "**Prerequisites**" with a proper Markdown
heading (e.g., "## Prerequisites" or the appropriate level for the document) to
improve document structure; locate the literal "**Prerequisites**" in README.md
and change it to the chosen heading syntax, ensuring surrounding spacing/newline
conventions for Markdown headings are preserved.
- Around line 161-163: Replace the hard-tabbed line under the "## Environment
Variables" heading with clear markdown formatting: either a two-column table
with headers like "Variable" and "Description" and a row for MINO_API_KEY
("MINO_API_KEY | API key for Mino browser automation") or a bulleted list item
("MINO_API_KEY — API key for Mino browser automation"); update the text around
the "## Environment Variables" heading and the MINO_API_KEY entry to use spaces
and standard Markdown syntax instead of tabs.
|
|
||
| ## Demo | ||
|
|
||
| https://github.com/user-attachments/assets/61c22b80-2cfc-40a6-bc3a-7d5917cf71a9 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wrap bare URLs in angle brackets for proper markdown formatting.
Lines 14 and 126 contain bare URLs that should be wrapped in angle brackets (<URL>) for consistent markdown rendering.
📝 Proposed fixes
Line 14:
-https://github.com/user-attachments/assets/61c22b80-2cfc-40a6-bc3a-7d5917cf71a9
+<https://github.com/user-attachments/assets/61c22b80-2cfc-40a6-bc3a-7d5917cf71a9>Line 126:
-Open http://localhost:3000
+Open <http://localhost:3000>Also applies to: 126-126
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
14-14: Bare URL used
(MD034, no-bare-urls)
🤖 Prompt for AI Agents
In `@game-buying-guide/README.md` at line 14, Wrap the bare URLs on the README.md
at the referenced occurrences in angle brackets so Markdown treats them as
links: replace the plain URL on the line around the image/asset reference (the
bare
"https://github.com/user-attachments/assets/61c22b80-2cfc-40a6-bc3a-7d5917cf71a9"
at line 14) and the other bare URL at line 126 with <https://...> respectively
to ensure consistent markdown rendering.
|
|
||
|
|
||
| ## How to Run | ||
| **Prerequisites** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use proper heading syntax instead of emphasis.
Line 103 uses bold emphasis (**Prerequisites**) where a proper markdown heading would be more appropriate for document structure.
📝 Proposed fix
-**Prerequisites**
+### Prerequisites📝 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.
| **Prerequisites** | |
| ### Prerequisites |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
103-103: Emphasis used instead of a heading
(MD036, no-emphasis-as-heading)
🤖 Prompt for AI Agents
In `@game-buying-guide/README.md` at line 103, Replace the bolded text
"**Prerequisites**" with a proper Markdown heading (e.g., "## Prerequisites" or
the appropriate level for the document) to improve document structure; locate
the literal "**Prerequisites**" in README.md and change it to the chosen heading
syntax, ensuring surrounding spacing/newline conventions for Markdown headings
are preserved.
| ## Environment Variables | ||
|
|
||
| MINO_API_KEY - API key for Mino browser automation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve environment variables formatting.
The environment variables section uses hard tabs and unclear formatting. Consider using a proper markdown table or list format for better clarity.
📝 Proposed fix (table format)
## Environment Variables
-MINO_API_KEY - API key for Mino browser automation
+| Variable | Description |
+|----------|-------------|
+| `MINO_API_KEY` | API key for Mino browser automation |Alternatively, as a list:
## Environment Variables
-MINO_API_KEY - API key for Mino browser automation
+- `MINO_API_KEY` - API key for Mino browser automation📝 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.
| ## Environment Variables | |
| MINO_API_KEY - API key for Mino browser automation | |
| ## Environment Variables | |
| | Variable | Description | | |
| |----------|-------------| | |
| | `MINO_API_KEY` | API key for Mino browser automation | |
| ## Environment Variables | |
| MINO_API_KEY - API key for Mino browser automation | |
| ## Environment Variables | |
| - `MINO_API_KEY` - API key for Mino browser automation |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)
163-163: Hard tabs
Column: 13
(MD010, no-hard-tabs)
🤖 Prompt for AI Agents
In `@game-buying-guide/README.md` around lines 161 - 163, Replace the hard-tabbed
line under the "## Environment Variables" heading with clear markdown
formatting: either a two-column table with headers like "Variable" and
"Description" and a row for MINO_API_KEY ("MINO_API_KEY | API key for Mino
browser automation") or a bulleted list item ("MINO_API_KEY — API key for Mino
browser automation"); update the text around the "## Environment Variables"
heading and the MINO_API_KEY entry to use spaces and standard Markdown syntax
instead of tabs.
hongjingzhou
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Please check the review comments from CodeRabbitAI.
Also, I suggest adding a .env.example file.
|
@hongjingzhou I’ve resolved a few issues highlighted by CodeRabbitAI and added .env.example file at the project root. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@game-buying-guide/.env.example`:
- Line 1: The .env example contains spaces around the assignment which can break
dotenv parsing; update the MINO_API_KEY entry (symbol: MINO_API_KEY) to remove
spaces around the '=' so it reads as a standard dotenv assignment (e.g.,
MINO_API_KEY=YOUR_API_KEY) in the .env.example file; ensure there are no
trailing spaces around the key or value.
| @@ -0,0 +1 @@ | |||
| MINO_API_KEY = YOUR_API_KEY | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove spaces around = to ensure dotenv parsing works.
Some dotenv parsers treat spaces as part of the key/value and won’t load the variable.
✅ Proposed fix
-MINO_API_KEY = YOUR_API_KEY
+MINO_API_KEY=YOUR_API_KEY📝 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.
| MINO_API_KEY = YOUR_API_KEY | |
| MINO_API_KEY=YOUR_API_KEY |
🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 1-1: [SpaceCharacter] The line has spaces around equal sign
(SpaceCharacter)
🤖 Prompt for AI Agents
In `@game-buying-guide/.env.example` at line 1, The .env example contains spaces
around the assignment which can break dotenv parsing; update the MINO_API_KEY
entry (symbol: MINO_API_KEY) to remove spaces around the '=' so it reads as a
standard dotenv assignment (e.g., MINO_API_KEY=YOUR_API_KEY) in the .env.example
file; ensure there are no trailing spaces around the key or value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@game-buying-guide/app/globals.css`:
- Line 1: Restore the missing globals.css by re-adding the Tailwind directives
and the design system tokens: insert `@tailwind` base, `@tailwind` components, and
`@tailwind` utilities at the top, then define CSS variables used across the app
(e.g. --background, --foreground, --primary, --primary-foreground, --muted,
etc.) including light and dark theme blocks (prefer :root and
[data-theme="dark"] or .dark selectors) and include base layer styles and
component tokens (buttons, headings, links) so components relying on those
variables render correctly; locate the project’s globals.css file and repopulate
it with the original variable definitions and theme token structure referenced
by components.
No description provided.