Uh oh!
There was an error while loading. Please reload this page.
Conversation
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review infoConfiguration used: Organization UI Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (6)
📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds an AI Image Generator feature: SDK types, queries, and mutations; web SDK wrappers; UI components and dialog; integration into publish/wave toolbars and perks pages; feature flag, config, localization entries, and package version bumps. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI as Web App UI
participant Query as React Query
participant SDK as SDK Mutation
participant Server as Private API
participant Gallery as Gallery Service
User->>UI: Enter prompt / request price
UI->>UI: resolve username & access token
UI->>Query: getAiGeneratePriceQueryOptions(accessToken)
Query->>Server: POST /private-api/ai-generate-price { code }
Server-->>Query: AiGenerationPrice[]
Query-->>UI: pricing
User->>UI: Confirm generate
UI->>SDK: useGenerateImage(username, accessToken).mutate({ prompt, aspect_ratio })
SDK->>Server: POST /private-api/ai-generate-image { code, us, prompt, aspect_ratio }
Server-->>SDK: AiGenerationResponse { url, cost, generation_id }
SDK->>Query: invalidate points cache for username
Query-->>UI: updated balance
UI-->>User: show generated image
User->>UI: Save to gallery
UI->>Gallery: add image (mutation)
Gallery-->>UI: confirmation
UI-->>User: success
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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.
Actionable comments posted: 8
🧹 Nitpick comments (1)
packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts (1)
2-3: Align this query with SDK transport (getBoundFetch)This path bypasses the SDK’s bound fetch layer, unlike other AI endpoints, which can create inconsistent runtime behavior.
Proposed change
-import { CONFIG } from "../../core";-import { QueryKeys } from "../../core";+import { CONFIG, getBoundFetch, QueryKeys } from "../../core"; @@ queryKey: QueryKeys.ai.prices(), queryFn: async () => { - const response = await fetch(CONFIG.privateApiHost + "/private-api/ai-generate-price", {+ const fetchApi = getBoundFetch();+ const response = await fetchApi(CONFIG.privateApiHost + "/private-api/ai-generate-price", { method: "POST", headers: { "Content-Type": "application/json",Also applies to: 10-16
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts` around lines 2 - 3, The query bypasses the SDK transport layer; update get-ai-generate-price-query-options (and the similar blocks at lines referenced) to use the SDK's getBoundFetch instead of calling fetch/absolute paths directly: import and call getBoundFetch(...) (using CONFIG) to obtain the bound fetch function and replace direct fetch/URL usage with that bound fetch, keep existing QueryKeys and payload logic intact so behavior remains the same but routed through the SDK transport.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/app/perks/ai-generator/_page.tsx`:
- Around line 13-23: Remove the outer Link wrapper and instead pass
href="/perks" directly to the Button component to avoid nested interactive
elements; update the Button invocation in
apps/web/src/app/perks/ai-generator/_page.tsx (the Button component that
currently has size="sm", appearance="gray-link", icon={<UilArrowLeft />},
iconPlacement="left", noPadding={true} and children i18next.t("g.back")) so it
receives an href prop and the surrounding Link is deleted.
- Around line 9-33: Add a feature-flag guard to the AiGeneratorPage component so
the /perks/ai-generator route cannot be accessed when the feature is disabled:
wrap the page (or its returned JSX) in an EcencyConfigManager.Conditional that
checks visionFeatures.aiImageGenerator.enabled and render a fallback (e.g.,
redirect to /perks or a not-found/disabled message) when the condition is false;
update AiGeneratorPage (and/or its parent layout) that currently renders
AiImageGenerator to perform this check before returning the main JSX.
In `@apps/web/src/app/publish/_components/publish-editor-toolbar.tsx`:
- Around line 440-451: The "AI" button label is hardcoded; replace the literal
string with a localized string using i18next (same key used for the tooltip). In
the JSX where Button is rendered (inside StyledTooltip/LoginRequired, tied to
setShowAiGenerator and the Button component), change the children from "AI" to
i18next.t("ai-image-generator.toolbar-button") (or the appropriate i18n key) so
the button text is translated consistently with the tooltip.
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`:
- Around line 73-76: The cost useMemo currently always reads prices[0].cost
which breaks balance/validation when the user chooses a different aspect ratio;
update the useMemo for the cost constant in ai-image-generator.tsx to look up
the price entry in prices that matches the currently selected aspect ratio
(e.g., match by selectedRatio or selectedAspectRatio state) and fall back to a
sensible default if no match exists, and apply the same fix to the other similar
useMemo/price-lookup occurrences around the 78-87 region so all balance checks
use the selected ratio's cost rather than prices[0].
- Around line 131-134: The handleDownload callback opens external URLs with
window.open(generatedUrl, "_blank") which allows opener abuse; update
handleDownload (referencing the handleDownload function and generatedUrl
variable) to call window.open with noopener,noreferrer (e.g. pass
"noopener,noreferrer" in the third argument) and, for extra safety, null out the
opener on the returned window object (if non-null) so the opened page cannot
access window.opener.
- Around line 240-252: The interactive aspect-ratio option is rendered as a
motion.div (keyed by price.aspect_ratio) and is not keyboard accessible; change
each option to a semantic interactive element or add keyboard handlers so
setSelectedPrice(price) can be triggered via Enter/Space and the element is
focusable—specifically replace or augment the motion.div used for options with a
button (or add tabIndex={0}, role="button", onKeyDown handling Enter/Space, and
aria-pressed reflecting selectedPrice?.aspect_ratio === price.aspect_ratio) and
preserve the existing onClick and className styling so keyboard users can focus,
activate, and perceive selection state.
In `@apps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsx`:
- Around line 47-53: The "AI" Button label is hardcoded; update the WaveForm
toolbar to use the app i18n function (e.g., import and call useTranslation()/t)
instead of the literal string: replace the "AI" child in the Button (the
component using setShowAiGenerator and disabled) with a translated key (e.g.,
t('waveForm.toolbar.ai') or similar), add the corresponding import for the
translation hook, and ensure the new translation key is used consistently when
rendering the Button label.
In `@packages/sdk/src/modules/ai/mutations/use-generate-image.ts`:
- Around line 5-8: The hook is capturing the auth token at initialization and
never letting callers override it; add an optional per-call token override to
the params (e.g., extend GenerateImageParams with an optional authToken?: string
or token?: string) and update the mutate function inside useGenerateImage to
prefer the passed-in token when present (fall back to the captured token
otherwise). Change the request code that currently reads the closure token
(search for the mutate/execute function in use-generate-image.ts and any
occurrences at the ranges noted) to use params.authToken (or token) if provided,
and update other similar hooks/mutations referenced (lines ~23-27 and ~37-39) to
accept and honor the same per-call token override.
---
Nitpick comments:
In `@packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts`:
- Around line 2-3: The query bypasses the SDK transport layer; update
get-ai-generate-price-query-options (and the similar blocks at lines referenced)
to use the SDK's getBoundFetch instead of calling fetch/absolute paths directly:
import and call getBoundFetch(...) (using CONFIG) to obtain the bound fetch
function and replace direct fetch/URL usage with that bound fetch, keep existing
QueryKeys and payload logic intact so behavior remains the same but routed
through the SDK transport.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (7)
packages/sdk/dist/browser/index.d.tsis excluded by!**/dist/**packages/sdk/dist/browser/index.jsis excluded by!**/dist/**packages/sdk/dist/browser/index.js.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.cjsis excluded by!**/dist/**packages/sdk/dist/node/index.cjs.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.mjsis excluded by!**/dist/**packages/sdk/dist/node/index.mjs.mapis excluded by!**/dist/**,!**/*.map
📒 Files selected for processing (21)
apps/web/src/api/sdk-mutations/index.tsapps/web/src/api/sdk-mutations/use-generate-image-mutation.tsapps/web/src/app/perks/_page.tsxapps/web/src/app/perks/ai-generator/_page.tsxapps/web/src/app/perks/ai-generator/page.tsxapps/web/src/app/publish/_components/publish-editor-toolbar.tsxapps/web/src/config/config.template.tsapps/web/src/config/config.tsapps/web/src/features/i18n/locales/en-US.jsonapps/web/src/features/shared/ai-image-generator/ai-image-generator-dialog.tsxapps/web/src/features/shared/ai-image-generator/ai-image-generator.tsxapps/web/src/features/shared/ai-image-generator/index.tsapps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsxpackages/sdk/src/index.tspackages/sdk/src/modules/ai/index.tspackages/sdk/src/modules/ai/mutations/index.tspackages/sdk/src/modules/ai/mutations/use-generate-image.tspackages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.tspackages/sdk/src/modules/ai/queries/index.tspackages/sdk/src/modules/ai/types/index.tspackages/sdk/src/modules/core/query-keys.ts
Uh oh!
There was an error while loading. Please reload this page.
| <Link href="/perks"> | ||
| <Button | ||
| size="sm" | ||
| appearance="gray-link" | ||
| icon={<UilArrowLeft />} | ||
| iconPlacement="left" | ||
| noPadding={true} | ||
| > | ||
| {i18next.t("g.back")} | ||
| </Button> | ||
| </Link> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"=== Locate Button implementation(s) ==="
fd -a 'button.tsx''apps''packages'2>/dev/null ||echo"No direct button.tsx found"echo""echo"=== Search for Button exports and component definition ==="
rg -n 'export.*Button|export.*button''apps/web/src' --type tsx --type ts -B1 -A3 2>/dev/null | head -50
echo""echo"=== Inspect UI Button implementation ==="
find apps packages -name 'button*' -o -name 'Button*'2>/dev/null | grep -E '\.(tsx|ts)$'| head -10
echo""echo"=== Check AI generator page current content ==="
head -n 80 'apps/web/src/app/perks/ai-generator/_page.tsx'2>/dev/null ||echo"File not found at expected path"echo""echo"=== Search for `@/features/ui` Button import ==="
rg -n 'from.*@/features/ui.*Button|import.*Button.*@/features/ui''apps/web/src' --type tsx --type ts -B2 -A2Repository: ecency/vision-next
Length of output: 1766
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"=== Find `@/features/ui` directory structure ==="
find apps/web/src/features/ui -type f -name '*.tsx' -o -name '*.ts'| head -20
echo""echo"=== Search for Button component export in features/ui ==="
rg -n 'Button''apps/web/src/features/ui' --type tsx --type ts -B2 -A5 | head -100
echo""echo"=== Check if Button uses asChild pattern (Radix UI style) ==="
rg -n 'asChild|forwardRef.*Button|Slot''apps/web/src/features/ui' --type tsx --type ts -B1 -A3
echo""echo"=== Inspect ai-generator page imports and usage ==="
grep -n 'import\|Button\|Link''apps/web/src/app/perks/ai-generator/_page.tsx'| head -30
echo""echo"=== Check for any polymorphic Button patterns ==="
rg -n 'href\s*\?.*Button|polymorphic|as\s*=''apps/web/src/features/ui' --type tsx --type ts -B1 -A2Repository: ecency/vision-next
Length of output: 1163
🏁 Script executed:
#!/bin/bashset -euo pipefail
echo"=== Read Button component implementation ==="
cat -n 'apps/web/src/features/ui/button/index.tsx'echo""echo"=== Read Button props ==="
cat -n 'apps/web/src/features/ui/button/props.ts'echo""echo"=== Check ai-generator page content (first 100 lines) ==="
cat -n 'apps/web/src/app/perks/ai-generator/_page.tsx'| head -100Repository: ecency/vision-next
Length of output: 6569
Remove <Link> wrapper; pass href directly to Button.
The Button component supports polymorphic rendering—when an href prop is provided, it renders as <Link> internally. Wrapping <Button> with <Link> without passing href creates nested interactive elements (<Link><button>), which is semantically incorrect and causes accessibility issues.
Change:
<Button
href="/perks"
size="sm"
appearance="gray-link"
icon={<UilArrowLeft />}
iconPlacement="left"
noPadding={true}
>
{i18next.t("g.back")}
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/perks/ai-generator/_page.tsx` around lines 13 - 23, Remove
the outer Link wrapper and instead pass href="/perks" directly to the Button
component to avoid nested interactive elements; update the Button invocation in
apps/web/src/app/perks/ai-generator/_page.tsx (the Button component that
currently has size="sm", appearance="gray-link", icon={<UilArrowLeft />},
iconPlacement="left", noPadding={true} and children i18next.t("g.back")) so it
receives an href prop and the surrounding Link is deleted.
Uh oh!
There was an error while loading. Please reload this page.
| const cost = useMemo(() => { | ||
| if (prices && prices.length > 0) return prices[0].cost; | ||
| return 150; | ||
| }, [prices]); |
There was a problem hiding this comment.
Use selected aspect ratio cost for balance checks
cost currently uses the first price entry, so balance validation can be wrong when the user picks a different ratio.
Proposed change
- const cost = useMemo(() => {- if (prices && prices.length > 0) return prices[0].cost;- return 150;- }, [prices]);+ const cost = useMemo(() => {+ if (selectedPrice) return selectedPrice.cost;+ if (prices && prices.length > 0) return prices[0].cost;+ return 150;+ }, [prices, selectedPrice]);Also applies to: 78-87
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`
around lines 73 - 76, The cost useMemo currently always reads prices[0].cost
which breaks balance/validation when the user chooses a different aspect ratio;
update the useMemo for the cost constant in ai-image-generator.tsx to look up
the price entry in prices that matches the currently selected aspect ratio
(e.g., match by selectedRatio or selectedAspectRatio state) and fall back to a
sensible default if no match exists, and apply the same fix to the other similar
useMemo/price-lookup occurrences around the 78-87 region so all balance checks
use the selected ratio's cost rather than prices[0].
| const handleDownload = useCallback(() => { | ||
| if (generatedUrl) { | ||
| window.open(generatedUrl, "_blank"); | ||
| } |
There was a problem hiding this comment.
Harden window.open against opener abuse
At Line 133, opening external URLs without noopener,noreferrer allows the opened page to access window.opener.
Proposed change
- window.open(generatedUrl, "_blank");+ window.open(generatedUrl, "_blank", "noopener,noreferrer");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`
around lines 131 - 134, The handleDownload callback opens external URLs with
window.open(generatedUrl, "_blank") which allows opener abuse; update
handleDownload (referencing the handleDownload function and generatedUrl
variable) to call window.open with noopener,noreferrer (e.g. pass
"noopener,noreferrer" in the third argument) and, for extra safety, null out the
opener on the returned window object (if non-null) so the opened page cannot
access window.opener.
| <motion.div | ||
| key={price.aspect_ratio} | ||
| initial={{ opacity: 0, y: 8 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| transition={{ delay: i * 0.03 }} | ||
| className={clsx( | ||
| "border px-3 py-2 rounded-lg cursor-pointer text-sm font-medium", | ||
| selectedPrice?.aspect_ratio === price.aspect_ratio | ||
| ? "border-blue-dark-sky bg-blue-dark-sky/10 text-blue-dark-sky" | ||
| : "border-[--border-color] hover:bg-gray-100 dark:hover:bg-gray-800" | ||
| )} | ||
| onClick={() => setSelectedPrice(price)} | ||
| > |
There was a problem hiding this comment.
Make aspect-ratio options keyboard accessible
At Line 240, interactive div elements are not keyboard-operable by default, which blocks selection for keyboard-only users.
Proposed change
- <motion.div+ <motion.button+ type="button"
key={price.aspect_ratio}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: i * 0.03 }}
className={clsx(
"border px-3 py-2 rounded-lg cursor-pointer text-sm font-medium",
selectedPrice?.aspect_ratio === price.aspect_ratio
? "border-blue-dark-sky bg-blue-dark-sky/10 text-blue-dark-sky"
: "border-[--border-color] hover:bg-gray-100 dark:hover:bg-gray-800"
)}
onClick={() => setSelectedPrice(price)}
>
{ASPECT_RATIO_LABELS[price.aspect_ratio] ?? price.aspect_ratio}
- </motion.div>+ </motion.button>📝 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.
| <motion.div | |
| key={price.aspect_ratio} | |
| initial={{opacity: 0,y: 8}} | |
| animate={{opacity: 1,y: 0}} | |
| transition={{delay: i*0.03}} | |
| className={clsx( | |
| "border px-3 py-2 rounded-lg cursor-pointer text-sm font-medium", | |
| selectedPrice?.aspect_ratio===price.aspect_ratio | |
| ? "border-blue-dark-sky bg-blue-dark-sky/10 text-blue-dark-sky" | |
| : "border-[--border-color] hover:bg-gray-100 dark:hover:bg-gray-800" | |
| )} | |
| onClick={()=>setSelectedPrice(price)} | |
| > | |
| <motion.button | |
| type="button" | |
| key={price.aspect_ratio} | |
| initial={{opacity: 0,y: 8}} | |
| animate={{opacity: 1,y: 0}} | |
| transition={{delay: i*0.03}} | |
| className={clsx( | |
| "border px-3 py-2 rounded-lg cursor-pointer text-sm font-medium", | |
| selectedPrice?.aspect_ratio===price.aspect_ratio | |
| ? "border-blue-dark-sky bg-blue-dark-sky/10 text-blue-dark-sky" | |
| : "border-[--border-color] hover:bg-gray-100 dark:hover:bg-gray-800" | |
| )} | |
| onClick={()=>setSelectedPrice(price)} | |
| > | |
| {ASPECT_RATIO_LABELS[price.aspect_ratio]??price.aspect_ratio} | |
| </motion.button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`
around lines 240 - 252, The interactive aspect-ratio option is rendered as a
motion.div (keyed by price.aspect_ratio) and is not keyboard accessible; change
each option to a semantic interactive element or add keyboard handlers so
setSelectedPrice(price) can be triggered via Enter/Space and the element is
focusable—specifically replace or augment the motion.div used for options with a
button (or add tabIndex={0}, role="button", onKeyDown handling Enter/Space, and
aria-pressed reflecting selectedPrice?.aspect_ratio === price.aspect_ratio) and
preserve the existing onClick and className styling so keyboard users can focus,
activate, and perceive selection state.
Uh oh!
There was an error while loading. Please reload this page.
| export interface GenerateImageParams { | ||
| prompt: string; | ||
| aspect_ratio?: string; | ||
| } |
There was a problem hiding this comment.
Allow per-call token override to avoid stale-auth submissions
The mutation always uses the token captured at hook initialization. Callers that refresh token right before mutate cannot pass that fresh token, so requests may still go out with stale credentials.
Proposed change
export interface GenerateImageParams {
prompt: string;
aspect_ratio?: string;
+ code?: string;
}
@@
- if (!accessToken) {+ const token = params.code ?? accessToken;+ if (!token) {
throw new Error(
"[SDK][AI][GenerateImage] – access token wasn't found"
);
}
@@
body: JSON.stringify({
- code: accessToken,+ code: token,
us: username,
prompt: params.prompt,
aspect_ratio: params.aspect_ratio ?? "1:1",Also applies to: 23-27, 37-39
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/sdk/src/modules/ai/mutations/use-generate-image.ts` around lines 5 -
8, The hook is capturing the auth token at initialization and never letting
callers override it; add an optional per-call token override to the params
(e.g., extend GenerateImageParams with an optional authToken?: string or token?:
string) and update the mutate function inside useGenerateImage to prefer the
passed-in token when present (fall back to the captured token otherwise). Change
the request code that currently reads the closure token (search for the
mutate/execute function in use-generate-image.ts and any occurrences at the
ranges noted) to use params.authToken (or token) if provided, and update other
similar hooks/mutations referenced (lines ~23-27 and ~37-39) to accept and honor
the same per-call token override.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (3)
apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx (3)
214-228:⚠️ Potential issue | 🟠 MajorMake aspect-ratio options keyboard accessible.
Interactive
divelements are not keyboard-operable by default. Replacemotion.divwithmotion.buttonto ensure keyboard users can select aspect ratios.Suggested fix
- <motion.div+ <motion.button+ type="button" key={price.aspect_ratio} initial={{ opacity: 0, y: 8 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: i * 0.03 }} className={clsx( "border px-3 py-2 rounded-lg cursor-pointer text-sm font-medium", selectedPrice?.aspect_ratio === price.aspect_ratio ? "border-blue-dark-sky bg-blue-dark-sky/10 text-blue-dark-sky" : "border-[--border-color] hover:bg-gray-100 dark:hover:bg-gray-800" )} onClick={() => setSelectedPrice(price)} > {ASPECT_RATIO_LABELS[price.aspect_ratio] ?? price.aspect_ratio} - </motion.div>+ </motion.button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx` around lines 214 - 228, Replace the non-interactive motion.div used for aspect ratio options with a semantic interactive element: change motion.div to motion.button, add type="button" to avoid form submit behavior, keep the existing onClick={() => setSelectedPrice(price)} and className logic, and add aria-pressed={selectedPrice?.aspect_ratio === price.aspect_ratio} so keyboard and assistive-tech users get proper state feedback; the visible label from ASPECT_RATIO_LABELS[price.aspect_ratio] and usage of price.aspect_ratio should remain unchanged.
67-77:⚠️ Potential issue | 🟠 MajorCost calculation doesn't reflect selected aspect ratio.
The
costcalculation always usesprices[0].costregardless of which aspect ratio the user selects. This causes incorrect balance validation when different aspect ratios have different prices.Suggested fix
const cost = useMemo(() => { + if (selectedPrice) return selectedPrice.cost; if (prices && prices.length > 0) return prices[0].cost; return 150; -}, [prices]);+}, [prices, selectedPrice]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx` around lines 67 - 77, The cost useMemo currently always returns prices[0].cost which ignores the user's selection; update the cost calculation in the useMemo (the cost variable) to pick the price that corresponds to the currently selected aspect ratio/size state (e.g., selectedAspectRatio, selectedSize, or selectedSizeIndex used elsewhere in this component) instead of prices[0], and add that selected state to the useMemo dependency array; ensure isInsufficientBalance continues to compare +activeUserPoints.points against the updated cost so balance validation reflects the selected aspect ratio price.
127-131:⚠️ Potential issue | 🟡 MinorHarden
window.openagainst opener abuse.Opening external URLs without
noopener,noreferrerallows the opened page to accesswindow.opener, which is a security concern.Suggested fix
const handleDownload = useCallback(() => { if (generatedUrl) { - window.open(generatedUrl, "_blank");+ window.open(generatedUrl, "_blank", "noopener,noreferrer"); } }, [generatedUrl]);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx` around lines 127 - 131, The handleDownload function currently calls window.open(generatedUrl, "_blank") which leaves window.opener accessible; change its implementation in handleDownload to open the URL with opener protection by passing the noopener,noreferrer feature string or by nulling the opener on the returned window (e.g., call window.open(generatedUrl, "_blank", "noopener,noreferrer") or assign newWin.opener = null after window.open returns) so that generatedUrl cannot access the opener.
🧹 Nitpick comments (2)
packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts (1)
2-3: Consolidate imports from the same module.Both lines import from
../../core. Consider merging them for cleaner code.Proposed fix
-import { CONFIG, getBoundFetch } from "../../core";-import { QueryKeys } from "../../core";+import { CONFIG, getBoundFetch, QueryKeys } from "../../core";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts` around lines 2 - 3, The two imports from "../../core" should be consolidated into a single import statement; replace the separate imports that pull CONFIG and getBoundFetch and the one that pulls QueryKeys with one combined import that includes CONFIG, getBoundFetch, and QueryKeys to clean up get-ai-generate-price-query-options.ts.apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx (1)
100-101: Consider logging gallery save failures for debugging.The gallery save error is silently swallowed. While non-blocking behavior is appropriate, consider at minimum logging the error for debugging purposes.
Suggested improvement
// Auto-add to user's gallery (non-blocking) - addToGallery({ url: result.url, code: token }).catch(() => {});+ addToGallery({ url: result.url, code: token }).catch((err) => {+ console.warn("Failed to auto-save generated image to gallery:", err);+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx` around lines 100 - 101, The call to addToGallery({ url: result.url, code: token }) swallows errors; change the non-blocking save to catch and log failures so they aren’t silent—append .catch(err => { console.error('Failed to save image to gallery', err, { url: result.url, code: token }); }) or use the app's existing logger (e.g., appLogger.error) to record the error and context; keep the operation non-blocking but include the catch and structured log entry referencing addToGallery, result.url and token.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts`:
- Line 8: The queryKey used in get-ai-generate-price-query-options.ts currently
calls QueryKeys.ai.prices() which returns ["ai","prices"] and lacks any
accessToken, causing cache collisions when the token changes; update the
queryKey to include the current accessToken (or a stable derived identifier like
userId or tokenHash) — e.g., call QueryKeys.ai.prices(accessToken) or build
["ai","prices", accessTokenOrId] — and if QueryKeys.ai.prices doesn't accept a
parameter, update the QueryKeys.ai.prices factory in query-keys.ts to accept a
token/id parameter and use that here so the React Query cache is isolated per
token.
---
Duplicate comments:
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`:
- Around line 214-228: Replace the non-interactive motion.div used for aspect
ratio options with a semantic interactive element: change motion.div to
motion.button, add type="button" to avoid form submit behavior, keep the
existing onClick={() => setSelectedPrice(price)} and className logic, and add
aria-pressed={selectedPrice?.aspect_ratio === price.aspect_ratio} so keyboard
and assistive-tech users get proper state feedback; the visible label from
ASPECT_RATIO_LABELS[price.aspect_ratio] and usage of price.aspect_ratio should
remain unchanged.
- Around line 67-77: The cost useMemo currently always returns prices[0].cost
which ignores the user's selection; update the cost calculation in the useMemo
(the cost variable) to pick the price that corresponds to the currently selected
aspect ratio/size state (e.g., selectedAspectRatio, selectedSize, or
selectedSizeIndex used elsewhere in this component) instead of prices[0], and
add that selected state to the useMemo dependency array; ensure
isInsufficientBalance continues to compare +activeUserPoints.points against the
updated cost so balance validation reflects the selected aspect ratio price.
- Around line 127-131: The handleDownload function currently calls
window.open(generatedUrl, "_blank") which leaves window.opener accessible;
change its implementation in handleDownload to open the URL with opener
protection by passing the noopener,noreferrer feature string or by nulling the
opener on the returned window (e.g., call window.open(generatedUrl, "_blank",
"noopener,noreferrer") or assign newWin.opener = null after window.open returns)
so that generatedUrl cannot access the opener.
---
Nitpick comments:
In `@apps/web/src/features/shared/ai-image-generator/ai-image-generator.tsx`:
- Around line 100-101: The call to addToGallery({ url: result.url, code: token
}) swallows errors; change the non-blocking save to catch and log failures so
they aren’t silent—append .catch(err => { console.error('Failed to save image to
gallery', err, { url: result.url, code: token }); }) or use the app's existing
logger (e.g., appLogger.error) to record the error and context; keep the
operation non-blocking but include the catch and structured log entry
referencing addToGallery, result.url and token.
In `@packages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts`:
- Around line 2-3: The two imports from "../../core" should be consolidated into
a single import statement; replace the separate imports that pull CONFIG and
getBoundFetch and the one that pulls QueryKeys with one combined import that
includes CONFIG, getBoundFetch, and QueryKeys to clean up
get-ai-generate-price-query-options.ts.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (6)
packages/sdk/dist/browser/index.jsis excluded by!**/dist/**packages/sdk/dist/browser/index.js.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.cjsis excluded by!**/dist/**packages/sdk/dist/node/index.cjs.mapis excluded by!**/dist/**,!**/*.mappackages/sdk/dist/node/index.mjsis excluded by!**/dist/**packages/sdk/dist/node/index.mjs.mapis excluded by!**/dist/**,!**/*.map
📒 Files selected for processing (6)
apps/web/src/app/perks/ai-generator/_page.tsxapps/web/src/app/publish/_components/publish-editor-toolbar.tsxapps/web/src/features/i18n/locales/en-US.jsonapps/web/src/features/shared/ai-image-generator/ai-image-generator.tsxapps/web/src/features/waves/components/wave-form/wave-form-toolbar.tsxpackages/sdk/src/modules/ai/queries/get-ai-generate-price-query-options.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/web/src/app/perks/ai-generator/_page.tsx
Uh oh!
There was an error while loading. Please reload this page.
Summary by CodeRabbit