Skip to content

Conversation

@Aunshon
Copy link
Collaborator

@Aunshon Aunshon commented Dec 18, 2025

…n and frontend components

Summary by CodeRabbit

  • New Features

    • AI-powered product enhancement in media library (image + optional video generation)
    • Try-on experience on product pages allowing customers to preview with their own images/camera
    • WooCommerce per-product try-on toggle and admin product list controls
    • Admin dashboard with settings UI for API key and plugin management
  • Refactor

    • Restructured asset and component architecture for modular frontend/admin bundles
  • Chores

    • Bumped plugin version to 1.0.0; added linting, formatting, and CI tooling configs

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 18, 2025

📝 Walkthrough

Walkthrough

This PR is a major v0.0.1 → v1.0.0 release introducing a React/TypeScript admin dashboard, modular AI Enhancer and Try‑On features, new REST endpoints for image generation, Tailwind-based styling and asset refactor, WooCommerce per-product toggle, and build/tooling/configuration updates.

Changes

Cohort / File(s) Summary
Core & Bootstrap
try-aura.php, inc/Plugin.php, inc/Assets.php
Version bump to 1.0.0; plugin constants and $version added; Plugin now wires Assets, Settings, GenerateController, WooCommerce. New Assets class centralizes script/style registration and translations.
Admin Entry & Settings
inc/Admin.php, src/admin/dashboard/*, src/admin/dashboard/pages/Settings.tsx
Admin menu/dashboard reworked to a React Router-based dashboard (Dashboard, Settings). Settings page persists API key via REST; admin menu hooks and page render adjusted.
Admin Enhancer UI
inc/Enhancer.php, src/admin/enhancer/*, src/components/*
New "Enhance with AI" button injected into WP media modal; modular PreviewModal and subcomponents (ConfigSettings, Output, OriginalImage, Image/Video inputs, ConfigFooter) implemented; shared UI components (Button, GroupButton, ModernSelect) added.
Frontend Try‑On
inc/TryOn.php, src/frontend/tryon/*, src/frontend/tryon/TryOnModal.tsx
Try-On button injected on product pages; TryOnModal supports upload/camera, product image selection, generation flow and add-to-cart; login redirect support and frontend data localization updated.
WooCommerce Integration
inc/WooCommerce.php, templates/products/tryon-switch.php, src/admin/woocommerce-products-list/*
New WooCommerce integration class with TRY_ON_META_KEY, product list column, per-product toggle template and AJAX handler; admin list JS handles toggle requests.
REST API
inc/Rest/GenerateController.php, inc/Rest/SettingsController.php
New GenerateController POST /generate/v1/image that proxies requests to Google GenAI (image modality); SettingsController adjusted (signature/style) to expose/save plugin settings.
Assets & Build
webpack.config.js, package.json, postcss.config.js, src/base-tailwind.scss, src/**/style.scss
Webpack entries extended (components, admin/dashboard, admin/enhancer, frontend/tryon, products list); package.json bumped, new scripts and deps (Tailwind, lucide-react, react-router-dom, WP packages); PostCSS + Tailwind base added; per-area styles import base-tailwind.
Tooling & Linting
.editorconfig, .eslintrc, .prettierrc.js, .stylelintrc, phpcs.xml, composer.json, bin/version-replace.js
Editor and lint configs added/adjusted; phpcs ruleset and composer scripts added; version-replace script syncs package.json version into plugin files.
Removed / Relocated
src/enhancer.tsx (deleted), tsconfig.json (deleted)
Monolithic enhancer.tsx removed in favor of modular enhancer/*; tsconfig removed (rely on @wordpress/scripts config).
Utilities & Router
src/utils/router.ts, src/admin/dashboard/utils/menu-fix.js
withRouter HOC and menuFix utility added for admin routing and menu highlighting.
i18n & Languages
languages/try-aura.pot
POT template added containing plugin strings and references.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MediaModal as WP Media Modal
    participant EnhanceBtn as Enhance Button
    participant PreviewModal as Preview Modal
    participant REST as REST API (/generate/v1/image)
    participant GoogleAI as Google GenAI

    User->>MediaModal: Open media uploader
    MediaModal->>EnhanceBtn: Toolbar available (injected)
    User->>EnhanceBtn: Click "Enhance with AI"
    EnhanceBtn->>EnhanceBtn: Read selected image URLs & IDs
    EnhanceBtn->>PreviewModal: Mount modal with images
    User->>PreviewModal: Configure options & request generation
    PreviewModal->>PreviewModal: Encode images, build prompt
    PreviewModal->>REST: POST (prompt, images)
    REST->>GoogleAI: Call Generative API (image modality)
    GoogleAI-->>REST: Return generated image data
    REST-->>PreviewModal: Return generated image
    PreviewModal->>PreviewModal: Parse & display result
    User->>PreviewModal: Add to Media
    PreviewModal->>REST: Upload to media library endpoint
    REST-->>MediaModal: Update selection (media frame)
    PreviewModal->>User: Close modal
Loading
sequenceDiagram
    participant User
    participant ProductPage as WooCommerce Product Page
    participant TryOnBtn as Try‑On Button
    participant TryOnModal as Try‑On Modal
    participant REST as REST API (/generate/v1/image)
    participant GoogleAI as Google GenAI

    User->>ProductPage: Load product page
    ProductPage->>TryOnBtn: Inject "Try On" button
    User->>TryOnBtn: Click "Try On"
    TryOnBtn->>TryOnBtn: Check login state
    alt Not logged in
        TryOnBtn->>User: Redirect to login (with return+auto-open)
    else Logged in
        TryOnBtn->>TryOnModal: Open modal (product images)
        User->>TryOnModal: Upload/capture user image + select product images
        User->>TryOnModal: Click "Try On"
        TryOnModal->>TryOnModal: Convert images to data URLs, build prompt
        TryOnModal->>REST: POST generation request
        REST->>GoogleAI: Call Generative API
        GoogleAI-->>REST: Return image
        REST-->>TryOnModal: Display result
        User->>TryOnModal: Add to cart / close
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A tiny hop, a big new dawn,
From dot-oh-one to one-oh-one drawn.
Buttons, modals, AI that dreams,
Tailwind stitches, webpack gleams.
I nibble bugs and seed the streams!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.92% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The PR title is truncated and only partially visible ('Refactor file structure and enhance Tailwind CSS integration for admi…'), making it difficult to fully assess its clarity and specificity. Provide the complete, untruncated PR title to properly evaluate whether it clearly summarizes the main changes and is specific enough for teammates scanning history.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
inc/Enhancer.php (1)

45-53: try-aura-components dependency may be lost when asset file exists.

When the asset file exists, line 47 overwrites $deps with the asset's dependencies, then only ensures media-views is present. The try-aura-components dependency set on line 42 would be lost.

🔎 Apply this diff to preserve the components dependency:
 		if ( file_exists( $asset_file ) ) {
 			$asset   = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
 			$deps    = $asset['dependencies'] ?? $deps;
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
+			// Ensure try-aura-components is present.
+			if ( ! in_array( 'try-aura-components', $deps, true ) ) {
+				$deps[] = 'try-aura-components';
+			}
 			$version = $asset['version'] ?? $version;
 		}
🧹 Nitpick comments (6)
src/components/ModernSelect.tsx (1)

58-64: Remove duplicate rounded-[5px] class

Line 59 contains rounded-[5px] twice in the className string.

🔎 Apply this diff to remove the duplicate:
           <div
-              className="bg-white border border-[#E9E9E9] rounded-[5px] rounded-[5px] shadow flex flex-row flex-wrap"
+              className="bg-white border border-[#E9E9E9] rounded-[5px] shadow flex flex-row flex-wrap"
               style={ {
                 width: contentRef?.current?.offsetWidth ?? 'auto',
                 padding: '8px'
inc/Admin.php (1)

83-89: Minor simplification possible.

The CSS registration and enqueueing work correctly. However, you could simplify by using wp_enqueue_style directly with all parameters instead of separate register/enqueue calls.

🔎 Optional simplification:
         // Enqueue compiled Tailwind CSS if available.
         $css_path = plugin_dir_path( __DIR__ ) . 'build/admin/settings/style-index.css';
         if ( file_exists( $css_path ) ) {
             $css_url = plugin_dir_url( __DIR__ ) . 'build/admin/settings/style-index.css';
-            wp_register_style( 'try-aura-admin', $css_url, [], filemtime( $css_path ) );
-            wp_enqueue_style( 'try-aura-admin' );
+            wp_enqueue_style( 'try-aura-admin', $css_url, [], filemtime( $css_path ) );
         }
src/components/Button.tsx (1)

14-14: The color prop is unused.

The color prop is defined and destructured but never used to influence the styling logic. Either implement color variants or remove the prop to avoid confusion.

src/components/GroupButton.tsx (1)

16-24: Consider using const and adding explicit return.

The variables classNames and normalButtonClass are never reassigned, so they should be const. Also, explicit return improves readability for multi-line JSX.

🔎 Cleaner approach:
-        options.map( (option, index) => {
-          return <button
+        options.map( (option, index) => (
+          <button
             className={ twMerge( value === option?.value ? classNames : normalButtonClass, index === 0 ? 'rounded-l-[5px]' : '', index === options.length - 1 ? 'rounded-r-[5px]' : '', option?.disabled ? 'opacity-50 cursor-not-allowed' : '' ) }
             onClick={ () => onClick( option?.value ) }
             key={ option?.value }
             disabled={option?.disabled ?? false}
           >
             {option?.label ?? ''}
           </button>
-        } )
+        ))
inc/Enhancer.php (1)

59-72: Same simplification opportunity as Admin.php.

The register + enqueue pattern could be simplified to single wp_enqueue_style calls.

package.json (1)

27-34: Consider moving build-time dependencies to devDependencies.

tailwindcss, @tailwindcss/postcss, and postcss are build-time tools used only during compilation. They don't need to be bundled or available at runtime. Moving them to devDependencies reduces production install size.

🔎 Suggested change:
   "devDependencies": {
     "@wordpress/scripts": "^31.1.0",
+    "@tailwindcss/postcss": "^4.1.18",
+    "postcss": "^8.5.6",
+    "tailwindcss": "^4.1.18",
     "typescript": "^5.6.2"
   },
   ...
   "dependencies": {
     "@google/genai": "^0.3.1",
-    "@tailwindcss/postcss": "^4.1.18",
     "@wordpress/api-fetch": "^7.36.0",
     "@wordpress/components": "^30.9.0",
     "@wordpress/element": "^6.36.0",
     "lucide-react": "^0.561.0",
-    "postcss": "^8.5.6",
-    "tailwind-merge": "^3.4.0",
-    "tailwindcss": "^4.1.18"
+    "tailwind-merge": "^3.4.0"
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca000c1 and 84e02e1.

📒 Files selected for processing (22)
  • inc/Admin.php (3 hunks)
  • inc/Enhancer.php (3 hunks)
  • inc/TryOn.php (2 hunks)
  • package.json (1 hunks)
  • postcss.config.js (1 hunks)
  • src/admin/enhancer/PreviewModal.tsx (1 hunks)
  • src/admin/enhancer/index.tsx (1 hunks)
  • src/admin/enhancer/style.scss (1 hunks)
  • src/admin/settings/index.tsx (1 hunks)
  • src/admin/settings/style.scss (1 hunks)
  • src/base-tailwind.scss (1 hunks)
  • src/components/Button.tsx (1 hunks)
  • src/components/GroupButton.tsx (1 hunks)
  • src/components/ModernSelect.tsx (1 hunks)
  • src/components/index.tsx (1 hunks)
  • src/components/style.scss (1 hunks)
  • src/enhancer.tsx (0 hunks)
  • src/frontend/tryon/index.tsx (1 hunks)
  • src/frontend/tryon/style.scss (1 hunks)
  • try-aura.php (1 hunks)
  • tsconfig.json (0 hunks)
  • webpack.config.js (1 hunks)
💤 Files with no reviewable changes (2)
  • tsconfig.json
  • src/enhancer.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/components/Button.tsx (1)
src/components/index.tsx (2)
  • ButtonProps (10-10)
  • Button (7-7)
src/components/GroupButton.tsx (1)
src/components/index.tsx (1)
  • GroupButton (9-9)
src/components/ModernSelect.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
🔇 Additional comments (20)
try-aura.php (1)

5-5: LGTM! Version bump to 1.0.0

The version update aligns with the major refactoring and feature additions in this PR.

inc/TryOn.php (1)

25-48: LGTM! Asset path updates and CSS enqueue

The reorganized asset paths align with the new modular build structure, and the conditional CSS enqueue with filemtime() versioning provides proper cache busting.

src/frontend/tryon/index.tsx (1)

3-3: LGTM! Stylesheet integration

The stylesheet import integrates Tailwind CSS styling for the Try-On frontend module, consistent with the broader styling refactor.

src/admin/settings/style.scss (1)

1-3: LGTM! Tailwind integration for admin settings

The stylesheet correctly imports the base Tailwind configuration and uses the @source directive for content detection, following the same pattern used across other modules.

src/admin/settings/index.tsx (1)

5-5: LGTM! Stylesheet integration

The stylesheet import wires Tailwind CSS styling for the admin settings module, consistent with the broader styling refactor.

src/frontend/tryon/style.scss (2)

1-2: LGTM! Tailwind integration for frontend Try-On

The stylesheet follows the standard pattern for Tailwind CSS integration across the project.


1-2: The libraries are already using current, secure versions. PostCSS 8.5.6 is the latest version, and Tailwind CSS v4 was released as stable on January 22, 2025. Version 8.5.6 includes the security fix for CVE-2023-44270 (patched in version 8.4.31 and later). No further action needed for version updates.

src/components/style.scss (1)

1-2: LGTM! Tailwind integration for components

The stylesheet correctly imports the base Tailwind configuration with the appropriate relative path for the components directory location.

src/components/ModernSelect.tsx (1)

1-86: Removed: Version verification request is not applicable to this code snippet

The provided code shows only import statements without version specifications. Version management occurs in package.json or lock files, which were not included in this review. The latest stable version of @wordpress/components is 30.9.0 and the latest stable version of lucide-react is 0.561.0. If you need to verify specific versions pinned in your project's dependency files, provide the package.json or lock file for review.

Likely an incorrect or invalid review comment.

src/admin/enhancer/style.scss (1)

1-2: LGTM!

The Tailwind v4 setup is correct. The @source directive properly specifies content sources for utility class detection, and the base-tailwind import path is accurate relative to this file's location.

postcss.config.js (1)

1-6: LGTM!

The PostCSS configuration correctly uses CommonJS format for @wordpress/scripts compatibility and properly registers the @tailwindcss/postcss plugin for Tailwind v4 integration.

src/base-tailwind.scss (1)

11-13: LGTM!

The @theme directive correctly defines a custom --color-primary CSS variable, making it available as a Tailwind color token (e.g., bg-primary, text-primary).

src/components/index.tsx (1)

1-10: LGTM!

The barrel export pattern is well-structured. Named exports enable tree-shaking, and the type export for ButtonProps follows TypeScript best practices.

inc/Admin.php (1)

113-113: LGTM!

Adding the tryaura class correctly enables the scoped Tailwind styles from base-tailwind.scss.

src/admin/enhancer/index.tsx (1)

23-56: LGTM!

The EnhanceButton component handles WordPress media frame selection correctly with appropriate error handling and loading states. The try-catch with window.alert fallback is reasonable for user feedback in this context.

webpack.config.js (1)

9-21: LGTM!

The webpack configuration cleanly extends the default @wordpress/scripts config by spreading defaultConfig.entry and adding new module entries. The library export pattern for components (window.tryaura.components) is appropriate for WordPress integration.

package.json (1)

3-3: Verify readiness for 1.0.0 release.

Jumping from 0.0.1 to 1.0.0 signals production readiness. Ensure this aligns with the project's release strategy, especially given the test script on Line 14 is a placeholder (echo "Error: no test specified").

src/admin/enhancer/PreviewModal.tsx (3)

18-36: LGTM on API key resolution.

The resolveApiKey function properly checks for a client-side key first, then falls back to fetching from the REST API with appropriate nonce authentication. Error handling returns null gracefully.


56-86: LGTM on cleanup effects.

Good memory management with proper blob URL revocation on both image change (Lines 70-72) and unmount (Lines 81-82). The body class toggle for modal state is also correctly cleaned up.


127-130: The generateContent API and gemini-2.5-flash-image-preview model name are correct per current @google/genai SDK documentation. Consider removing unnecessary type assertions (as any) to improve type safety.

Comment on lines 306 to 323
for (let i = 0; i < 60; i++) { // up to ~10 minutes
await new Promise((r) => setTimeout(r, 10000));
const statusRes = await fetch(`${BASE_URL}/${operationName}`, {
headers: { 'x-goog-api-key': apiKey },
});
if (!statusRes.ok) {
const t = await statusRes.text();
throw new Error(t || 'Failed to check video generation status.');
}
const json = await statusRes.json();
if (json?.done) {
finalJson = json;
break;
}
}
if (!finalJson) {
throw new Error('Timed out waiting for video generation.');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Polling loop lacks cancellation; can cause state updates after unmount.

The polling loop (up to 10 minutes) continues even if the user closes the modal. This causes orphan network requests and potential "state update on unmounted component" warnings. Consider using an AbortController or a ref-based cancellation flag.

🔎 Suggested approach using AbortController:
+  const abortControllerRef = useRef<AbortController | null>(null);
+
+  useEffect(() => {
+    return () => {
+      // Cancel any in-flight video generation on unmount
+      abortControllerRef.current?.abort();
+    };
+  }, []);

   const doGenerateVideo = async () => {
     try {
+      abortControllerRef.current?.abort();
+      abortControllerRef.current = new AbortController();
+      const signal = abortControllerRef.current.signal;
       // ... existing setup code ...

       for (let i = 0; i < 60; i++) {
+        if (signal.aborted) return;
         await new Promise((r) => setTimeout(r, 10000));
-        const statusRes = await fetch(`${BASE_URL}/${operationName}`, {
+        const statusRes = await fetch(`${BASE_URL}/${operationName}`, {
           headers: { 'x-goog-api-key': apiKey },
+          signal,
         });
         // ... rest of loop ...
       }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +4 to +9
.tryaura {
@import "tailwindcss/utilities.css" layer(utilities) source(none) important;
@layer base {
@import "tailwindcss/preflight.css";
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

The important parameter on the @import directive is not valid in Tailwind v4.

Nested @import within selectors is supported in Tailwind v4 for scoping styles. However, important is not a recognized parameter for @import directives. Use source(none) to disable automatic source detection, but remove the important parameter as it has no effect and may cause build issues. In Tailwind v4, apply !important significance at the utility level using the ! suffix on individual class names instead.

🤖 Prompt for AI Agents
In src/base-tailwind.scss around lines 4 to 9 the @import directive uses an
unsupported "important" parameter; remove "important" from the @import and keep
source(none) for disabling automatic source detection, leaving the @import as
valid Tailwind v4 syntax and, if needed, apply !important via utility-level !
suffixes in component classes instead of the @import parameter.

}, ref
){

let classNames = "rounded-[5px] bg-primary px-3 py-2 text-[14px] text-white hover:bg-bg-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 cursor-pointer";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Likely typo: hover:bg-bg-primary.

The class hover:bg-bg-primary appears to be a typo. Should this be hover:bg-primary/90 or another valid hover state?

🤖 Prompt for AI Agents
In src/components/Button.tsx around line 22, the tailwind class string contains
a typo "hover:bg-bg-primary"; replace it with an appropriate valid hover utility
such as "hover:bg-primary/90" (or another project-consistent hover variant) so
the hover background color works as intended, and keep the rest of the class
list unchanged.

if (variant === 'outline') {
classNames = "rounded-[5px] bg-white px-3 py-2 text-[14px] text-gray-900 inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer"
}
const Tag = type === 'link' ? 'a' : 'button';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type inconsistency: type prop check won't work as intended.

The type prop is typed as a button's type attribute (e.g., 'button', 'submit', 'reset'), but the logic checks for 'link' which isn't a valid button type. Consider using a separate as prop for element selection.

🔎 Suggested approach:
 export interface ButtonProps extends Omit<JSX.IntrinsicElements['button'], 'color'> {
   variant?: Variant;
   color?: Color;
+  as?: 'button' | 'a';
 }
 const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
   {
     variant = 'solid',
     color = 'primary',
     className,
     children,
     type = 'button',
+    as = 'button',
     ...rest
   }, ref
 ){
   // ...
-  const Tag = type === 'link' ? 'a' : 'button';
+  const Tag = as;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/Button.tsx around line 27, the component is using the prop
named `type` to decide whether to render an 'a' or 'button' element but `type`
is currently the native button `type` attribute (e.g.,
'button'|'submit'|'reset'), so the check for 'link' is incorrect; add a separate
prop (e.g., `as` or `variant`) for element selection, update the component
props/type definitions to include that new prop while keeping `type` for button
semantics, change the Tag selection to use the new prop (e.g., Tag = as === 'a'
? 'a' : 'button'), ensure when rendering you only pass valid attributes to each
element (do not pass native button `type` to an anchor and vice versa), and set
sensible defaults and prop typings so TypeScript enforces correct usage.

Comment on lines 27 to 36
const Tag = type === 'link' ? 'a' : 'button';

return (
<Tag
className={ twMerge(classNames, className) }
{...rest}
>
{children ?? ''}
</Tag>
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: ref is not forwarded to the element.

The component uses forwardRef but the ref is never passed to the rendered element, breaking the forwarding functionality.

🔎 Apply this diff to fix ref forwarding:
   return (
     <Tag
       className={ twMerge(classNames, className) }
+      ref={ref}
       {...rest}
     >
       {children ?? ''}
     </Tag>
   );
📝 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.

Suggested change
const Tag = type === 'link' ? 'a' : 'button';
return (
<Tag
className={ twMerge(classNames, className) }
{...rest}
>
{children ?? ''}
</Tag>
);
const Tag = type === 'link' ? 'a' : 'button';
return (
<Tag
className={ twMerge(classNames, className) }
ref={ref}
{...rest}
>
{children ?? ''}
</Tag>
);
🤖 Prompt for AI Agents
In src/components/Button.tsx around lines 27 to 36, the component renders Tag
but never forwards the ref; update the forwardRef signature to accept a ref
parameter with the correct element union type (HTMLButtonElement |
HTMLAnchorElement or HTMLElement) and pass that ref into the rendered Tag as
ref={ref}; also ensure the component props typing matches (e.g., extends
React.ButtonHTMLAttributes<HTMLButtonElement> |
React.AnchorHTMLAttributes<HTMLAnchorElement> or use
React.HTMLAttributes<HTMLElement>) so the ref type aligns with the rendered
element.

Comment on lines 10 to 11
let classNames = "bg-black px-3 py-2 text-[14px] text-white hover:bg-bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 cursor-pointer";
let normalButtonCLass = "bg-white px-3 py-2 text-[14px] text-gray-900 inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Likely typo and naming inconsistency.

  1. hover:bg-bg-black appears to be a typo (similar to Button.tsx)
  2. Variable normalButtonCLass has inconsistent casing - should be normalButtonClass
🔎 Apply this diff:
-  let classNames = "bg-black px-3 py-2 text-[14px] text-white hover:bg-bg-black focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 cursor-pointer";
-  let normalButtonCLass = "bg-white px-3 py-2 text-[14px] text-gray-900 inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer";
+  let classNames = "bg-black px-3 py-2 text-[14px] text-white hover:bg-black/90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 cursor-pointer";
+  let normalButtonClass = "bg-white px-3 py-2 text-[14px] text-gray-900 inset-ring inset-ring-gray-300 hover:bg-gray-50 cursor-pointer";

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/components/GroupButton.tsx around lines 10 to 11, fix the typo and naming
inconsistency: change the class string `hover:bg-bg-black` to `hover:bg-black`,
rename the variable `normalButtonCLass` to `normalButtonClass`, and update any
other references in this file to use the corrected variable name to preserve
consistent casing.

Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
phpcs.xml (1)

14-16: Consider removing the redundant WordPress ruleset.

The WordPress-Extra ruleset already includes all rules from the WordPress ruleset, making line 16 redundant.

🔎 Suggested simplification
 <!-- Use WordPress-Extra ruleset which includes all WordPress rules -->
 <rule ref="WordPress-Extra"/>
-<rule ref="WordPress"/>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84e02e1 and 8fece71.

📒 Files selected for processing (8)
  • .editorconfig (1 hunks)
  • .eslintrc (1 hunks)
  • .prettierrc.js (1 hunks)
  • .stylelintrc (1 hunks)
  • composer.json (1 hunks)
  • package.json (1 hunks)
  • phpcs.xml (1 hunks)
  • src/admin/enhancer/index.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • .eslintrc
  • .editorconfig
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/admin/enhancer/index.tsx
  • package.json
🔇 Additional comments (2)
composer.json (1)

11-43: LGTM! Configuration is well-structured and consistent.

The composer.json updates are properly aligned with the phpcs.xml configuration:

  • PHP version requirement (≥7.4) matches the testVersion in phpcs.xml
  • Script targets (inc, templates, try-aura.php) match the files specified in phpcs.xml
  • Dev dependencies include standard WordPress coding standards packages
.prettierrc.js (1)

1-5: LGTM! Clean Prettier configuration.

The configuration correctly imports and re-exports the WordPress Prettier defaults using the standard pattern documented in WordPress guidelines. The implementation is clean and minimal.

@@ -0,0 +1,3 @@
{
"extends": [ "plugin:@wordpress/eslint-plugin/recommended" ]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace ESLint plugin reference with WordPress stylelint configuration.

The extends field references plugin:@wordpress/eslint-plugin/recommended, which is an ESLint plugin syntax. For stylelint, use @wordpress/stylelint-config or @wordpress/stylelint-config/scss instead.

🤖 Prompt for AI Agents
In .stylelintrc around line 2, the extends entry currently uses an ESLint plugin
reference ("plugin:@wordpress/eslint-plugin/recommended") which is invalid for
stylelint; replace it with the WordPress stylelint config — e.g. set the extends
value to "@wordpress/stylelint-config" or "@wordpress/stylelint-config/scss" (if
you use SCSS), keeping it inside the array as before so stylelint can load the
correct configuration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fece71 and 8399a38.

📒 Files selected for processing (1)
  • src/admin/enhancer/index.tsx (1 hunks)
🔇 Additional comments (5)
src/admin/enhancer/index.tsx (5)

25-31: LGTM!

State and ref declarations are appropriate for managing the modal lifecycle and selection data.


33-67: LGTM!

The selection handling is defensive with proper optional chaining. The finally block correctly ensures setLoading(false) runs even after early returns.


70-121: LGTM!

The modal rendering and cleanup logic properly addresses the previous memory leak concerns. Mounting to document.body avoids toolbar overflow issues, and the dual useEffect pattern ensures cleanup on both state changes and component unmount.


136-166: LGTM!

The timestamp-keyed root registry pattern enables proper cleanup when the modal closes. This addresses the previous memory leak concern by tracking each React root instance.


168-217: Good approach using modal lifecycle hooks.

Overriding wp.media.view.Modal.prototype.open/close is cleaner and more performant than the previous MutationObserver approach. This properly hooks into the WordPress modal lifecycle for both injection and cleanup.

Comment on lines +14 to +23
declare global {
interface Window {
// eslint-disable-line @typescript-eslint/consistent-type-definitions
tryAura?: {
apiKey?: string;
restUrl?: string;
nonce?: string;
};
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Missing TypeScript declaration for tryAuraMediaRoots.

The code uses window.tryAuraMediaRoots (lines 159-162, 206, 211) but the Window interface only declares tryAura. This will cause TypeScript compilation errors.

🔎 Proposed fix
 declare global {
 	interface Window {
 		// eslint-disable-line @typescript-eslint/consistent-type-definitions
 		tryAura?: {
 			apiKey?: string;
 			restUrl?: string;
 			nonce?: string;
 		};
+		tryAuraMediaRoots?: Record< number, any >;
 	}
 }
📝 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.

Suggested change
declare global {
interface Window {
// eslint-disable-line @typescript-eslint/consistent-type-definitions
tryAura?: {
apiKey?: string;
restUrl?: string;
nonce?: string;
};
}
}
declare global {
interface Window {
// eslint-disable-line @typescript-eslint/consistent-type-definitions
tryAura?: {
apiKey?: string;
restUrl?: string;
nonce?: string;
};
tryAuraMediaRoots?: Record< number, any >;
}
}
🤖 Prompt for AI Agents
In src/admin/enhancer/index.tsx around lines 14 to 23, the global Window
interface only declares tryAura but the code accesses window.tryAuraMediaRoots
causing TypeScript errors; add a declaration for tryAuraMediaRoots to the Window
interface (e.g. tryAuraMediaRoots?: string[];) matching the shape used in the
file (or a more specific type if known), and update any usages with proper
typing or null checks if necessary.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (6)
src/admin/enhancer/PreviewModal.tsx (6)

353-357: Unused variable: extras is defined but never used in video prompt.

The extras variable captures optional user instructions but isn't included in videoPromptText, unlike the image generation prompt.


433-453: Polling loop lacks cancellation; can cause state updates after unmount.

The polling loop (up to 10 minutes) continues even if the user closes the modal, causing orphan network requests and potential React warnings. Consider using an AbortController or a ref-based cancellation flag.


624-632: Duplicate conflicting height classes: h-[152px] h-auto.

Both image elements have conflicting height classes. The last class wins in CSS, making h-[152px] ineffective.


787-788: Invalid Tailwind class and missing text domain.

  • w-[500] is missing a unit (should be w-[500px])
  • __('Prompt (Optional)') is missing the text domain parameter

861-861: Invalid Tailwind class: w-[500] is missing a unit.

Should be w-[500px] or similar.


1025-1027: Missing text domain in translation function call.

__('Adding…') is missing the text domain 'try-aura'.

🧹 Nitpick comments (6)
src/components/ModernSelect.tsx (2)

18-18: Consider improving type safety for the icon property.

The icon property is typed as any, which bypasses TypeScript's type checking. Consider using React.ReactNode or React.ReactElement for better type safety.

🔎 Suggested improvement
-	options: { label: string; value: string; icon?: any }[];
+	options: { label: string; value: string; icon?: React.ReactNode }[];

38-109: Consider using Tailwind theme tokens instead of arbitrary values.

The component uses many arbitrary values (e.g., border-[#E9E9E9], text-[14px], p-[10px_16px], bg-[#EFEAFF]) throughout. While functional, using theme tokens (e.g., border-gray-200, text-sm, p-2.5, bg-purple-50) would improve consistency, maintainability, and allow centralized theme management.

src/admin/enhancer/PreviewModal.tsx (4)

42-48: Consider adding a timeout to the settings fetch.

The fetch to resolve the API key has no timeout configured. If the server is slow or unresponsive, this could delay the user experience significantly before eventually failing.

🔎 Suggested approach using AbortController:
+	const controller = new AbortController();
+	const timeoutId = setTimeout(() => controller.abort(), 10000);
 	const res = await fetch(
 		rest.replace( /\/?$/, '/' ) + 'try-aura/v1/settings',
 		{
 			headers: { 'X-WP-Nonce': nonce },
 			credentials: 'same-origin',
+			signal: controller.signal,
 		}
 	);
+	clearTimeout(timeoutId);

896-944: Inconsistent styling: inline styles vs Tailwind classes.

The video tab section uses inline style attributes while the rest of the component uses Tailwind classes. Consider using Tailwind for consistency and maintainability.


948-1007: Inconsistent component usage: native buttons vs Button component.

The video controls use native <button> and <a> elements with WordPress "button" classes, while the image controls use the custom Button component. Consider using Button consistently for uniform styling and behavior.


780-802: Improve label-textarea association for accessibility.

The <label> wraps the textarea which provides implicit association, but using explicit htmlFor and id attributes would improve compatibility with assistive technologies.

🔎 Suggested fix:
 <label
+	htmlFor="optional-prompt-input"
 	style={ {
 		display: 'flex',
 		flexDirection: 'column',
 		gap: 4,
 	} }
 >
 	<span className="w-[500px] text-[14px] mb-[8px]">
 		{ __( 'Prompt (Optional)', 'try-aura' ) }
 	</span>
 	<textarea
+		id="optional-prompt-input"
 		className="border border-[#E9E9E9]"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8399a38 and f30619f.

📒 Files selected for processing (3)
  • src/admin/enhancer/PreviewModal.tsx (1 hunks)
  • src/components/ModernSelect.tsx (1 hunks)
  • tsconfig.json (0 hunks)
💤 Files with no reviewable changes (1)
  • tsconfig.json
🧰 Additional context used
🧬 Code graph analysis (2)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (3)
  • GroupButton (9-9)
  • ModernSelect (8-8)
  • Button (7-7)
src/components/ModernSelect.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
🔇 Additional comments (9)
src/components/ModernSelect.tsx (6)

1-5: LGTM!

The imports are well-organized and include the necessary dependencies for the component functionality.


23-26: LGTM!

State management and ref declarations are properly implemented.


31-34: LGTM!

The select handler correctly updates the value and closes the popover.


45-62: LGTM!

The trigger button is well-implemented with proper ARIA attributes and appropriate interaction behavior.


117-117: LGTM!

The component is properly exported as a default export, which aligns with the import pattern used in src/components/index.tsx.


1-115: No action needed—the component is compatible with Tailwind CSS v4.1.18.

The syntax used here, including arbitrary values like p-[10px_16px], gap-[4px], and border-[#E9E9E9], works correctly in Tailwind v4. The breaking changes mentioned (important modifier syntax, CSS variable parentheses, opacity slash syntax) do not apply to this component since it uses standard utilities and literal arbitrary values without CSS variables or opacity modifiers.

src/admin/enhancer/PreviewModal.tsx (3)

1-30: LGTM!

Imports and type definitions are well-structured. The PreviewProps type clearly documents the component's interface.


231-334: LGTM!

The media upload and selection logic is well-implemented with proper WordPress REST authentication, context detection for featured image frames, and appropriate error handling.


498-583: LGTM!

Video media upload logic correctly mirrors the image upload implementation with appropriate handling for video-specific concerns (not setting as featured image).

Comment on lines 197 to 200
const response: any = await ( ai as any ).models.generateContent( {
model: 'gemini-2.5-flash-image-preview',
contents: prompt,
} );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove unnecessary type assertion (ai as any) on line 197.

The current @google/genai SDK provides full TypeScript support for ai.models.generateContent() with proper type definitions—no any cast is needed. This suggests either using an outdated SDK version or an initialization issue. Update to the latest @google/genai SDK or fix the client initialization to eliminate this unsafe type bypass.

Comment on lines +28 to +29
if ( variant === 'list' ) {
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused dead code.

This empty conditional block serves no purpose and should be removed to improve code cleanliness.

🔎 Proposed fix
-	if ( variant === 'list' ) {
-	}
-
📝 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.

Suggested change
if ( variant === 'list' ) {
}
🤖 Prompt for AI Agents
In src/components/ModernSelect.tsx around lines 28 to 29, there is an empty if
block "if ( variant === 'list' ) { }" which is dead code; remove the entire
conditional block (including its braces) and any trailing blank line, confirm
there are no side-effecting comments or TODOs that need preserving, and run
lint/format and unit/UI tests to ensure no behavioral impact.

ref={ contentRef }
>
{ label && (
<span className="w-[500] text-[14px] mb-[8px]">{ label }</span>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix incorrect Tailwind class.

The class w-[500] is invalid for setting font weight. In Tailwind, w- is used for width properties. To set font weight to 500, use font-medium or font-[500].

🔎 Proposed fix
-				<span className="w-[500] text-[14px] mb-[8px]">{ label }</span>
+				<span className="font-medium text-[14px] mb-[8px]">{ label }</span>
📝 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.

Suggested change
<span className="w-[500] text-[14px] mb-[8px]">{ label }</span>
<span className="font-medium text-[14px] mb-[8px]">{ label }</span>
🤖 Prompt for AI Agents
In src/components/ModernSelect.tsx around line 42, the span uses an incorrect
Tailwind class `w-[500]` (width) instead of a font-weight utility; replace
`w-[500]` with a correct font weight class such as `font-medium` (or
`font-[500]` if you need the numeric value) while preserving the other classes
(`text-[14px] mb-[8px]`), so the class list becomes e.g. `font-medium
text-[14px] mb-[8px]`.

>
<div
className={ twMerge(
'bg-white border border-[#E9E9E9] rounded-[5px] rounded-[5px] shadow flex flex-wrap',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove duplicate Tailwind class.

The class rounded-[5px] appears twice in the className string.

🔎 Proposed fix
-								'bg-white border border-[#E9E9E9] rounded-[5px] rounded-[5px] shadow flex flex-wrap',
+								'bg-white border border-[#E9E9E9] rounded-[5px] shadow flex flex-wrap',
📝 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.

Suggested change
'bg-white border border-[#E9E9E9] rounded-[5px] rounded-[5px] shadow flex flex-wrap',
'bg-white border border-[#E9E9E9] rounded-[5px] shadow flex flex-wrap',
🤖 Prompt for AI Agents
In src/components/ModernSelect.tsx around line 79, the className string contains
the duplicate Tailwind class "rounded-[5px]" repeated twice; remove the
redundant occurrence so the class list only includes "rounded-[5px]" once,
keeping the rest of the classes unchanged.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

♻️ Duplicate comments (5)
src/admin/enhancer/PreviewModal.tsx (5)

97-114: Potential stale closure: videoUrl accessed without being in dependency array.

The effect accesses videoUrl (line 106) but only depends on imageUrls. This could cause cleanup of a stale videoUrl value instead of the current one.

🔎 Suggested fix using a ref:
+	const videoUrlRef = useRef<string | null>(null);
+
+	// Keep ref in sync
+	useEffect(() => {
+		videoUrlRef.current = videoUrl;
+	}, [videoUrl]);
+
 	// Reset state when image changes
 	useEffect( () => {
 		setStatus( 'idle' );
 		setMessage( 'Ready to generate' );
 		setGeneratedUrl( null );
 		setError( null );
 		// Reset video state too when images change
 		setVideoStatus( 'idle' );
 		setVideoMessage( 'Ready to generate video' );
-		if ( videoUrl && videoUrl.startsWith( 'blob:' ) ) {
+		if ( videoUrlRef.current && videoUrlRef.current.startsWith( 'blob:' ) ) {
 			try {
-				URL.revokeObjectURL( videoUrl );
+				URL.revokeObjectURL( videoUrlRef.current );
 			} catch {}
 		}
 		setVideoUrl( null );
 		setVideoError( null );
 		setActiveTab( 'image' );
 	}, [ imageUrls ] );

Based on past review comments, this issue was previously identified and remains unresolved.


127-217: Remove unnecessary type assertion (ai as any) on line 185.

The @google/genai SDK provides full TypeScript support. The type assertion bypasses type safety and suggests either an outdated SDK version or incorrect client initialization.

Please verify you're using the latest @google/genai SDK version and that the client is properly initialized. Run the following to check:

#!/bin/bash
# Check the installed version of @google/genai
cat package.json | jq '.dependencies["@google/genai"], .devDependencies["@google/genai"]'

Based on past review comments, this issue was previously identified and remains unresolved.


341-345: Unused variable: extras is defined but never used in video prompt.

The extras variable (line 341-343) captures optional user instructions but isn't included in videoPromptText (line 345), unlike the image generation prompt which does include it.

🔎 Suggested fix:
 		const extras =
 			optionalPrompt && optionalPrompt.trim().length
 				? `\n\nAdditional instruction from user: ${ optionalPrompt.trim() }`
 				: '';
-		const videoPromptText = `Create a short 3–5 second smooth product showcase video based on the generated try-on image. Use gentle camera motion and keep the scene aligned with preferences. make the model walk relaxed.`;
+		const videoPromptText = `Create a short 3–5 second smooth product showcase video based on the generated try-on image. Use gentle camera motion and keep the scene aligned with preferences. make the model walk relaxed.${ extras }`;

Based on past review comments, this issue was previously identified and remains unresolved.


418-441: Polling loop lacks cancellation; can cause state updates after unmount.

The polling loop (up to 10 minutes, lines 421-441) continues even if the user closes the modal. This causes orphan network requests and potential "state update on unmounted component" warnings.

🔎 Suggested approach using AbortController:
+	const abortControllerRef = useRef<AbortController | null>(null);
+
+	useEffect(() => {
+		return () => {
+			// Cancel any in-flight video generation on unmount
+			abortControllerRef.current?.abort();
+		};
+	}, []);

 	const doGenerateVideo = async () => {
 		try {
+			abortControllerRef.current?.abort();
+			abortControllerRef.current = new AbortController();
+			const signal = abortControllerRef.current.signal;
 			// ... existing setup code ...

 			for ( let i = 0; i < 60; i++ ) {
+				if ( signal.aborted ) return;
 				await new Promise( ( r ) => setTimeout( r, 10000 ) );
 				const statusRes = await fetch(
 					`${ BASE_URL }/${ operationName }`,
 					{
 						headers: { 'x-goog-api-key': apiKey },
+						signal,
 					}
 				);
 				// ... rest of loop ...
 			}

Based on past review comments, this issue was previously identified and remains unresolved.


646-648: Missing text domain in translation function call.

Line 647: __('Adding…') is missing the text domain 'try-aura'.

🔎 Suggested fix:
-			{ uploading
-				? __( 'Adding…' )
-				: __( 'Add to Media Library', 'try-aura' ) }
+			{ uploading
+				? __( 'Adding…', 'try-aura' )
+				: __( 'Add to Media Library', 'try-aura' ) }

Based on past review comments, this issue was previously identified and remains unresolved.

🧹 Nitpick comments (7)
src/admin/enhancer/PreviewSections/Output.tsx (1)

4-18: Add TypeScript types for component props.

The component accepts many props but lacks type definitions, reducing type safety and developer experience.

🔎 Proposed type definition
+interface OutputProps {
+	generatedUrl?: string;
+	supportsVideo?: boolean;
+	activeTab: 'image' | 'video';
+	setActiveTab: ( tab: 'image' | 'video' ) => void;
+	message?: string;
+	videoUrl?: string;
+	videoMessage?: string;
+	videoError?: string;
+	doGenerateVideo: () => void;
+	isVideoBusy: boolean;
+	videoUploading: boolean;
+	setVideoInMediaSelection: () => void;
+	error?: string;
+}

-function Output( {
+function Output( {
 	generatedUrl,
 	supportsVideo,
 	activeTab,
 	setActiveTab,
 	message,
 	videoUrl,
 	videoMessage,
 	videoError,
 	doGenerateVideo,
 	isVideoBusy,
 	videoUploading,
 	setVideoInMediaSelection,
 	error,
-} ) {
+}: OutputProps ) {
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (2)

19-30: Add TypeScript types and remove unused props.

Props lack type definitions. Also, isBlockEditorPage and isWoocommerceProductPage are accepted but never used in the component.

🔎 Proposed type definition
+interface VideoConfigInputsProps {
+	backgroundType: string;
+	styleType: string;
+	imageSize: string;
+	optionalPrompt: string;
+	setBackgroundType: ( val: string ) => void;
+	setStyleType: ( val: string ) => void;
+	setImageSize: ( val: string ) => void;
+	setOptionalPrompt: ( val: string ) => void;
+}

 function VideoConfigInputs( {
 	backgroundType,
 	styleType,
 	imageSize,
 	optionalPrompt,
 	setBackgroundType,
 	setStyleType,
 	setImageSize,
 	setOptionalPrompt,
-	isBlockEditorPage,
-	isWoocommerceProductPage,
-} ) {
+}: VideoConfigInputsProps ) {

165-166: Avoid using any type.

Use the proper React event type for type safety.

-				onChange={ ( e: any ) =>
+				onChange={ ( e: React.ChangeEvent<HTMLTextAreaElement> ) =>
 					setOptionalPrompt( e.target.value )
 				}
src/admin/enhancer/PreviewSections/ConfigSettings.tsx (2)

7-24: Add TypeScript types for component props.

The component accepts many props but lacks type definitions.

🔎 Proposed type definition
+interface ConfigSettingsProps {
+	supportsVideo?: boolean;
+	activeTab: 'image' | 'video';
+	setActiveTab: ( tab: 'image' | 'video' ) => void;
+	isBlockEditorPage: boolean;
+	isWoocommerceProductPage: boolean;
+	backgroundType: string;
+	setBackgroundType: ( val: string ) => void;
+	generatedUrl?: string;
+	styleType: string;
+	setStyleType: ( val: string ) => void;
+	imageSize: string;
+	setImageSize: ( val: string ) => void;
+	optionalPrompt: string;
+	setOptionalPrompt: ( val: string ) => void;
+	doGenerate: () => void;
+	isBusy: boolean;
+}

 function ConfigSettings( {
 	supportsVideo,
 	activeTab,
 	...
-} ) {
+}: ConfigSettingsProps ) {

1-1: Use centralized component export.

GroupButton is exported from ../../../components, so prefer importing from there for consistency with Button.

-import GroupButton from '../../../components/GroupButton';
-import { __ } from '@wordpress/i18n';
-import { Button } from '../../../components';
+import { Button, GroupButton } from '../../../components';
+import { __ } from '@wordpress/i18n';
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (2)

16-27: Add TypeScript types for component props.

Props lack type definitions, reducing type safety and developer experience.

🔎 Proposed type definition
+interface ImageConfigInputsProps {
+	backgroundType: string;
+	styleType: string;
+	imageSize: string;
+	optionalPrompt: string;
+	setBackgroundType: ( val: string ) => void;
+	setStyleType: ( val: string ) => void;
+	setImageSize: ( val: string ) => void;
+	setOptionalPrompt: ( val: string ) => void;
+	isBlockEditorPage: boolean;
+	isWoocommerceProductPage: boolean;
+}

 function ImageConfigInputs( {
 	backgroundType,
 	...
-} ) {
+}: ImageConfigInputsProps ) {

125-126: Avoid using any type.

Use the proper React event type for type safety.

-				onChange={ ( e: any ) =>
+				onChange={ ( e: React.ChangeEvent<HTMLTextAreaElement> ) =>
 					setOptionalPrompt( e.target.value )
 				}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f30619f and 5f71d1a.

📒 Files selected for processing (6)
  • src/admin/enhancer/PreviewModal.tsx (1 hunks)
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx (1 hunks)
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (1 hunks)
  • src/admin/enhancer/PreviewSections/OriginalImage.tsx (1 hunks)
  • src/admin/enhancer/PreviewSections/Output.tsx (1 hunks)
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/admin/enhancer/PreviewSections/Output.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
🔇 Additional comments (9)
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (1)

31-31: Verify conditional rendering logic covers all page types.

The conditions are mutually exclusive:

  • Line 31: !isBlockEditorPage && isWoocommerceProductPage → WooCommerce classic editor
  • Line 86: isBlockEditorPage && !isWoocommerceProductPage → Block editor non-WooCommerce

This means no configuration controls (except the prompt textarea) render for:

  • Classic editor non-WooCommerce pages
  • Block editor WooCommerce pages

Is this intentional?

Also applies to: 86-86

src/admin/enhancer/PreviewModal.tsx (8)

1-17: LGTM!

Imports and type definitions are clean and appropriate for the component's functionality.


19-48: LGTM!

The API key resolution logic is well-structured with appropriate fallbacks and error handling.


50-95: LGTM!

Component initialization and state management are well-organized. The body class management for modal styling is appropriately handled with cleanup.


116-125: LGTM!

The cleanup effect for video blob URLs is properly implemented with correct dependencies.


219-322: LGTM!

The media library integration is well-implemented with comprehensive error handling and careful WordPress media frame manipulation.


486-571: LGTM!

Video media library integration mirrors the image upload logic appropriately, with correct handling to avoid setting videos as featured images.


573-583: LGTM!

The computed busy flags appropriately track all relevant async operations.


584-663: Well-structured modal component.

The render logic is clean with appropriate component composition and accessibility considerations. The modal overlay and layout are properly implemented.

Comment on lines +119 to +120
<span className="w-[500] text-[14px] mb-[8px]">
{ __( 'Prompt (Optional)' ) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Invalid Tailwind class and inconsistent i18n text domain.

Same issues as in other files: w-[500] is invalid, and text domain inconsistency.

-			<span className="w-[500] text-[14px] mb-[8px]">
-				{ __( 'Prompt (Optional)' ) }
+			<span className="font-medium text-[14px] mb-[8px]">
+				{ __( 'Prompt (Optional)', 'try-aura' ) }
...
 				placeholder={ __(
 					'Add any specific instructions (optional)',
-					'tryaura'
+					'try-aura'
 				) }

Also applies to: 129-131

🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx around lines 119-120
(and also lines 129-131), the Tailwind class w-[500] is invalid and the i18n
call lacks the consistent text-domain; replace w-[500] with a valid
arbitrary-width class (e.g., w-[500px]) and update the translation calls to
include the project’s canonical text domain (e.g., change __( 'Prompt
(Optional)' ) to __( 'Prompt (Optional)', 'your-text-domain' ) using the same
domain string used elsewhere in the repo).

Comment on lines +10 to +11
<div className="text-[14px] mb-[8px]">
{ multiple ? 'Original Images' : 'Original Image' }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded strings should use i18n.

The header text should be wrapped with __() for internationalization.

+import { __ } from '@wordpress/i18n';
...
 			<div className="text-[14px] mb-[8px]">
-				{ multiple ? 'Original Images' : 'Original Image' }
+				{ multiple ? __( 'Original Images', 'try-aura' ) : __( 'Original Image', 'try-aura' ) }
 			</div>
📝 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.

Suggested change
<div className="text-[14px] mb-[8px]">
{ multiple ? 'Original Images' : 'Original Image' }
import { __ } from '@wordpress/i18n';
// ... other code ...
<div className="text-[14px] mb-[8px]">
{ multiple ? __( 'Original Images', 'try-aura' ) : __( 'Original Image', 'try-aura' ) }
</div>
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/OriginalImage.tsx around lines 10-11, the
header text is hardcoded; wrap the strings with the i18n helper __() instead of
raw text (e.g. use __( 'Original Images', 'your-text-domain' ) and __( 'Original
Image', 'your-text-domain' ) depending on multiple), and ensure the file imports
__ from the project's i18n module (match the import style/text-domain used
elsewhere in the repo).

key={ idx }
src={ url }
alt={ `Original ${ idx + 1 }` }
className="w-auto h-[152px] h-auto block border-none rounded-[8px]"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Conflicting Tailwind height classes.

h-[152px] h-auto both specify height; only h-auto will apply (last wins). This is likely unintended.

🔎 Proposed fix
-						className="w-auto h-[152px] h-auto block border-none rounded-[8px]"
+						className="w-auto max-h-[152px] h-auto block border-none rounded-[8px]"

Or if fixed height is desired:

-						className="w-auto h-[152px] h-auto block border-none rounded-[8px]"
+						className="w-auto h-[152px] block border-none rounded-[8px]"

Also applies to: 28-28

🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/OriginalImage.tsx around lines 20 and 28,
the element has conflicting Tailwind height classes ("h-[152px] h-auto") where
only the last declared value applies; decide whether you want a fixed height
(keep h-[152px] and remove h-auto) or fluid height (keep h-auto and remove
h-[152px]) and update both occurrences accordingly so there are no conflicting
height utilities.

Comment on lines +24 to +29
) : (
<img
src={ imageUrls[ 0 ] }
alt="Original"
className="w-auto h-[152px] h-auto block border-none rounded-[8px]"
/>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add guard for empty imageUrls array.

If imageUrls is empty, accessing imageUrls[0] will render undefined as the src, potentially causing a broken image or console warning.

🔎 Proposed fix
 			) : (
+				imageUrls[ 0 ] && (
 				<img
 					src={ imageUrls[ 0 ] }
 					alt="Original"
 					className="w-auto max-h-[152px] h-auto block border-none rounded-[8px]"
 				/>
+				)
 			) }
📝 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.

Suggested change
) : (
<img
src={ imageUrls[ 0 ] }
alt="Original"
className="w-auto h-[152px] h-auto block border-none rounded-[8px]"
/>
) : (
imageUrls[ 0 ] && (
<img
src={ imageUrls[ 0 ] }
alt="Original"
className="w-auto max-h-[152px] h-auto block border-none rounded-[8px]"
/>
)
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/OriginalImage.tsx around lines 24 to 29,
the JSX unconditionally uses imageUrls[0] for the img src which will be
undefined when imageUrls is empty; update the render to guard against an empty
array by checking imageUrls && imageUrls.length > 0 (or imageUrls?.[0]) before
using imageUrls[0], and render a fallback (null, placeholder image URL, or
different element) when there is no first image so that src is never undefined
and no broken image or console warning occurs.

} ) {
return (
<div className="w-full">
<div className="w-[500] text-[14px] mb-[8px]">
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Invalid Tailwind class w-[500].

w-[500] is not valid Tailwind syntax. If this is meant to set font-weight: 500, use font-medium or font-[500]. If it's meant to set width, use w-[500px].

🔎 Proposed fix
-			<div className="w-[500] text-[14px] mb-[8px]">
+			<div className="font-medium text-[14px] mb-[8px]">
📝 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.

Suggested change
<div className="w-[500] text-[14px] mb-[8px]">
<div className="font-medium text-[14px] mb-[8px]">
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/Output.tsx around line 21, the Tailwind
class "w-[500]" is invalid; replace it with the intended class — if you meant
width use "w-[500px]" (or a standard width like "w-64"), or if you meant
font-weight use "font-medium" or "font-[500]"; update the className string
accordingly to the correct Tailwind utility.

Comment on lines 91 to 141
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? 'Regenerating video…'
: 'Regenerate video' }
</button>
<a
className="button"
href={ isVideoBusy ? undefined : videoUrl }
download={
isVideoBusy
? undefined
: 'enhanced-video.mp4'
}
aria-disabled={ isVideoBusy }
style={ {
pointerEvents: isVideoBusy
? 'none'
: 'auto',
opacity: isVideoBusy ? 0.6 : 1,
} }
>
Download video
</a>
<button
className="button button-primary"
onClick={ setVideoInMediaSelection }
disabled={
isVideoBusy ||
videoUploading ||
! videoUrl
}
>
{ videoUploading
? 'Adding…'
: 'Add to Media' }
</button>
</>
) : (
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? 'Generating video…'
: 'Generate video' }
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Hardcoded strings and inconsistent button usage.

Multiple issues in the video controls section:

  1. Strings like 'Regenerating video…', 'Download video', 'Add to Media' are not internationalized.
  2. Uses native <button> and <a> elements instead of the imported Button component, creating UI inconsistency.
🔎 Proposed fix for i18n
-								{ isVideoBusy
-									? 'Regenerating video…'
-									: 'Regenerate video' }
+								{ isVideoBusy
+									? __( 'Regenerating video…', 'try-aura' )
+									: __( 'Regenerate video', 'try-aura' ) }
...
-									Download video
+									{ __( 'Download video', 'try-aura' ) }
...
-									{ videoUploading
-										? 'Adding…'
-										: 'Add to Media' }
+									{ videoUploading
+										? __( 'Adding…', 'try-aura' )
+										: __( 'Add to Media', 'try-aura' ) }
...
-								{ isVideoBusy
-									? 'Generating video…'
-									: 'Generate video' }
+								{ isVideoBusy
+									? __( 'Generating video…', 'try-aura' )
+									: __( 'Generate video', 'try-aura' ) }

Consider using the Button component for consistency with the rest of the UI.

📝 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.

Suggested change
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? 'Regenerating video…'
: 'Regenerate video' }
</button>
<a
className="button"
href={ isVideoBusy ? undefined : videoUrl }
download={
isVideoBusy
? undefined
: 'enhanced-video.mp4'
}
aria-disabled={ isVideoBusy }
style={ {
pointerEvents: isVideoBusy
? 'none'
: 'auto',
opacity: isVideoBusy ? 0.6 : 1,
} }
>
Download video
</a>
<button
className="button button-primary"
onClick={ setVideoInMediaSelection }
disabled={
isVideoBusy ||
videoUploading ||
! videoUrl
}
>
{ videoUploading
? 'Adding…'
: 'Add to Media' }
</button>
</>
) : (
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? 'Generating video…'
: 'Generate video' }
</button>
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? __( 'Regenerating video…', 'try-aura' )
: __( 'Regenerate video', 'try-aura' ) }
</button>
<a
className="button"
href={ isVideoBusy ? undefined : videoUrl }
download={
isVideoBusy
? undefined
: 'enhanced-video.mp4'
}
aria-disabled={ isVideoBusy }
style={ {
pointerEvents: isVideoBusy
? 'none'
: 'auto',
opacity: isVideoBusy ? 0.6 : 1,
} }
>
{ __( 'Download video', 'try-aura' ) }
</a>
<button
className="button button-primary"
onClick={ setVideoInMediaSelection }
disabled={
isVideoBusy ||
videoUploading ||
! videoUrl
}
>
{ videoUploading
? __( 'Adding…', 'try-aura' )
: __( 'Add to Media', 'try-aura' ) }
</button>
</>
) : (
<button
className="button"
onClick={ doGenerateVideo }
disabled={ isVideoBusy }
>
{ isVideoBusy
? __( 'Generating video…', 'try-aura' )
: __( 'Generate video', 'try-aura' ) }
</button>
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/Output.tsx around lines 91-141, the video
control buttons use hardcoded strings and native <button>/<a> elements causing
missing i18n and inconsistent UI; update all visible strings (e.g.,
'Regenerating video…', 'Download video', 'Add to Media', 'Generating video…') to
use the project's i18n function/translation hook, and replace native <button>
and <a> with the imported Button component (or Button configured as an
anchor/variant for downloads) preserving existing props (onClick, disabled,
aria-disabled, href, download, style/pointerEvents/opacity) and conditional
states (isVideoBusy, videoUploading, videoUrl) so behavior and accessibility
remain identical.

Comment on lines +159 to +160
<span className="w-[500] text-[14px] mb-[8px]">
{ __( 'Prompt (Optional)' ) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Invalid Tailwind class and missing i18n text domain.

Same issues as in other files: w-[500] is invalid, and __() is missing the text domain parameter.

-			<span className="w-[500] text-[14px] mb-[8px]">
-				{ __( 'Prompt (Optional)' ) }
+			<span className="font-medium text-[14px] mb-[8px]">
+				{ __( 'Prompt (Optional)', 'try-aura' ) }
📝 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.

Suggested change
<span className="w-[500] text-[14px] mb-[8px]">
{ __( 'Prompt (Optional)' ) }
<span className="font-medium text-[14px] mb-[8px]">
{ __( 'Prompt (Optional)', 'try-aura' ) }
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx around lines
159-160, the Tailwind class w-[500] is invalid and the i18n call __('Prompt
(Optional)') is missing the text domain; update the class to a valid Tailwind
value (e.g., w-[500px] or an appropriate utility like w-96) and add the project
text domain to the translation call (e.g., __('Prompt (Optional)',
'your-text-domain') / use the actual repo domain constant).

@getdokan getdokan deleted a comment from coderabbitai bot Dec 20, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/components/ModernSelect.tsx (3)

28-29: Remove unused dead code.

This empty conditional block was previously flagged and should be removed.


42-42: Fix incorrect Tailwind class.

The class w-[500] was previously flagged. It should be font-medium or font-[500] for font weight.


79-79: Remove duplicate Tailwind class.

The class rounded-[5px] appears twice and was previously flagged.

🧹 Nitpick comments (3)
src/components/ModernSelect.tsx (3)

18-18: Consider typing the icon property more precisely.

The icon?: any type weakens type safety. Consider using React.ReactNode or React.ComponentType for better type checking.

🔎 Proposed fix
-	options: { label: string; value: string; icon?: any }[];
+	options: { label: string; value: string; icon?: React.ReactNode }[];

25-25: Add explicit typing for popoverAnchor state.

The state is implicitly typed as any. Consider adding an explicit type annotation to improve type safety and avoid the need for @ts-ignore on line 47.

🔎 Proposed fix
-	const [ popoverAnchor, setPopoverAnchor ] = useState();
+	const [ popoverAnchor, setPopoverAnchor ] = useState< HTMLElement | undefined >();

46-47: Remove @ts-ignore by properly typing the ref.

The @ts-ignore comment suppresses a TypeScript error that can be resolved by properly typing the popoverAnchor state (see comment on line 25). Suppressing type errors reduces type safety.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f71d1a and 5cdbf49.

📒 Files selected for processing (1)
  • src/components/ModernSelect.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/components/ModernSelect.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)

Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
src/admin/enhancer/PreviewSections/Output.tsx (1)

21-21: Invalid Tailwind class w-[500] (duplicate).

This issue was already flagged in a previous review. The class w-[500] is not valid Tailwind syntax. If you meant font-weight: 500, use font-medium or font-[500]. If you meant width, use w-[500px].

🧹 Nitpick comments (5)
src/admin/enhancer/PreviewSections/Output.tsx (5)

4-18: Add TypeScript prop types for better type safety.

The function signature lacks TypeScript type definitions for its props. Consider defining an interface or type for the component props to improve type safety and provide better IDE support.

🔎 Proposed fix
+interface OutputProps {
+	generatedUrl?: string;
+	supportsVideo?: boolean;
+	activeTab: 'image' | 'video';
+	setActiveTab: ( tab: 'image' | 'video' ) => void;
+	message?: string;
+	videoUrl?: string;
+	videoMessage?: string;
+	videoError?: string;
+	doGenerateVideo?: () => void;
+	isVideoBusy?: boolean;
+	videoUploading?: boolean;
+	setVideoInMediaSelection?: () => void;
+	error?: string;
+}
+
-function Output( {
+function Output( {
 	generatedUrl,
 	supportsVideo,
 	activeTab,
 	setActiveTab,
 	message,
 	videoUrl,
 	videoMessage,
 	videoError,
 	doGenerateVideo,
 	isVideoBusy,
 	videoUploading,
 	setVideoInMediaSelection,
 	error,
-} ) {
+}: OutputProps ) {

46-46: Prefer standard Tailwind class over arbitrary value.

Replace font-[400] with the standard Tailwind class font-normal for consistency and better maintainability.

🔎 Proposed fix
-					<div className="bg-[#F3F4F6] text-[#67686B] text-[14px] font-[400] rounded-[8px] min-h-[316px] flex items-center justify-center">
+					<div className="bg-[#F3F4F6] text-[#67686B] text-[14px] font-normal rounded-[8px] min-h-[316px] flex items-center justify-center">

59-59: Prefer standard Tailwind class over arbitrary value.

Replace font-[400] with font-normal for consistency with Tailwind conventions.

🔎 Proposed fix
-						<div className="bg-[#F3F4F6] text-[#67686B] text-[14px] font-[400] rounded-[8px] min-h-[316px] flex items-center justify-center">
+						<div className="bg-[#F3F4F6] text-[#67686B] text-[14px] font-normal rounded-[8px] min-h-[316px] flex items-center justify-center">

64-66: Consider using Tailwind classes for consistency.

The inline styles for error messages could be replaced with Tailwind classes to maintain consistency with the rest of the component's styling approach.

🔎 Proposed fix
-					{ videoError && (
-						<div style={ { color: 'red', marginTop: 8 } }>
-							{ videoError }
-						</div>
-					) }
+					{ videoError && (
+						<div className="text-red-600 mt-[8px]">
+							{ videoError }
+						</div>
+					) }

70-72: Consider using Tailwind classes for consistency.

The inline styles for the generic error message could be replaced with Tailwind classes to maintain consistency.

🔎 Proposed fix
 	{ error ? (
-		<div style={ { color: 'red', marginTop: 8 } }>{ error }</div>
+		<div className="text-red-600 mt-[8px]">{ error }</div>
 	) : null }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cdbf49 and f3cccfa.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewModal.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/Output.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewModal.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/enhancer/PreviewSections/Output.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
🔇 Additional comments (1)
src/admin/enhancer/PreviewSections/Output.tsx (1)

1-2: LGTM!

The imports are clean and appropriate for this component.

className="border border-primary text-primary bg-white"
onClick={ () => setActiveTab( 'video' ) }
>
{ __( 'Generate Video', 'tryaura' ) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Text domain inconsistency.

The text domain 'tryaura' is inconsistent with 'try-aura' used elsewhere in this file (line 22). Use the same text domain throughout for proper internationalization.

🔎 Proposed fix
-									{ __( 'Generate Video', 'tryaura' ) }
+									{ __( 'Generate Video', 'try-aura' ) }
📝 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.

Suggested change
{ __( 'Generate Video', 'tryaura' ) }
{ __( 'Generate Video', 'try-aura' ) }
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/Output.tsx around line 40 (and check line
22), the text domain 'tryaura' is inconsistent with the rest of the file which
uses 'try-aura'; update the string domain at line 40 from 'tryaura' to
'try-aura' (and scan the file for any other occurrences of 'tryaura' to replace
so all i18n calls use the same 'try-aura' text domain).

Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (5)
src/admin/enhancer/PreviewSections/ConfigSettings.tsx (1)

37-97: Inconsistent i18n text domain still present.

Despite being marked as addressed in a previous review, the text domain inconsistency persists. Lines 37, 41, 95-97 use 'tryaura' while lines 114, 131-132 use 'try-aura'. Standardize all occurrences to 'try-aura'.

🔎 Proposed fix
-						label: __( 'Generate Image', 'tryaura' ),
+						label: __( 'Generate Image', 'try-aura' ),
 						value: 'image',
 					},
 					{
-						label: __( 'Generate Video', 'tryaura' ),
+						label: __( 'Generate Video', 'try-aura' ),
 						value: 'video',
 						disabled: ! generatedUrl,
...
 						>
-							{ isBusy
-								? __( 'Regenerating…', 'tryaura' )
-								: __( 'Regenerate', 'tryaura' ) }
+							{ isBusy
+								? __( 'Regenerating…', 'try-aura' )
+								: __( 'Regenerate', 'try-aura' ) }
 						</Button>
src/admin/enhancer/PreviewModal.tsx (4)

97-114: Stale closure: videoUrl accessed without being in dependency array.

The effect at line 97 resets state when imageUrls changes, but references videoUrl (line 106) which is not in the dependency array (line 114). This could revoke an outdated blob URL instead of the current one if videoUrl has changed between renders.

🔎 Suggested fix using a ref
+	const videoUrlRef = useRef<string | null>(null);
+
+	// Keep ref in sync
+	useEffect(() => {
+		videoUrlRef.current = videoUrl;
+	}, [videoUrl]);
+
 	// Reset state when image changes
 	useEffect( () => {
 		setStatus( 'idle' );
 		setMessage( 'Ready to generate' );
 		setGeneratedUrl( null );
 		setError( null );
 		// Reset video state too when images change
 		setVideoStatus( 'idle' );
 		setVideoMessage( 'Ready to generate video' );
-		if ( videoUrl && videoUrl.startsWith( 'blob:' ) ) {
+		if ( videoUrlRef.current && videoUrlRef.current.startsWith( 'blob:' ) ) {
 			try {
-				URL.revokeObjectURL( videoUrl );
+				URL.revokeObjectURL( videoUrlRef.current );
 			} catch {}
 		}
 		setVideoUrl( null );
 		setVideoError( null );
 		setActiveTab( 'image' );
 	}, [ imageUrls ] );

185-188: Remove unnecessary type assertion on GoogleGenAI client.

The (ai as any) cast on line 185 bypasses TypeScript's type checking. The @google/genai SDK provides full type definitions—verify you're using the latest version and that the client is correctly initialized to eliminate this unsafe type bypass.

#!/bin/bash
# Check the installed version of @google/genai package
cat package.json | jq '.dependencies["@google/genai"], .devDependencies["@google/genai"]'

341-345: User instructions ignored: extras variable defined but not used.

The extras variable (line 341) captures the user's optional prompt but is never included in videoPromptText (line 345), unlike the image generation prompt which does include it. This means user-provided instructions are silently ignored for video generation.

🔎 Suggested fix
 		const extras =
 			optionalPrompt && optionalPrompt.trim().length
 				? `\n\nAdditional instruction from user: ${ optionalPrompt.trim() }`
 				: '';
-		const videoPromptText = `Create a short 3–5 second smooth product showcase video based on the generated try-on image. Use gentle camera motion and keep the scene aligned with preferences. make the model walk relaxed.`;
+		const videoPromptText = `Create a short 3–5 second smooth product showcase video based on the generated try-on image. Use gentle camera motion and keep the scene aligned with preferences. make the model walk relaxed.${ extras }`;

418-441: Polling loop lacks cancellation; can cause orphaned requests and state updates after unmount.

The polling loop (lines 421-441) runs for up to 10 minutes but has no cancellation mechanism. If the user closes the modal or the component unmounts, the loop continues making network requests and may attempt state updates on an unmounted component.

🔎 Suggested fix using AbortController
+	const abortControllerRef = useRef<AbortController | null>(null);
+
+	useEffect(() => {
+		return () => {
+			// Cancel any in-flight video generation on unmount
+			abortControllerRef.current?.abort();
+		};
+	}, []);

 	const doGenerateVideo = async () => {
 		try {
+			abortControllerRef.current?.abort();
+			abortControllerRef.current = new AbortController();
+			const signal = abortControllerRef.current.signal;
 			// ... existing setup code ...

 			for ( let i = 0; i < 60; i++ ) {
+				if (signal.aborted) return;
 				await new Promise( ( r ) => setTimeout( r, 10000 ) );
 				const statusRes = await fetch(
 					`${ BASE_URL }/${ operationName }`,
 					{
 						headers: { 'x-goog-api-key': apiKey },
+						signal,
 					}
 				);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f3cccfa and 06887b5.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewModal.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/Output.tsx
  • src/components/Button.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/Button.tsx
  • src/admin/enhancer/PreviewSections/Output.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)

… state management for image and video settings
Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (5)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)

169-170: Fix invalid Tailwind class and add missing text domain.

Line 169 has an invalid Tailwind class w-[500] (missing unit) and line 170 is missing the text domain parameter for the translation call.

🔎 Proposed fix
-				<span className="w-[500] text-[14px] mb-[8px]">
-					{ __( 'Prompt (Optional)' ) }
+				<span className="font-medium text-[14px] mb-[8px]">
+					{ __( 'Prompt (Optional)', 'try-aura' ) }
src/admin/enhancer/PreviewModal.tsx (4)

107-128: Potential stale closure: videoUrl accessed in effect without being in dependency array.

The effect resets state when imageUrls changes but references videoUrl (lines 118-122) which is not in the dependency array. This could cause the wrong blob URL to be revoked if videoUrl has changed since the effect was created.

Based on past review comments, the suggested fix involves using a ref to track the current videoUrl value.


200-203: Remove unnecessary type assertion (ai as any).

The Google GenAI SDK provides proper TypeScript support. This type assertion bypasses type safety and suggests either an outdated SDK version or an initialization issue.

Run this script to check the installed SDK version:

#!/bin/bash
# Check the installed version of @google/genai
cat package.json | jq '.dependencies["@google/genai"], .devDependencies["@google/genai"]'

339-500: Polling loop lacks cancellation; can cause orphan requests and state updates after unmount.

The polling loop (lines 437-457) continues for up to 10 minutes even if the user closes the modal. This causes orphan network requests and potential "state update on unmounted component" warnings.

Consider implementing cancellation using an AbortController as suggested in the past review comments.


668-675: Missing text domain in translation call.

Line 673 uses __( 'Adding…' ) without the text domain parameter, which will cause translation lookup failures.

 							{ (
 								activeTab === 'image'
 									? uploading
 									: videoUploading
 							)
-								? __( 'Adding…' )
+								? __( 'Adding…', 'try-aura' )
 								: __( 'Add to Media Library', 'try-aura' ) }
🧹 Nitpick comments (1)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)

20-23: Add TypeScript types for component props.

The component props lack type definitions, reducing type safety and IDE assistance. Define an interface for the props to ensure correct usage.

🔎 Proposed fix
+type VideoConfigInputsProps = {
+	videoConfigData: {
+		styles: string;
+		cameraMotion: string;
+		aspectRatio: string;
+		duration: string;
+		optionalPrompt: string;
+	};
+	setVideoConfigData: ( data: VideoConfigInputsProps['videoConfigData'] ) => void;
+};
+
-function VideoConfigInputs( {
+function VideoConfigInputs( {
 	videoConfigData,
 	setVideoConfigData,
-} ) {
+}: VideoConfigInputsProps ) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06887b5 and 7873895.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewModal.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
🔇 Additional comments (1)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)

105-159: LGTM! Duration state binding fixed.

The Duration control now correctly uses videoConfigData.duration instead of the previously incorrect imageSize state. Both Aspect Ratio and Duration controls are properly bound to their respective state properties.

Comment on lines +182 to +185
placeholder={ __(
'Add any specific instructions (optional)',
'tryaura'
) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent text domain: use 'try-aura' instead of 'tryaura'.

The placeholder text uses 'tryaura' as the text domain, while the rest of the file uses 'try-aura'. This inconsistency will cause translation lookup failures.

 					placeholder={ __(
 						'Add any specific instructions (optional)',
-						'tryaura'
+						'try-aura'
 					) }
📝 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.

Suggested change
placeholder={ __(
'Add any specific instructions (optional)',
'tryaura'
) }
placeholder={ __(
'Add any specific instructions (optional)',
'try-aura'
) }
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx around lines 182 to
185, the placeholder call uses the wrong text domain 'tryaura'; update that
string to the correct domain 'try-aura' to match the rest of the file so
translation lookups succeed (replace 'tryaura' with 'try-aura' in the __()
call).

…gement and enhance prompt generation for images and videos
Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (4)
src/admin/enhancer/PreviewModal.tsx (4)

107-124: Stale closure: videoUrl accessed without being in dependency array.

The effect resets state when imageUrls changes (line 124) but references videoUrl (lines 116-120) which is not in the dependency array. This could revoke the wrong blob URL if videoUrl has changed since the effect was created.

🔎 Suggested fix using a ref
+	const videoUrlRef = useRef<string | null>(null);
+
+	// Keep ref in sync
+	useEffect(() => {
+		videoUrlRef.current = videoUrl;
+	}, [videoUrl]);
+
 	// Reset state when image changes
 	useEffect( () => {
 		setStatus( 'idle' );
 		setMessage( 'Ready to generate' );
 		setGeneratedUrl( null );
 		setError( null );
 		// Reset video state too when images change
 		setVideoStatus( 'idle' );
 		setVideoMessage( 'Ready to generate video' );
-		if ( videoUrl && videoUrl.startsWith( 'blob:' ) ) {
+		if ( videoUrlRef.current && videoUrlRef.current.startsWith( 'blob:' ) ) {
 			try {
-				URL.revokeObjectURL( videoUrl );
+				URL.revokeObjectURL( videoUrlRef.current );
 			} catch {}
 		}
 		setVideoUrl( null );
 		setVideoError( null );
 		setActiveTab( 'image' );
 	}, [ imageUrls ] );

Note: This issue was previously flagged but remains unresolved.


196-199: Remove unsafe type assertion (ai as any).

The type cast on line 196 bypasses TypeScript's type safety. The @google/genai SDK (version 0.3.1 per library context) provides proper TypeScript definitions for the generateContent method. This suggests either an outdated SDK version or incorrect client initialization.

🔎 Recommended approach

Update to the latest @google/genai SDK and verify the correct API usage:

-			const response: any = await ( ai as any ).models.generateContent( {
+			const response = await ai.models.generateContent( {
 				model: 'gemini-2.5-flash-image-preview',
 				contents: prompt,
 			} );

If type errors persist, check the SDK documentation for the correct method signature rather than using any.

Note: This issue was previously flagged but remains unresolved.


671-671: Missing text domain in translation call.

Line 671 uses __( 'Adding…' ) without the text domain parameter, while line 672 correctly includes 'try-aura'. This will cause translation lookup failures.

🔎 Proposed fix
 							? uploading
 							: videoUploading
 					)
-						? __( 'Adding…' )
+						? __( 'Adding…', 'try-aura' )
 						: __( 'Add to Media Library', 'try-aura' ) }

Note: This issue was previously flagged as addressed in commit 7873895, but appears to have regressed or the current code predates that fix.


335-498: Polling loop lacks cancellation; can cause state updates after unmount.

The polling loop (lines 435-455) can run for up to 10 minutes but has no cancellation mechanism. If the user closes the modal during generation, the loop continues making network requests and will attempt state updates on an unmounted component, causing memory leaks and console warnings.

🔎 Suggested fix using AbortController
+	const abortControllerRef = useRef<AbortController | null>(null);
+
+	useEffect(() => {
+		return () => {
+			// Cancel any in-flight video generation on unmount
+			abortControllerRef.current?.abort();
+		};
+	}, []);

 	const doGenerateVideo = async () => {
 		try {
+			abortControllerRef.current?.abort();
+			abortControllerRef.current = new AbortController();
+			const signal = abortControllerRef.current.signal;
 			// ... existing setup code ...

 			for ( let i = 0; i < 60; i++ ) {
+				if ( signal.aborted ) return;
 				await new Promise( ( r ) => setTimeout( r, 10000 ) );
 				const statusRes = await fetch(
 					`${ BASE_URL }/${ operationName }`,
 					{
 						headers: { 'x-goog-api-key': apiKey },
+						signal,
 					}
 				);
 				// ... rest of loop ...
 			}

Note: This issue was previously flagged but remains unresolved.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7873895 and 9dc57e2.

📒 Files selected for processing (2)
  • src/admin/enhancer/PreviewModal.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)

Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
inc/Enhancer.php (1)

63-69: Misleading comment: refers to "frontend" but this is admin enhancer.

The comment on line 63 says "Enqueue compiled Tailwind CSS for frontend if available" but this CSS is for the admin enhancer module. This issue was previously flagged but remains unaddressed.

🔎 Apply this diff:
-		// Enqueue compiled Tailwind CSS for frontend if available.
+		// Enqueue compiled Tailwind CSS for admin enhancer if available.
🧹 Nitpick comments (2)
src/admin/woocommerce-products-list/index.tsx (1)

7-8: Consider adding proper jQuery typings instead of @ts-ignore.

While this works, you could add @types/jquery as a dev dependency and reference it properly to get type safety.

🔎 Suggested improvement
-// @ts-ignore
-jQuery( document ).ready( function ( $ ) {
+jQuery( document ).ready( function ( $: JQueryStatic ) {

After installing: npm install --save-dev @types/jquery

inc/WooCommerce.php (1)

52-61: Fallback when 'featured' column is absent.

If the 'featured' column doesn't exist (e.g., due to theme/plugin modifications), the Try-on column won't be added. Consider adding a fallback placement.

🔎 Suggested fallback
 public function add_product_column( array $columns ): array {
 	$new_columns = array();
+	$added = false;
 	foreach ( $columns as $key => $column ) {
 		$new_columns[ $key ] = $column;
 		if ( 'featured' === $key ) {
 			$new_columns['try_aura_try_on'] = __( 'Try-on', 'try-aura' );
+			$added = true;
 		}
 	}
+	// Fallback: append if 'featured' column wasn't found.
+	if ( ! $added ) {
+		$new_columns['try_aura_try_on'] = __( 'Try-on', 'try-aura' );
+	}
 	return $new_columns;
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dc57e2 and f0dd72a.

📒 Files selected for processing (9)
  • inc/Admin.php
  • inc/Enhancer.php
  • inc/Plugin.php
  • inc/TryOn.php
  • inc/WooCommerce.php
  • src/admin/woocommerce-products-list/index.tsx
  • src/admin/woocommerce-products-list/style.scss
  • templates/products/tryon-switch.php
  • webpack.config.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • inc/Admin.php
🧰 Additional context used
🧬 Code graph analysis (2)
inc/Enhancer.php (2)
inc/Admin.php (1)
  • __construct (23-27)
inc/WooCommerce.php (1)
  • __construct (22-36)
inc/Plugin.php (5)
inc/Admin.php (2)
  • __construct (23-27)
  • Admin (12-125)
inc/Enhancer.php (2)
  • __construct (17-19)
  • Enhancer (12-92)
inc/TryOn.php (2)
  • __construct (13-15)
  • TryOn (12-75)
inc/WooCommerce.php (2)
  • __construct (22-36)
  • WooCommerce (12-154)
inc/Rest/SettingsController.php (2)
  • __construct (26-29)
  • SettingsController (16-92)
🪛 PHPMD (2.15.0)
inc/WooCommerce.php

80-80: Avoid unused local variables such as '$checked'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (15)
templates/products/tryon-switch.php (1)

12-16: LGTM - Well-structured toggle template with proper escaping.

The template correctly uses esc_attr() for the product ID and WordPress's native checked() function. The Tailwind utility classes provide consistent styling scoped under .tryaura.

src/admin/woocommerce-products-list/style.scss (1)

1-9: LGTM - Clean stylesheet setup with proper Tailwind integration.

The @source directives correctly reference the relevant TypeScript/template files for Tailwind class scanning, and the column width is appropriately constrained for the toggle control.

inc/Plugin.php (1)

25-40: LGTM - Clean plugin initialization structure.

The initialization order is logical: REST endpoints, frontend assets, WooCommerce integrations (needed for the meta key constant reference in frontend), then admin-only components. The is_admin() guard appropriately restricts Admin and Enhancer to the admin context.

src/admin/woocommerce-products-list/index.tsx (1)

16-36: LGTM - AJAX toggle implementation is solid.

The implementation properly:

  • Disables the checkbox during the request to prevent double-clicks
  • Reverts state on both error and unsuccessful responses
  • Re-enables the checkbox on completion

jQuery's form serialization converts the boolean enabled to a string 'true'/'false', which matches the server-side expectation in toggle_try_on_ajax().

inc/TryOn.php (2)

61-71: Security consideration: API key exposed to frontend.

The inline comment acknowledges this concern. Exposing the API key in client-side JavaScript makes it accessible to anyone viewing the page source. For production use, consider implementing a server-side proxy endpoint that adds the API key to requests, keeping it hidden from the client.

Is this acceptable for the current phase, or should a proxied approach be implemented before release?


30-34: LGTM - Per-product enablement logic is correct.

The logic properly defaults to enabled when meta is unset (empty string) and only disables when explicitly set to 'no'. This matches the default behavior in WooCommerce::render_product_column().

webpack.config.js (1)

8-22: LGTM - Well-structured multi-entry webpack configuration.

The configuration properly:

  • Preserves default WordPress script entries via spread
  • Exposes the components bundle as a window global for cross-bundle access
  • Organizes entries by their deployment context (admin vs frontend)
inc/WooCommerce.php (2)

74-85: Static analysis false positive: $checked is used.

The PHPMD hint flagging $checked as unused is incorrect—the variable is consumed by the included template (tryon-switch.php) via checked( $checked ). No action needed.


135-153: LGTM - AJAX handler with proper security controls.

The handler correctly implements:

  • Nonce verification via check_ajax_referer()
  • Capability check for edit_products
  • Input sanitization with intval() for product ID
  • Proper JSON success/error responses
inc/Enhancer.php (6)

14-19: LGTM!

The PHPDoc comment for the constructor follows standard conventions and improves code documentation.


32-43: LGTM!

The component asset registration follows WordPress best practices for handling webpack-generated asset files, with appropriate fallback defaults.


59-61: LGTM!

The script URL correctly reflects the new build structure.


71-76: LGTM!

The component CSS enqueue follows the same correct pattern with file existence check and filemtime() versioning.


79-87: LGTM!

The localization correctly passes REST API settings to the enhancer UI script, following the standard WordPress pattern of register → localize → enqueue.


89-90: LGTM!

Both scripts are correctly enqueued after registration, with dependencies properly declared.

inc/Enhancer.php Outdated
Comment on lines 45 to 57
$asset_file = plugin_dir_path( __DIR__ ) . 'build/admin/enhancer/index.asset.php';
$deps = array( 'wp-element', 'media-views', 'try-aura-components' );
$version = '1.0.0';

if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
$version = $asset['version'] ?? $version;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure try-aura-components is present in dependencies.

The code ensures media-views is in the dependencies array after loading the asset file (lines 52-55), but doesn't apply the same check for try-aura-components. If the asset file exists and doesn't include try-aura-components in its dependencies, the enhancer script could fail at runtime.

🔎 Proposed fix to ensure try-aura-components dependency
 		if ( file_exists( $asset_file ) ) {
 			$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
 			$deps  = $asset['dependencies'] ?? $deps;
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
+			// Ensure try-aura-components is present.
+			if ( ! in_array( 'try-aura-components', $deps, true ) ) {
+				$deps[] = 'try-aura-components';
+			}
 			$version = $asset['version'] ?? $version;
 		}
📝 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.

Suggested change
$asset_file = plugin_dir_path( __DIR__ ) . 'build/admin/enhancer/index.asset.php';
$deps = array( 'wp-element', 'media-views', 'try-aura-components' );
$version = '1.0.0';
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
$version = $asset['version'] ?? $version;
}
$asset_file = plugin_dir_path( __DIR__ ) . 'build/admin/enhancer/index.asset.php';
$deps = array( 'wp-element', 'media-views', 'try-aura-components' );
$version = '1.0.0';
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
// Ensure try-aura-components is present.
if ( ! in_array( 'try-aura-components', $deps, true ) ) {
$deps[] = 'try-aura-components';
}
$version = $asset['version'] ?? $version;
}
🤖 Prompt for AI Agents
In inc/Enhancer.php around lines 45 to 57, after loading the asset file and
overriding $deps, add the same presence check you have for media-views to ensure
'try-aura-components' is included: check with in_array('try-aura-components',
$deps, true) and append it to $deps if missing, so the enhancer script always
enqueues that dependency even when the asset file omits it.

…nto new `Assets` class, streamline enqueues across components, and add constants for better maintainability.
Copy link

@coderabbitai coderabbitai bot left a 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

🧹 Nitpick comments (1)
inc/WooCommerce.php (1)

146-147: Add sanitization for $_POST values.

While intval() and string comparison provide some protection, WordPress best practices recommend using wp_unslash() before sanitizing $_POST data to handle potential slashing.

🔎 Proposed fix
-		$product_id = isset( $_POST['product_id'] ) ? intval( $_POST['product_id'] ) : 0;
-		$enabled    = isset( $_POST['enabled'] ) && 'true' === $_POST['enabled'] ? 'yes' : 'no';
+		$product_id = isset( $_POST['product_id'] ) ? intval( wp_unslash( $_POST['product_id'] ) ) : 0;
+		$enabled    = isset( $_POST['enabled'] ) && 'true' === wp_unslash( $_POST['enabled'] ) ? 'yes' : 'no';
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f0dd72a and e254a42.

📒 Files selected for processing (7)
  • inc/Admin.php
  • inc/Assets.php
  • inc/Enhancer.php
  • inc/Plugin.php
  • inc/TryOn.php
  • inc/WooCommerce.php
  • try-aura.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • try-aura.php
🧰 Additional context used
🧬 Code graph analysis (3)
inc/Admin.php (6)
inc/Assets.php (1)
  • __construct (7-9)
inc/Enhancer.php (1)
  • __construct (17-19)
inc/Plugin.php (1)
  • __construct (25-28)
inc/TryOn.php (1)
  • __construct (13-15)
inc/WooCommerce.php (1)
  • __construct (22-39)
inc/Rest/SettingsController.php (1)
  • __construct (26-29)
inc/TryOn.php (1)
inc/WooCommerce.php (2)
  • __construct (22-39)
  • WooCommerce (12-157)
inc/WooCommerce.php (1)
inc/TryOn.php (2)
  • __construct (13-15)
  • TryOn (12-51)
🪛 PHPMD (2.15.0)
inc/Assets.php

174-174: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

inc/WooCommerce.php

83-83: Avoid unused local variables such as '$checked'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (4)
inc/TryOn.php (1)

20-50: LGTM! Well-structured per-product enablement.

The implementation correctly:

  • Guards against non-product pages and missing product IDs
  • Respects per-product try-on settings via meta key
  • Defaults to enabled when not explicitly disabled (consistent with WooCommerce::render_product_column)
  • Includes a helpful NOTE about API key exposure for production considerations
inc/Admin.php (1)

23-103: LGTM! Clean refactor to centralized asset management.

The admin class is well-structured with:

  • Consistent array() syntax for callbacks
  • Proper hook gating on the correct admin page (toplevel_page_try-aura)
  • Defensive checking for wp-components style availability
  • Clean localization with all necessary data (restUrl, nonce, apiKey, optionKey)
  • Simplified asset enqueuing relying on centralized registration
inc/WooCommerce.php (1)

83-83: False positive: $checked variable is used in the included template.

The static analysis tool flags $checked as unused, but it's passed to the included template templates/products/tryon-switch.php. This is a false positive.

inc/Assets.php (1)

174-174: False positive: $script variable structure is standard WordPress pattern.

The static analysis tool flags $script as unused, but it's part of the standard foreach loop structure. The actual data ($handle) is used in the wp_enqueue_script() call. This is a false positive.

Comment on lines 99 to 114
$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
$version = $asset['version'] ?? '1.0.0';

$scripts['try-aura-components'] = array(
'version' => $version,
'src' => $asset_url . 'build/admin/enhancer/index.js',
'deps' => $deps,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Wrong script handle overwrites components library.

Line 109 assigns the enhancer script to the try-aura-components handle, overwriting the components library registered on line 92. This will break both the components library and the enhancer script.

Additionally, line 102 uses an undefined $deps variable—it should use a default array like the other asset blocks.

🔎 Proposed fix
 		$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
 		if ( file_exists( $asset_file ) ) {
 			$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
-			$deps  = $asset['dependencies'] ?? $deps;
+			$deps  = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
 			$version = $asset['version'] ?? '1.0.0';
 
-			$scripts['try-aura-components'] = array(
+			$scripts['try-aura-enhancer'] = array(
 				'version' => $version,
 				'src'     => $asset_url . 'build/admin/enhancer/index.js',
 				'deps'    => $deps,
 			);
 		}
📝 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.

Suggested change
$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? $deps;
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
$version = $asset['version'] ?? '1.0.0';
$scripts['try-aura-components'] = array(
'version' => $version,
'src' => $asset_url . 'build/admin/enhancer/index.js',
'deps' => $deps,
);
}
$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
if ( file_exists( $asset_file ) ) {
$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$deps = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
// Ensure media-views is present so wp.media is available.
if ( ! in_array( 'media-views', $deps, true ) ) {
$deps[] = 'media-views';
}
$version = $asset['version'] ?? '1.0.0';
$scripts['try-aura-enhancer'] = array(
'version' => $version,
'src' => $asset_url . 'build/admin/enhancer/index.js',
'deps' => $deps,
);
}
🤖 Prompt for AI Agents
In inc/Assets.php around lines 99 to 114, the enhancer block wrongly assigns its
script to the 'try-aura-components' handle (overwriting the components library)
and uses an undefined $deps; change the script handle to a unique name such as
'try-aura-enhancer' (or another non-conflicting handle) and initialize $deps
using a safe default when reading the asset file, e.g. $deps =
$asset['dependencies'] ?? array(); so the enhancer registers separately and does
not rely on an undefined variable.

public function define_constants() {
defined( 'TRYAURA_PLUGIN_VERSION' ) || define( 'TRYAURA_PLUGIN_VERSION', $this->version );
defined( 'TRYAURA_INC_DIR' ) || define( 'TRYAURA_INC_DIR', TRYAURA_DIR . '/inc' );
defined( 'TRYAURA_PLUGIN_ASSEST' ) || define( 'TRYAURA_PLUGIN_ASSEST', plugins_url( 'build', TRYAURA_FILE ) );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix typo in constant name.

The constant name TRYAURA_PLUGIN_ASSEST contains a typo. It should be TRYAURA_PLUGIN_ASSETS (missing 'S').

🔎 Proposed fix
-		defined( 'TRYAURA_PLUGIN_ASSEST' ) || define( 'TRYAURA_PLUGIN_ASSEST', plugins_url( 'build', TRYAURA_FILE ) );
+		defined( 'TRYAURA_PLUGIN_ASSETS' ) || define( 'TRYAURA_PLUGIN_ASSETS', plugins_url( 'build', TRYAURA_FILE ) );
📝 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.

Suggested change
defined( 'TRYAURA_PLUGIN_ASSEST' ) || define( 'TRYAURA_PLUGIN_ASSEST', plugins_url( 'build', TRYAURA_FILE ) );
defined( 'TRYAURA_PLUGIN_ASSETS' ) || define( 'TRYAURA_PLUGIN_ASSETS', plugins_url( 'build', TRYAURA_FILE ) );
🤖 Prompt for AI Agents
In inc/Plugin.php around line 38, the defined constant name has a typo:
`TRYAURA_PLUGIN_ASSEST`; rename it to `TRYAURA_PLUGIN_ASSETS` and update the
define call accordingly, then search the codebase for any references to the
misspelled constant and replace them with the corrected `TRYAURA_PLUGIN_ASSETS`
to ensure consistency and avoid undefined constant errors.

…ine styles with Tailwind classes, enhance camera and image upload UX, and improve i18n support.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (1)
inc/Assets.php (1)

99-114: Critical: Wrong script handle overwrites components library.

Line 109 assigns the enhancer script to the try-aura-components handle, overwriting the components library registered on line 92. This will break both the components library and the enhancer script.

Additionally, line 102 uses an undefined $deps variable—it should use a default array like the other asset blocks.

🔎 Proposed fix
 		$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
 		if ( file_exists( $asset_file ) ) {
 			$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
-			$deps  = $asset['dependencies'] ?? $deps;
+			$deps  = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
 			$version = $asset['version'] ?? '1.0.0';
 
-			$scripts['try-aura-components'] = array(
+			$scripts['try-aura-enhancer'] = array(
 				'version' => $version,
 				'src'     => $asset_url . 'build/admin/enhancer/index.js',
 				'deps'    => $deps,
 			);
 		}
🧹 Nitpick comments (2)
src/frontend/tryon/index.tsx (2)

396-402: Mixed styling approach: inline styles alongside Tailwind classes.

The PR objective mentions replacing inline styles with Tailwind CSS classes, but this component uses both approaches inconsistently. Consider converting remaining inline styles (lines 397-401, 473-478, 502-507, 553-558, 565-570, 584-588, 592-600, 607-612, 623-628) to Tailwind utilities for consistency.

🔎 Example conversion
 <div
-  style={ {
-    display: 'flex',
-    gap: 8,
-    flexWrap: 'wrap',
-  } }
+  className="flex gap-2 flex-wrap"
 >

708-712: Consider more targeted MutationObserver scope.

The observer watches the entire document.body with subtree: true, which triggers on any DOM mutation. While injectButton() has an early return guard, consider scoping the observer to a more specific container (e.g., .woocommerce or the product form) to reduce unnecessary callback invocations.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e254a42 and 3a3a685.

📒 Files selected for processing (3)
  • inc/Assets.php
  • src/components/GroupButton.tsx
  • src/frontend/tryon/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/GroupButton.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/frontend/tryon/index.tsx (1)
src/components/index.tsx (1)
  • GroupButton (9-9)
inc/Assets.php (5)
inc/Enhancer.php (1)
  • __construct (17-19)
inc/Plugin.php (1)
  • __construct (25-28)
inc/Admin.php (1)
  • __construct (23-27)
inc/TryOn.php (1)
  • __construct (13-15)
inc/WooCommerce.php (1)
  • __construct (22-39)
🪛 PHPMD (2.15.0)
inc/Assets.php

174-174: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (8)
inc/Assets.php (6)

7-9: LGTM! Constructor correctly hooks asset registration.

The init hook is the appropriate place to register scripts and styles. The explicit priority 10 is redundant (it's the default) but doesn't cause issues.


14-22: LGTM! Well-structured asset registration flow.

The method cleanly orchestrates the registration process and provides an action hook for extensibility.


24-66: LGTM! Solid style registration with proper checks.

The file existence checks and filemtime() versioning are appropriate. The dependency chain (e.g., try-aura-tryon depending on try-aura-components) is correctly specified.


73-97: LGTM! Admin, components, and try-on script registration is correct.

These script blocks properly handle default dependencies and version fallbacks. The array_merge on line 119 correctly combines dependencies for the try-on script.

Also applies to: 116-127


139-145: LGTM! Standard style registration pattern.

The method correctly registers styles with proper fallbacks to TRYAURA_PLUGIN_VERSION.


155-163: LGTM! Script registration with proper translation setup.

The method correctly registers scripts with in_footer=true (appropriate for React apps) and sets up translations for internationalization.

src/frontend/tryon/index.tsx (2)

1-21: LGTM!

The imports and global type declarations are appropriate for a WordPress-based React component with Google GenAI integration.


23-52: LGTM!

The API key resolution logic handles both pre-loaded keys and REST API fallback gracefully, with appropriate error handling returning null on failure.

Comment on lines +173 to +176
public function enqueue_scripts( $scripts ) {
foreach ( $scripts as $handle => $script ) {
wp_enqueue_script( $handle );
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused loop variable.

The $script variable is assigned but never used. Consider simplifying the loop.

🔎 Proposed fix
 	public function enqueue_scripts( $scripts ) {
-		foreach ( $scripts as $handle => $script ) {
+		foreach ( array_keys( $scripts ) as $handle ) {
 			wp_enqueue_script( $handle );
 		}
 	}
📝 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.

Suggested change
public function enqueue_scripts( $scripts ) {
foreach ( $scripts as $handle => $script ) {
wp_enqueue_script( $handle );
}
public function enqueue_scripts( $scripts ) {
foreach ( array_keys( $scripts ) as $handle ) {
wp_enqueue_script( $handle );
}
}
🧰 Tools
🪛 PHPMD (2.15.0)

174-174: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

🤖 Prompt for AI Agents
In inc/Assets.php around lines 173 to 176, the foreach currently declares an
unused $script variable; replace the key=>value form with a keys-only iteration
(e.g., iterate over array_keys($scripts) and use $handle) so only the handles
are iterated and passed to wp_enqueue_script, removing the unused variable.

Comment on lines 317 to 320
const response: any = await ( ai as any ).models.generateContent( {
model: 'gemini-2.5-flash-image-preview',
contents,
} );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

@google/genai npm package API generateContent models

💡 Result:

Short answer — use ai.models.generateContent (or generateContentStream) on the Google GenAI client and pass a model string (e.g., "gemini-2.5-flash", "gemini-2.5-pro", "gemini-2.0-flash", "gemini-1.5-pro", etc.). You can also programmatically list available model names via the API. (ai.google.dev)

Quick example (Node.js):

  • import { GoogleGenAI } from "@google/genai"
  • ai.models.generateContent({ model: "gemini-2.5-flash", contents: "Write a short story." }) (npmjs.com)

Notes:

  • Stable/preview/experimental model name variants exist (aliases like "-latest" or "-preview-..."); consult the Models page or call models.list to get the exact, up‑to‑date model strings for your region. (ai.google.dev)

Citations:


🏁 Script executed:

# Check the codebase for package.json to see GenAI version
find . -name "package.json" | head -5 | xargs grep -l "google.*genai\|genai"

Repository: getdokan/tryAura

Length of output: 74


🏁 Script executed:

# Look at the file context to understand the API initialization and imports
sed -n '290,330p' src/frontend/tryon/index.tsx

Repository: getdokan/tryAura

Length of output: 1561


🏁 Script executed:

# Check imports at the top of the file
head -50 src/frontend/tryon/index.tsx

Repository: getdokan/tryAura

Length of output: 1516


Remove unnecessary type cast and verify model availability.

The API pattern ai.models.generateContent is correct per Google's documentation. However, the (ai as any) type cast is unnecessary—GoogleGenAI is properly typed. Additionally, verify that gemini-2.5-flash-image-preview is a current available model; the standard model names documented are gemini-2.5-flash, gemini-2.5-pro, etc., and Google recommends confirming preview/experimental model variants via the Models page or models.list().

🤖 Prompt for AI Agents
In src/frontend/tryon/index.tsx around lines 317 to 320, remove the unnecessary
(ai as any) type cast and call ai.models.generateContent directly (ai is already
typed as GoogleGenAI), and replace or verify the model string: confirm that
"gemini-2.5-flash-image-preview" is available (use the Models page or
ai.models.list()/models.list() to check) and switch to a supported model name
like "gemini-2.5-flash" or the correct preview identifier if confirmed; ensure
types remain correct after removing the cast.

…improve selection indication with CircleCheck, and streamline image handling logic.
… generated image display, enhance code organization, and improve user experience for image handling.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/frontend/tryon/UserImageSection/UseCamera.tsx (1)

14-17: Strengthen prop types for type safety.

The prop types remain overly permissive. capture: any, videoRef: any, and userImages: [] prevent effective type checking and should be replaced with proper types as suggested in the previous review.

🧹 Nitpick comments (4)
src/frontend/tryon/UserImageSection/UseCamera.tsx (2)

25-31: Add accessibility attributes to video element.

The video element lacks accessible labeling, making it difficult for screen reader users to understand its purpose. Add an aria-label or title attribute to describe the video stream.

🔎 Proposed fix
 					<video
 						ref={ videoRef }
 						autoPlay
 						playsInline
 						muted
+						aria-label={ __( 'Camera preview', 'try-aura' ) }
 						className="w-full h-full bg-[#000] block"
 					/>

33-40: Consider using standard Tailwind classes for better maintainability.

Both buttons use arbitrary color and spacing values (bg-[#000000], px-[50px], py-[10px]) and have identical styling. Consider:

  • Using bg-black instead of bg-[#000000]
  • Using standard Tailwind spacing scale (e.g., px-12 py-2.5) instead of arbitrary pixel values
  • Extracting the repeated button styles to a reusable class or component
🔎 Example refactor
 					<button
-						className="bg-[#000000] text-white px-[50px] py-[10px] cursor-pointer mx-auto"
+						className="bg-black text-white px-12 py-2.5 cursor-pointer mx-auto"
 						onClick={ capture }
 						disabled={ ! cameraActive || isBusy }
 					>

Apply the same change to the Recapture button on lines 54-60.

Also applies to: 54-60

src/frontend/tryon/Output/index.tsx (2)

5-8: Prefer standard Tailwind utilities over arbitrary values where possible.

The styling uses several arbitrary values that have standard Tailwind equivalents:

  • font-[500]font-medium
  • text-[14px]text-sm (14px)
  • Hardcoded color #25252D could use a theme color for consistency
🔎 Proposed refactor using standard utilities
-		<div className="w-1/3">
-			<div className="font-[500] text-[14px] text-[#25252D] mb-[20px]">
+		<div className="w-1/3">
+			<div className="font-medium text-sm text-gray-900 mb-5">
 				{ __( 'Generated', 'try-aura' ) }
 			</div>

Note: Adjust the theme color (e.g., text-gray-900) to match your design system if #25252D is a specific brand color defined in your Tailwind theme.


9-19: Improve image alt text and consider using standard Tailwind utilities.

Two improvements to consider:

  1. The alt text "Generated try-on" is generic. Consider making it more descriptive or dynamic based on context.
  2. Several arbitrary values could use standard utilities:
    • rounded-[8px]rounded-lg (8px)
    • min-h-[300px]min-h-[300px] is fine (no standard equivalent)
    • bg-[#F3F4F6]bg-gray-100 (standard gray-100 is #F3F4F6)
🔎 Proposed improvements
 			{ generatedUrl ? (
 				<img
 					src={ generatedUrl }
-					alt="Generated try-on"
-					className="max-w-full h-auto block rounded-[8px]"
+					alt="AI-generated try-on preview"
+					className="max-w-full h-auto block rounded-lg"
 				/>
 			) : (
-				<div className="bg-[#F3F4F6] rounded-[8px] min-h-[300px] flex items-center text-center justify-center p-[12px] font-[500] text-[14px] text-[#25252D]">
+				<div className="bg-gray-100 rounded-lg min-h-[300px] flex items-center text-center justify-center p-3 font-medium text-sm text-gray-900">
 					<span>{ message }</span>
 				</div>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a17bc0b and 55ec320.

📒 Files selected for processing (3)
  • src/frontend/tryon/Output/index.tsx
  • src/frontend/tryon/TryOnModal.tsx
  • src/frontend/tryon/UserImageSection/UseCamera.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/tryon/TryOnModal.tsx
🔇 Additional comments (2)
src/frontend/tryon/Output/index.tsx (2)

1-2: LGTM!

The import statement is clean and appropriate for the component's translation needs.


38-38: LGTM!

The default export is appropriate. The component naming recommendation was already covered in the earlier comment.

@@ -0,0 +1,38 @@
import { __ } from '@wordpress/i18n';

function Index( { generatedUrl, message, isBusy } ) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add TypeScript interface for props and use a descriptive component name.

The component has two major maintainability issues:

  1. No TypeScript types defined for props despite using a .tsx file extension
  2. Generic name "Index" makes the component harder to identify and import
🔎 Proposed fix with TypeScript types and better naming
+interface TryOnOutputProps {
+	generatedUrl?: string;
+	message: string;
+	isBusy: boolean;
+}
+
-function Index( { generatedUrl, message, isBusy } ) {
+function TryOnOutput( { generatedUrl, message, isBusy }: TryOnOutputProps ) {
 	return (

And update the export:

-export default Index;
+export default TryOnOutput;
📝 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.

Suggested change
function Index( { generatedUrl, message, isBusy } ) {
interface TryOnOutputProps {
generatedUrl?: string;
message: string;
isBusy: boolean;
}
function TryOnOutput( { generatedUrl, message, isBusy }: TryOnOutputProps ) {
🤖 Prompt for AI Agents
In src/frontend/tryon/Output/index.tsx around line 3, the component is named
"Index" and lacks TypeScript props types; define a Props interface (e.g.,
GeneratedOutputProps) with generatedUrl: string, message?: string, and isBusy?:
boolean, change the function signature to use that interface (function
GeneratedOutput(props: GeneratedOutputProps) or destructured params with types),
rename the component to a descriptive name like GeneratedOutput, update the
default export to export default GeneratedOutput, and search/update any imports
elsewhere to use the new component name/path.

Comment on lines +20 to +33
{ generatedUrl ? (
<div className="flex gap-2 mt-2 justify-end">
<a
className={ `bg-[#000000] text-white px-[50px] py-[10px] cursor-pointer mx-auto ${
isBusy ? 'opacity-60 cursor-not-allowed' : ''
}` }
href={ isBusy ? undefined : generatedUrl }
download={ isBusy ? undefined : 'tryon.png' }
aria-disabled={ isBusy }
>
{ __( 'Download', 'try-aura' ) }
</a>
</div>
) : null }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix accessibility: use a button element when disabled, not a disabled anchor.

The current implementation has a significant accessibility issue. Setting href={undefined} on an anchor when isBusy is true doesn't properly disable the link—it creates a link to the current page and remains keyboard-accessible but non-functional.

Additionally, there's a styling contradiction: the parent div uses justify-end but the anchor uses mx-auto, which conflicts.

🔎 Proposed fix with proper disabled state
 			{ generatedUrl ? (
-				<div className="flex gap-2 mt-2 justify-end">
-					<a
-						className={ `bg-[#000000] text-white px-[50px] py-[10px] cursor-pointer mx-auto ${
-							isBusy ? 'opacity-60 cursor-not-allowed' : ''
-						}` }
-						href={ isBusy ? undefined : generatedUrl }
-						download={ isBusy ? undefined : 'tryon.png' }
-						aria-disabled={ isBusy }
-					>
-						{ __( 'Download', 'try-aura' ) }
-					</a>
+				<div className="flex gap-2 mt-2 justify-center">
+					{ isBusy ? (
+						<button
+							disabled
+							className="bg-black text-white px-[50px] py-[10px] opacity-60 cursor-not-allowed"
+						>
+							{ __( 'Download', 'try-aura' ) }
+						</button>
+					) : (
+						<a
+							href={ generatedUrl }
+							download="tryon.png"
+							className="bg-black text-white px-[50px] py-[10px] cursor-pointer"
+						>
+							{ __( 'Download', 'try-aura' ) }
+						</a>
+					) }
 				</div>
-			) : null }
+			) }

This approach:

  • Uses a proper <button disabled> when busy
  • Uses a functional <a> when not busy
  • Fixes the layout contradiction (uses justify-center to match intent)
  • Simplifies the conditional logic

Committable suggestion skipped: line range outside the PR's diff.

…'try-aura-enhancer' for improved clarity and consistency
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
inc/Assets.php (2)

99-114: Critical: Undefined variable fallback on line 102.

Line 102 uses $deps as the fallback value ($deps = $asset['dependencies'] ?? $deps), but $deps is not defined in this scope. If $asset['dependencies'] is missing, this will trigger an undefined variable warning.

Note: The handle name was correctly changed to 'try-aura-enhancer' (line 109), addressing the previous overwrite concern. However, the $deps fallback issue remains unresolved.

🔎 Proposed fix
 		$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
 		if ( file_exists( $asset_file ) ) {
 			$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
-			$deps  = $asset['dependencies'] ?? $deps;
+			$deps  = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
 			$version = $asset['version'] ?? '1.0.0';
 
 			$scripts['try-aura-enhancer'] = array(
 				'version' => $version,
 				'src'     => $asset_url . 'build/admin/enhancer/index.js',
 				'deps'    => $deps,
 			);
 		}

173-177: Remove unused loop variable.

The $script variable is assigned but never used in the loop body. Simplify by iterating over handles only.

🔎 Proposed fix
 	public function enqueue_scripts( $scripts ) {
-		foreach ( $scripts as $handle => $script ) {
+		foreach ( array_keys( $scripts ) as $handle ) {
 			wp_enqueue_script( $handle );
 		}
 	}
🧹 Nitpick comments (1)
inc/Assets.php (1)

24-24: Consider making visibility consistent.

get_styles() is private while get_scripts() is public, yet both serve the same role and are only called internally by register_all_scripts(). For consistency and clearer API boundaries, consider making both private.

🔎 Proposed fix
-	public function get_scripts() {
+	private function get_scripts() {

Also applies to: 68-68

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 55ec320 and f85f97a.

📒 Files selected for processing (1)
  • inc/Assets.php
🧰 Additional context used
🧬 Code graph analysis (1)
inc/Assets.php (4)
inc/Enhancer.php (1)
  • __construct (17-19)
inc/Admin.php (1)
  • __construct (23-27)
inc/TryOn.php (1)
  • __construct (13-15)
inc/WooCommerce.php (1)
  • __construct (22-39)
🪛 PHPMD (2.15.0)
inc/Assets.php

174-174: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f85f97a and 581d018.

📒 Files selected for processing (2)
  • languages/try-aura.pot
  • package.json
✅ Files skipped from review due to trivial changes (1)
  • languages/try-aura.pot
🧰 Additional context used
🪛 Biome (2.1.2)
package.json

[error] 16-16: Expected a property but instead found '}'.

Expected a property here.

(parse)

🔇 Additional comments (5)
package.json (5)

3-3: LGTM: Version bump to 1.0.0

The major version bump appropriately reflects the significant refactoring and enhanced Tailwind CSS integration described in the PR objectives.


8-9: LGTM: Enhanced build scripts with progress feedback

Adding the --progress flag improves developer experience by providing visual feedback during build and development processes.


17-17: LGTM: Author attribution updated

The author field has been appropriately updated to reflect organizational ownership.


36-36: The postcss version ^8.5.6 is already the latest version in the 8.x series (released June 16, 2025). No action needed.

Likely an incorrect or invalid review comment.


31-31: Tailwind CSS v4 dependency is correctly configured and codebase is compatible—no breaking patterns found.

The codebase is already properly set up for Tailwind CSS v4:

  • postcss.config.js correctly uses @tailwindcss/postcss plugin
  • src/base-tailwind.scss uses v4 syntax (@import "tailwindcss/...", @theme block)
  • All Tailwind utilities in components (e.g., text-[14px], rounded-[5px], gap-[8px]) are v4-compatible
  • No deprecated v3 patterns found (no opacity utilities, no incorrect modifier placement, no CSS variable syntax issues)

No action required; the v4 upgrade is complete.

Likely an incorrect or invalid review comment.

Comment on lines +20 to +23
"@wordpress/eslint-plugin": "^22.22.0",
"@wordpress/prettier-config": "^4.36.0",
"@wordpress/scripts": "^31.1.0",
"@wordpress/stylelint-config": "^23.28.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

@wordpress/eslint-plugin latest version npm

💡 Result:

The latest npm version is 18.0.0 — published Dec 17, 2025. [1][2]

Sources:
[1] npm package page for @wordpress/eslint-plugin.
[2] WordPress Developer Handbook package page for @wordpress/eslint-plugin.


🌐 Web query:

@wordpress/prettier-config latest version npm

💡 Result:

Latest npm version: 4.31.0. Install with:
npm install --save-dev @wordpress/prettier-config. [1][2]

Sources:
[1] npm package page for @wordpress/prettier-config.
[2] WordPress developer documentation for @wordpress/prettier-config.


🌐 Web query:

@wordpress/scripts latest version npm

💡 Result:

I can’t confidently determine a single authoritative "latest" release from the web results right now (multiple sources show different recent versions). To get the exact current latest version on npm for @wordpress/scripts run locally:

  • npm view @wordpress/scripts version
    or
  • npm info @wordpress/scripts dist-tags

For reference, Snyk reports latest 31.1.0 (see Snyk). [1]

Sources:
[1] Snyk — @wordpress/scripts package info.


🌐 Web query:

@wordpress/stylelint-config latest version npm

💡 Result:

Latest npm version: @wordpress/stylelint-config v23.22.0. [1]

Sources:
[1] npm package page for @wordpress/stylelint-config. [2] WordPress Block Editor package reference.


Remove or downgrade to actual latest stable versions

The WordPress packages specified exceed the latest released stable versions on npm:

  • @wordpress/eslint-plugin ^22.22.0 (latest stable is 18.0.0)
  • @wordpress/prettier-config ^4.36.0 (latest stable is 4.31.0)
  • @wordpress/stylelint-config ^23.28.0 (latest stable is 23.22.0)

@wordpress/scripts ^31.1.0 matches the latest. Update the others to their actual latest stable versions to avoid npm resolution failures.

🤖 Prompt for AI Agents
In package.json around lines 20 to 23, the pinned @wordpress package versions
are higher than the actual latest stable releases on npm; change
"@wordpress/eslint-plugin" from "^22.22.0" to "^18.0.0", change
"@wordpress/prettier-config" from "^4.36.0" to "^4.31.0", and change
"@wordpress/stylelint-config" from "^23.28.0" to "^23.22.0"; after updating the
package.json, run your package manager (npm install or yarn install) to update
the lockfile and verify no resolution errors.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
package.json (1)

20-23: Verify WordPress package versions still exceed latest stable releases.

A previous review flagged that these versions exceed the latest stable releases on npm. Since this hasn't been addressed, please verify these packages are installable:

  • @wordpress/eslint-plugin@^22.22.0 (flagged latest: 18.0.0)
  • @wordpress/prettier-config@^4.36.0 (flagged latest: 4.31.0)
  • @wordpress/stylelint-config@^23.28.0 (flagged latest: 23.22.0)

If these versions don't exist, npm install will fail.

Run the following script to verify the package versions exist on npm:

#!/bin/bash
# Description: Verify the WordPress package versions exist on npm registry

echo "Checking @wordpress/[email protected]..."
npm view @wordpress/[email protected] version 2>/dev/null || echo "❌ Version 22.22.0 not found"

echo "Checking @wordpress/[email protected]..."
npm view @wordpress/[email protected] version 2>/dev/null || echo "❌ Version 4.36.0 not found"

echo "Checking @wordpress/[email protected]..."
npm view @wordpress/[email protected] version 2>/dev/null || echo "❌ Version 23.28.0 not found"

echo -e "\nLatest available versions:"
echo "@wordpress/eslint-plugin: $(npm view @wordpress/eslint-plugin version)"
echo "@wordpress/prettier-config: $(npm view @wordpress/prettier-config version)"
echo "@wordpress/stylelint-config: $(npm view @wordpress/stylelint-config version)"
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 581d018 and 9fe11b2.

📒 Files selected for processing (2)
  • bin/version-replace.js
  • package.json
🔇 Additional comments (1)
package.json (1)

38-38: The package.json already specifies the latest PostCSS 8.x version (8.5.6, released June 2025). No update is needed.

Likely an incorrect or invalid review comment.

'try-aura.php',
];

const { version } = JSON.parse( fs.readFileSync( 'package.json' ) );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for file read and JSON parse.

If package.json is missing, malformed, or lacks a version field, this script will crash with an unhelpful error. Add try-catch and validate the version field exists.

🔎 Proposed fix
-const { version } = JSON.parse( fs.readFileSync( 'package.json' ) );
+let version;
+try {
+	const pkg = JSON.parse( fs.readFileSync( 'package.json', 'utf8' ) );
+	version = pkg.version;
+	if ( ! version ) {
+		throw new Error( 'Version field missing in package.json' );
+	}
+} catch ( error ) {
+	console.error( 'Error reading package.json:', error.message );
+	process.exit( 1 );
+}
📝 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.

Suggested change
const { version } = JSON.parse( fs.readFileSync( 'package.json' ) );
let version;
try {
const pkg = JSON.parse( fs.readFileSync( 'package.json', 'utf8' ) );
version = pkg.version;
if ( ! version ) {
throw new Error( 'Version field missing in package.json' );
}
} catch ( error ) {
console.error( 'Error reading package.json:', error.message );
process.exit( 1 );
}
🤖 Prompt for AI Agents
In bin/version-replace.js around line 11, the code reads and parses package.json
directly with JSON.parse(fs.readFileSync(...)) and then destructures version
without any error handling; wrap the file read and JSON.parse in a try-catch,
validate that the parsed object has a non-empty string "version" property (throw
or log a clear error if missing/malformed), and on any error print a descriptive
message to stderr (e.g., console.error) and exit with a non-zero code so the
script fails visibly instead of crashing with an unhelpful stack trace.

…proved error handling and modify placeholder from 'TRYAURA_SINCE' to 'PLUGIN_SINCE'
Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
package.json (1)

21-24: WordPress package versions still exceed latest stable releases.

This issue was previously flagged but remains unresolved. The specified versions exceed the latest published on npm:

  • @wordpress/eslint-plugin ^22.22.0 (latest: 18.0.0)
  • @wordpress/prettier-config ^4.36.0 (latest: 4.31.0)
  • @wordpress/stylelint-config ^23.28.0 (latest: 23.22.0)

These non-existent versions will cause installation failures. Update to the actual latest stable versions.

🔎 Proposed fix
-    "@wordpress/eslint-plugin": "^22.22.0",
-    "@wordpress/prettier-config": "^4.36.0",
+    "@wordpress/eslint-plugin": "^18.0.0",
+    "@wordpress/prettier-config": "^4.31.0",
     "@wordpress/scripts": "^31.1.0",
-    "@wordpress/stylelint-config": "^23.28.0",
+    "@wordpress/stylelint-config": "^23.22.0",
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d24f7e and 1a946c5.

📒 Files selected for processing (4)
  • inc/TryOn.php
  • package.json
  • src/frontend/tryon/TryOnModal.tsx
  • src/frontend/tryon/index.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/frontend/tryon/index.tsx
  • inc/TryOn.php
  • src/frontend/tryon/TryOnModal.tsx
🔇 Additional comments (4)
package.json (4)

3-3: Verify the major version bump is intentional.

Jumping from version 0.0.1 to 1.0.0 is a significant semantic versioning change that typically indicates the plugin is production-ready. Ensure this aligns with your release strategy and that all breaking changes, features, and testing are complete.


8-9: LGTM!

Adding the --progress flag improves developer experience by providing visual feedback during builds.


18-18: LGTM!

Updating the author to the organization name is appropriate for an organizational project.


34-34: Tailwind CSS v4 is correctly implemented.

The codebase already follows Tailwind CSS v4 syntax:

  • PostCSS config uses @tailwindcss/postcss plugin
  • Imports use @import "tailwindcss/..." with layer directives (not @tailwind directives)
  • @theme directive is properly configured with custom colors
  • Component classes use v4 syntax (e.g., bg-primary, hover:bg-bg-primary, disabled:opacity-50)

No v3 syntax patterns found (!prefix, bg-opacity-*, bg-[--color]). Continue following v4 conventions.

package.json Outdated
Comment on lines 12 to 16
"packages-update": "wp-scripts packages-update",
"makepot": "wp i18n make-pot --domain='try-aura' --include='build,inc,lib,templates,try-aura.php' . --headers='{\"Report-Msgid-Bugs-To\":\"https://dokan.co/contact/\"}' --file-comment=\"Copyright (c) $(date +'%Y') Dokan Inc. All Rights Reserved.\" languages/try-aura.pot",
"zip": "rm -rf try-aura try-aura.zip && mkdir try-aura && cp -r build inc templates vendor try-aura.php try-aura/ && zip -r try-aura.zip try-aura && rm -rf try-aura",
"plugin-zip": "wp-scripts plugin-zip",
"test": "echo \"Error: no test specified\" && exit 1"
"version": "node bin/version-replace"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify paths referenced in package.json scripts

echo "Checking directories referenced in scripts..."
for dir in build inc templates vendor lib; do
  if [ -d "$dir" ]; then
    echo "$dir exists"
  else
    echo "$dir missing"
  fi
done

echo -e "\nChecking files referenced in scripts..."
for file in try-aura.php bin/version-replace.js bin/version-replace; do
  if [ -f "$file" ]; then
    echo "$file exists"
  else
    echo "$file missing"
  fi
done

echo -e "\nListing bin directory contents..."
if [ -d "bin" ]; then
  ls -la bin/ || echo "Could not list bin directory"
else
  echo "bin directory does not exist"
fi

echo -e "\nProject root structure:"
ls -la | head -20

Repository: getdokan/tryAura

Length of output: 1723


Fix the version script filename and verify referenced directories.

The version script references bin/version-replace but the file is bin/version-replace.js. Update the script to:

"version": "node bin/version-replace.js"

Additionally, the zip and makepot scripts reference directories that don't currently exist in the repository:

  • zip tries to copy build and vendor directories (will fail at runtime)
  • makepot includes build and lib in the source paths

Either create these directories or remove them from the scripts if they're no longer needed.

🤖 Prompt for AI Agents
In package.json around lines 12 to 16, the "version" npm script points to a
missing filename (bin/version-replace) and will fail; change it to call the
actual file (node bin/version-replace.js). Also verify the paths used by "zip"
and "makepot": either create the referenced directories (build, vendor, lib) or
remove them from the respective scripts so the commands won't fail at runtime;
update the scripts to only reference existing directories or add the missing
folders to the repo/build process.

Comment on lines +25 to 27
"fs-extra": "^11.3.3",
"replace-in-file": "^8.4.0",
"typescript": "^5.6.2"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

latest stable version fs-extra npm package 2025

💡 Result:

Latest stable version: 11.3.3 (as of Dec 29, 2025). [1][2]

Sources:
[1] Snyk package page (fs-extra) — latest version 11.3.3.
[2] jsDelivr package info (fs-extra) — version 11.3.3.


🌐 Web query:

latest stable version replace-in-file npm package 2025

💡 Result:

As of December 29, 2025 the latest stable release of the npm package replace-in-file is version 8.3.0. [1]

Sources:
[1] npm package page — replace-in-file (shows version 8.3.0).


Downgrade replace-in-file to the correct latest stable version.

fs-extra at ^11.3.3 is correct, but replace-in-file should be ^8.3.0, not ^8.4.0. Version 8.4.0 does not exist; the latest stable release is 8.3.0.

🤖 Prompt for AI Agents
In package.json around lines 25 to 27, the dependency "replace-in-file" is
pinned to a non-existent version (^8.4.0); change it to the correct latest
stable ^8.3.0, then run your package manager (npm install or yarn install) to
update the lockfile (package-lock.json or yarn.lock) and commit both
package.json and the updated lockfile; finally run the test/build commands to
verify no regressions.

Comment on lines 35 to 40
"@wordpress/api-fetch": "^7.36.0",
"@wordpress/components": "^30.9.0",
"@wordpress/element": "^6.36.0",
"lucide-react": "^0.561.0",
"postcss": "^8.5.6",
"tailwind-merge": "^3.4.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify lucide-react is actually used in the codebase
echo "Checking for lucide-react imports in source files..."
rg -n "from ['\"]lucide-react['\"]|require\(['\"]lucide-react['\"]|import.*lucide-react" --type-add 'code:*.{js,jsx,ts,tsx}' --type code -g '!node_modules' -g '!vendor' -g '!dist' -g '!build'
if [ $? -eq 0 ]; then
  echo "lucide-react imports found above."
else
  echo "No lucide-react imports detected in codebase."
fi

Repository: getdokan/tryAura

Length of output: 819


🌐 Web query:

latest stable versions npm packages @wordpress/api-fetch @wordpress/components @wordpress/element lucide-react postcss December 2025

💡 Result:

Here are the latest stable npm versions I found (as of Dec 29, 2025):

  • @wordpress/api-fetch — 7.30.0. [1]
  • @wordpress/components — 30.3.0. [2]
  • @wordpress/element — 6.32.0. [3]
  • lucide-react — 0.562.0. [4][5]
  • postcss — 8.5.6. [6]

Sources:

  1. npm package @wordpress/api-fetch. [turn0search0]
  2. npm package @wordpress/components. [turn0search1]
  3. @wordpress/element package listings (npm/registry). [turn2search6][turn2search0]
  4. lucide-react package metadata (Socket / Snyk showing v0.562.0). [turn1search3][turn1search4]
  5. (alternative lucide-react npm pages with recent releases). [turn3search2]
  6. npm package postcss. [turn0search6]

If you want, I can output these as npm install commands (exact syntax) or verify any additional packages.


🌐 Web query:

PostCSS 8.5.6 compatibility with Tailwind CSS v4

💡 Result:

Yes — PostCSS 8.5.6 is compatible with Tailwind CSS v4. Tailwind v4 requires PostCSS 8.x (the PostCSS plugin was moved to @tailwindcss/postcss), and the v4 docs and GitHub discussions show v4 is tested with PostCSS 8 (8.4+). Make sure you install and use @tailwindcss/postcss in your PostCSS config (not tailwindcss directly). [1][2]

Sources:
[1] Tailwind CSS — Installing with PostCSS.
[2] Tailwind CSS GitHub discussion: using @tailwindcss/postcss and PostCSS v8.


Fix WordPress package version constraints.

The specified WordPress package versions exceed currently published releases on npm and will cause dependency resolution failures:

  • @wordpress/api-fetch ^7.36.0 (latest is 7.30.0)
  • @wordpress/components ^30.9.0 (latest is 30.3.0)
  • @wordpress/element ^6.36.0 (latest is 6.32.0)

Update these to match the actual latest stable releases. lucide-react is actively used throughout the codebase and PostCSS 8.5.6 is compatible with Tailwind CSS v4.

🤖 Prompt for AI Agents
In package.json around lines 35 to 40, the WordPress package version constraints
exceed published npm releases and must be lowered; update @wordpress/api-fetch
to ^7.30.0, @wordpress/components to ^30.3.0, and @wordpress/element to ^6.32.0
in the dependencies block, leave lucide-react and postcss as-is, then run your
package manager (npm/yarn/pnpm) to reinstall and update the lockfile to ensure
dependency resolution succeeds.

…xtensibility, update text localization with `__`, and refactor code for flexibility and maintainability.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (10)
package.json (4)

16-16: Fix the version script filename (still unresolved).

The version script references bin/version-replace but the actual file is bin/version-replace.js. This script will fail when executed.

🔎 Proposed fix
-    "version": "node bin/version-replace"
+    "version": "node bin/version-replace.js"

21-24: WordPress dev dependency versions still exceed npm releases (still unresolved).

Three packages specify versions higher than what's published on npm:

  • @wordpress/eslint-plugin ^22.22.0 (latest: 18.0.0)
  • @wordpress/prettier-config ^4.36.0 (latest: 4.31.0)
  • @wordpress/stylelint-config ^23.28.0 (latest: 23.22.0)

This will cause npm install to fail.

🔎 Proposed fix
-    "@wordpress/eslint-plugin": "^22.22.0",
-    "@wordpress/prettier-config": "^4.36.0",
+    "@wordpress/eslint-plugin": "^18.0.0",
+    "@wordpress/prettier-config": "^4.31.0",
     "@wordpress/scripts": "^31.1.0",
-    "@wordpress/stylelint-config": "^23.28.0",
+    "@wordpress/stylelint-config": "^23.22.0",

26-26: Fix replace-in-file version (still unresolved).

Version ^8.4.0 does not exist on npm. The latest stable release is 8.3.0. This will cause npm install to fail.

🔎 Proposed fix
-    "replace-in-file": "^8.4.0",
+    "replace-in-file": "^8.3.0",

35-38: WordPress runtime dependency versions still exceed npm releases (still unresolved).

Three packages specify versions higher than published on npm:

  • @wordpress/api-fetch ^7.36.0 (latest: 7.30.0)
  • @wordpress/components ^30.9.0 (latest: 30.3.0)
  • @wordpress/element ^6.36.0 (latest: 6.32.0)

Additionally, verify @wordpress/hooks ^4.37.0 as its version wasn't checked in previous reviews.

🔎 Proposed fix for confirmed versions
-    "@wordpress/api-fetch": "^7.36.0",
-    "@wordpress/components": "^30.9.0",
-    "@wordpress/element": "^6.36.0",
+    "@wordpress/api-fetch": "^7.30.0",
+    "@wordpress/components": "^30.3.0",
+    "@wordpress/element": "^6.32.0",

Verify @wordpress/hooks version:

#!/bin/bash
# Description: Check latest version of @wordpress/hooks on npm.

echo "Checking @wordpress/hooks latest version..."
npm view @wordpress/hooks version

echo -e "\nAll recent versions:"
npm view @wordpress/hooks versions --json | jq -r '.[]' | tail -10
src/admin/enhancer/PreviewModal.tsx (4)

544-574: Polling loop lacks cancellation mechanism.

The polling loop (lines 545-569) runs for up to ~10 minutes without any cancellation mechanism. If the user closes the modal during video generation, the loop continues running and may attempt state updates on an unmounted component.

Consider using an AbortController or a ref-based cancellation flag that's checked each iteration and set to abort on component unmount.

🔎 Suggested approach using AbortController
// Add ref at component level
const abortControllerRef = useRef<AbortController | null>(null);

// Cleanup on unmount
useEffect(() => {
  return () => {
    abortControllerRef.current?.abort();
  };
}, []);

// In doGenerateVideo:
abortControllerRef.current?.abort();
abortControllerRef.current = new AbortController();
const signal = abortControllerRef.current.signal;

// In polling loop:
for (let i = 0; i < 60; i++) {
  if (signal.aborted) return;
  await new Promise((r) => setTimeout(r, 10000));
  const statusRes = await fetch(`${BASE_URL}/${operationName}`, {
    headers: { 'x-goog-api-key': apiKey },
    signal,
  });
  // ...
}

818-825: Missing text domain in translation call.

Line 823 uses __( 'Adding…' ) without the text domain parameter, while line 824 correctly includes 'try-aura'. This will cause translation lookup failures.

🔎 Proposed fix
 						{ (
 							activeTab === 'image'
 								? uploading
 								: videoUploading
 						)
-							? __( 'Adding…' )
+							? __( 'Adding…', 'try-aura' )
 							: __( 'Add to Media Library', 'try-aura' ) }

616-619: Inconsistent i18n: Video status message should be translated.

Line 618 uses a plain English string while other video messages use __() for translation.

🔎 Proposed fix
 			setVideoUrl( objectUrl );
 			setVideoStatus( 'done' );
-			setVideoMessage( 'Done' );
+			setVideoMessage( __( 'Done', 'try-aura' ) );
 			setVideoError( null );

257-265: Inconsistent i18n: Status messages should be translated.

Lines 259 and 264 use plain English strings while other messages in this component use __() for translation.

🔎 Proposed fix
 			setGeneratedUrl( dataUrl );
 			setStatus( 'done' );
-			setMessage( 'Done' );
+			setMessage( __( 'Done', 'try-aura' ) );
 			setError( null );
 		} catch ( e: any ) {
-			setError( e?.message || 'Generation failed.' );
+			setError( e?.message || __( 'Generation failed.', 'try-aura' ) );
 			setStatus( 'error' );
-			setMessage( 'Generation failed.' );
+			setMessage( __( 'Generation failed.', 'try-aura' ) );
 		}
src/admin/enhancer/index.tsx (2)

92-95: Copy-paste error in comment.

Line 94 comment says "Call original open first so the DOM exists" but this is the close override calling origClose.

🔎 Proposed fix
 		const origClose = wp.media.view.Modal.prototype.close;
 		wp.media.view.Modal.prototype.close = function ( ...args ) {
-			// Call original open first so the DOM exists
+			// Call original close first
 			const ret = origClose.apply( this, args );

8-17: Missing TypeScript declaration for tryAuraMediaRoots.

The code uses window.tryAuraMediaRoots (lines 56-60, 110, 131) but the Window interface only declares tryAura. This will cause TypeScript compilation errors.

🔎 Proposed fix
 declare global {
 	interface Window {
 		// eslint-disable-line @typescript-eslint/consistent-type-definitions
 		tryAura?: {
 			apiKey?: string;
 			restUrl?: string;
 			nonce?: string;
 		};
+		tryAuraMediaRoots?: Record< number, any >;
 	}
 }
🧹 Nitpick comments (2)
src/frontend/tryon/TryOnModal.tsx (2)

46-77: Consider stronger typing and error logging for camera operations.

Lines 62 and 138 use (video as any).srcObject type assertions, and lines 64, 140, 158 have empty catch blocks that silently swallow errors. While this defensive approach prevents crashes, it makes debugging harder.

Recommended improvements
  1. Add proper type definition for video element with srcObject:
interface VideoElementWithStream extends HTMLVideoElement {
	srcObject: MediaStream | null;
}
  1. Log caught errors to console for debugging:
 try {
-	( video as any ).srcObject = stream;
+	( video as VideoElementWithStream ).srcObject = stream;
 	await video.play?.();
-} catch {}
+} catch ( err ) {
+	console.warn( 'Video play failed:', err );
+}

407-471: Migrate Tailwind classes to v4 conventions and standard utilities.

The UI uses many arbitrary values (e.g., bg-[#fff], p-[16px_24px], text-[18px]) instead of Tailwind's standard design tokens. This reduces consistency and defeats Tailwind's design system benefits.

Key improvements based on Tailwind CSS v4:

  • Replace inset-[0px] with inset-0
  • Replace bg-[rgba(0,0,0,0.5)] with bg-black/50 (v4 slash syntax for opacity)
  • Replace bg-[#fff] with bg-white and bg-[#000000] with bg-black
  • Replace border-b-[1px] with border-b
  • Replace z-[200000] with a semantic z-index utility (e.g., z-[200] or define a custom layer)
  • Replace arbitrary spacing like p-[16px_24px] with px-6 py-4, mt-[24px] with mt-6, etc.
  • Replace arbitrary sizing like w-[16px] h-[16px] with size-4 or w-4 h-4
  • Replace arbitrary text sizes like text-[18px] with text-lg or text-base
  • Replace arbitrary font weights like font-[500] with font-medium
Example refactor for modal container (lines 407-420)
-<div className="ai-enhancer-modal fixed inset-[0px] bg-[rgba(0,0,0,0.5)] flex items-center justify-center z-[200000]">
-	<div className="ai-enhancer-modal__content bg-[#fff] rounded-[3px] max-w-[1000px] w-[90vw] h-auto">
-		<div className="flex flex-row justify-between border-b-[1px] border-b-[#E9E9E9] p-[16px_24px]">
-			<h2 className="m-0 font-[700] font-bold text-[18px] text-[#25252D]">
+<div className="ai-enhancer-modal fixed inset-0 bg-black/50 flex items-center justify-center z-[200]">
+	<div className="ai-enhancer-modal__content bg-white rounded max-w-[1000px] w-[90vw] h-auto">
+		<div className="flex flex-row justify-between border-b border-gray-200 px-6 py-4">
+			<h2 className="m-0 font-bold text-lg text-gray-900">
 				{ __( 'Try-On Product', 'try-aura' ) }
 			</h2>
 			<button
-				className="w-[16px] h-[16px] cursor-pointer p-0 m-0 bg-transparent"
+				className="size-4 cursor-pointer p-0 m-0 bg-transparent"

Apply similar patterns throughout the remaining class names.

Based on Tailwind CSS v4 documentation provided in the coding guidelines.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a946c5 and 2b408aa.

📒 Files selected for processing (6)
  • package.json
  • src/admin/enhancer/EnhanceButton.tsx
  • src/admin/enhancer/PreviewModal.tsx
  • src/admin/enhancer/index.tsx
  • src/frontend/tryon/TryOnModal.tsx
  • src/frontend/tryon/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/frontend/tryon/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/enhancer/PreviewModal.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
🔇 Additional comments (6)
package.json (2)

8-9: LGTM – Progress reporting improves developer experience.

Adding the --progress flag provides useful build feedback during development and production builds.


34-34: Tailwind CSS v4.1.18 packages are published on npm. Both @tailwindcss/[email protected] and [email protected] are available and valid.

src/admin/enhancer/EnhanceButton.tsx (1)

1-153: Well-structured component with good WordPress integration.

The implementation properly handles the media frame lifecycle, uses WordPress hooks for extensibility, and correctly manages React root cleanup when rendering the modal into document.body. The error handling with doAction for error events provides good debugging hooks.

Minor note: The empty catch {} blocks on lines 77 and 133 are acceptable for cleanup operations where failures are non-critical, but consider adding a brief comment explaining the intent (e.g., // Ignore unmount errors).

src/admin/enhancer/index.tsx (1)

24-63: Good React root lifecycle management.

The approach of storing React roots in a global registry keyed by timestamp and properly unmounting them on modal close is sound. This addresses the memory leak concerns from earlier reviews.

src/admin/enhancer/PreviewModal.tsx (2)

148-157: Good blob URL cleanup implementation.

The effect properly revokes blob URLs on unmount/change to prevent memory leaks. This is a best practice for handling object URLs.


751-838: Well-structured modal UI with proper accessibility.

The modal includes proper ARIA labeling for the close button, uses semantic structure, and correctly handles disabled states for buttons based on the current operation status.

Comment on lines +100 to +103
applyFilters(
'teyaura.is_block_editor_page_classname',
'block-editor-page'
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Typo in filter name: teyaura should be tryaura.

Line 101 uses teyaura.is_block_editor_page_classname while the rest of the codebase uses tryaura prefix. This inconsistency will prevent the filter from working as expected.

🔎 Proposed fix
 		setIsBlockEditorPage(
 			document.body.classList.contains(
 				applyFilters(
-					'teyaura.is_block_editor_page_classname',
+					'tryaura.is_block_editor_page_classname',
 					'block-editor-page'
 				)
 			)
📝 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.

Suggested change
applyFilters(
'teyaura.is_block_editor_page_classname',
'block-editor-page'
)
applyFilters(
'tryaura.is_block_editor_page_classname',
'block-editor-page'
)
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewModal.tsx around lines 100 to 103, the filter name
is misspelled as "teyaura.is_block_editor_page_classname" while the project uses
the "tryaura" prefix; update the filter key to
"tryaura.is_block_editor_page_classname" so it matches the rest of the codebase
and allows the filter to be applied correctly.

Comment on lines +200 to +201
setStatus( 'generating' );
setMessage( 'Thinking and generating…' );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent i18n: Status message should be translated.

Line 201 sets the message to a plain English string while other messages in this function use __() for translation.

🔎 Proposed fix
 			setStatus( 'generating' );
-			setMessage( 'Thinking and generating…' );
+			setMessage( __( 'Thinking and generating…', 'try-aura' ) );
📝 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.

Suggested change
setStatus( 'generating' );
setMessage( 'Thinking and generating…' );
setStatus( 'generating' );
setMessage( __( 'Thinking and generating…', 'try-aura' ) );
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewModal.tsx around lines 200 to 201, the status
message is set using a hard-coded English string; replace setMessage('Thinking
and generating…') with a translated call (use the existing __() i18n helper) so
the message reads something like setMessage(__('Thinking and generating…'));
ensure the same translation helper is used as other messages in this function
and that the translation key/text matches project conventions.

Comment on lines +309 to +314
const uploadRes = await fetch(
applyFilters(
'tryaura.media_upload_rest_api',
`${ restBase }'wp/v2/media'`
),
{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Malformed URL due to quotes inside template literal.

Line 312 constructs the URL as `${ restBase }'wp/v2/media'` which will produce a URL like https://example.com/'wp/v2/media' with literal single quotes. This will cause all image upload API calls to fail.

Compare to line 662 which correctly uses: restBase + 'wp/v2/media'

🔎 Proposed fix
 			const uploadRes = await fetch(
 				applyFilters(
 					'tryaura.media_upload_rest_api',
-					`${ restBase }'wp/v2/media'`
+					`${ restBase }wp/v2/media`
 				),
📝 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.

Suggested change
const uploadRes = await fetch(
applyFilters(
'tryaura.media_upload_rest_api',
`${ restBase }'wp/v2/media'`
),
{
const uploadRes = await fetch(
applyFilters(
'tryaura.media_upload_rest_api',
`${ restBase }wp/v2/media`
),
{
🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewModal.tsx around lines 309 to 314, the template
literal builds a malformed URL by including literal single quotes (`${ restBase
}'wp/v2/media'`) which yields e.g. https://example.com/'wp/v2/media' and breaks
uploads; replace the template literal with a proper concatenation or
correctly-formed template literal without extra quotes (e.g. restBase +
'wp/v2/media' or `${restBase}wp/v2/media`) so the resulting URL is valid, then
run the upload call to verify requests go to the correct endpoint.

Comment on lines +392 to +398
const addToCart = () => {
// @ts-ignore
const productId = applyFilters( 'tryaura.tryon.product_id', window?.tryAura?.productId );
if ( productId ) {
window.location.href = applyFilters( 'tryaura.tryon.add_to_cart_url', `?add-to-cart=${ productId }` );
}
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove @ts-ignore by adding proper type definitions.

Line 393 uses @ts-ignore to access window.tryAura.productId, which bypasses TypeScript's type safety. This is the same typing issue flagged in lines 79-120.

🔎 Proposed fix

Extend the TryAuraConfig interface mentioned in the earlier review comment to include productId:

// In src/types/global.d.ts
interface TryAuraConfig {
	apiKey?: string;
	restUrl?: string;
	nonce?: string;
	productId?: number | string;
}

Then remove the @ts-ignore:

 const addToCart = () => {
-	// @ts-ignore
-	const productId = applyFilters( 'tryaura.tryon.product_id', window?.tryAura?.productId );
+	const productId = applyFilters( 'tryaura.tryon.product_id', window.tryAura?.productId );
 	if ( productId ) {
 		window.location.href = applyFilters( 'tryaura.tryon.add_to_cart_url', `?add-to-cart=${ productId }` );
 	}
 };

Based on past review comments suggesting proper typing for window.tryAura.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/frontend/tryon/TryOnModal.tsx around lines 392 to 398, remove the `//
@ts-ignore` by adding a proper type for window.tryAura.productId: extend the
existing TryAuraConfig (e.g., in src/types/global.d.ts) to include productId?:
number | string, ensure the global Window interface references TryAuraConfig,
then remove the @ts-ignore and access window.tryAura.productId with the typed
definition so TypeScript no longer complains.

…rate it into `ImageConfigInputs` and `VideoConfigInputs` for improved reusability and cleaner code.
…rate it into `ImageConfigInputs` and `VideoConfigInputs` for improved reusability and cleaner code.
Copy link

@coderabbitai coderabbitai bot left a 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

♻️ Duplicate comments (1)
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (1)

133-134: Invalid Tailwind class and inconsistent i18n text domain.

These issues were previously flagged but remain unresolved:

  • Line 133: w-[500] is not a valid Tailwind class (missing unit like px)
  • Line 134: Missing text domain parameter in __() call
  • Line 148: Inconsistent text domain 'tryaura' should be 'try-aura'
🔎 Proposed fix
-			<span className="w-[500] text-[14px] mb-[8px]">
-				{ __( 'Prompt (Optional)' ) }
+			<span className="font-medium text-[14px] mb-[8px]">
+				{ __( 'Prompt (Optional)', 'try-aura' ) }
 			</span>
 			<textarea
 				className="border border-[#E9E9E9] placeholder-[#A5A5AA]"
 				value={ imageConfigData?.optionalPrompt ?? '' }
 				onChange={ ( e: any ) =>
 					setImageConfigData( {
 						...imageConfigData,
 						optionalPrompt: e.target.value,
 					} )
 				}
 				rows={ 3 }
 				placeholder={ __(
 					'Add any specific instructions (optional)',
-					'tryaura'
+					'try-aura'
 				) }
 			/>

Also applies to: 146-149

🧹 Nitpick comments (4)
src/admin/enhancer/PreviewSections/ConfigFooter.tsx (2)

4-4: Add TypeScript prop types for better type safety.

The component lacks type annotations for its props. Consider defining an interface to ensure type safety and improve developer experience.

🔎 Proposed type definition
+interface ConfigFooterProps {
+	generatedUrl?: string;
+	doGenerate: () => void;
+	isBusy: boolean;
+	uploading: boolean;
+}
+
-function ConfigFooter( { generatedUrl, doGenerate, isBusy, uploading } ) {
+function ConfigFooter( { generatedUrl, doGenerate, isBusy, uploading }: ConfigFooterProps ) {

22-22: Consider making the download filename dynamic.

The filename is hardcoded as 'enhanced.png', which may not be appropriate for all asset types (e.g., video content). Consider accepting a filename prop or deriving it from the content type.

🔎 Example approach
-function ConfigFooter( { generatedUrl, doGenerate, isBusy, uploading } ) {
+function ConfigFooter( { generatedUrl, doGenerate, isBusy, uploading, downloadFilename = 'enhanced.png' } ) {
 	return (
 		<div className="flex flex-row gap-[12px]">
 			{ generatedUrl ? (
 				<>
 					<Button
 						onClick={ doGenerate }
 						disabled={ isBusy || uploading }
 					>
 						{ isBusy
 							? __( 'Regenerating…', 'tryaura' )
 							: __( 'Regenerate', 'tryaura' ) }
 					</Button>
 
 					<Button
 						type="link"
 						variant="outline"
 						href={ isBusy ? undefined : generatedUrl }
-						download={ isBusy ? undefined : 'enhanced.png' }
+						download={ isBusy ? undefined : downloadFilename }
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (2)

17-26: Consider adding a TypeScript interface for props.

While the component works without explicit types, defining a TypeScript interface would improve type safety and developer experience.

🔎 Suggested TypeScript interface
interface ImageConfigInputsProps {
	isBlockEditorPage: boolean;
	isWoocommerceProductPage: boolean;
	imageConfigData: {
		backgroundType?: string;
		styleType?: string;
		imageSize?: string;
		optionalPrompt?: string;
	};
	setImageConfigData: (data: any) => void;
	generatedUrl?: string;
	doGenerate: () => void;
	isBusy: boolean;
	uploading: boolean;
}

function ImageConfigInputs({
	isBlockEditorPage,
	isWoocommerceProductPage,
	imageConfigData,
	setImageConfigData,
	generatedUrl,
	doGenerate,
	isBusy,
	uploading,
}: ImageConfigInputsProps) {
	// ...
}

139-139: Consider using proper React event type instead of any.

Replace any with React.ChangeEvent<HTMLTextAreaElement> for better type safety.

🔎 Suggested fix
-				onChange={ ( e: any ) =>
+				onChange={ ( e: React.ChangeEvent<HTMLTextAreaElement> ) =>
 					setImageConfigData( {
 						...imageConfigData,
 						optionalPrompt: e.target.value,
 					} )
 				}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2b408aa and c1992d1.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewSections/ConfigFooter.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/admin/enhancer/PreviewSections/ConfigFooter.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
🔇 Additional comments (3)
src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx (3)

30-93: LGTM! Well-structured conditional controls.

The background preference and output style selects are properly implemented with consistent i18n text domain usage and appropriate icon choices.


95-124: LGTM! Clean implementation of image size selector.

The conditional image size control is well-implemented with proper i18n and appropriate aspect ratio options.


153-163: LGTM! Clean integration of ConfigFooter.

The ConfigFooter component is properly integrated with the correct props, and the export is standard.

Comment on lines +14 to +15
? __( 'Regenerating…', 'tryaura' )
: __( 'Regenerate', 'tryaura' ) }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix inconsistent i18n text domains.

The text domain is inconsistent across the file: 'tryaura' (lines 14-15) vs. 'try-aura' (lines 30, 36-37). This breaks translation lookups and must be standardized.

🔎 Proposed fix
-					>
-						{ __( 'Download', 'try-aura' ) }
-					</Button>
+					>
+						{ __( 'Download', 'tryaura' ) }
+					</Button>
-				<Button onClick={ doGenerate } disabled={ isBusy || uploading }>
-					{ isBusy
-						? __( 'Generating…', 'try-aura' )
-						: __( 'Generate', 'try-aura' ) }
-				</Button>
+				<Button onClick={ doGenerate } disabled={ isBusy || uploading }>
+					{ isBusy
+						? __( 'Generating…', 'tryaura' )
+						: __( 'Generate', 'tryaura' ) }
+				</Button>

Also applies to: 30-30, 36-37

🤖 Prompt for AI Agents
In src/admin/enhancer/PreviewSections/ConfigFooter.tsx around lines 14-15, 30,
and 36-37, the i18n text domain is inconsistent ('tryaura' vs 'try-aura');
standardize all translation calls to use the single correct domain (use
'try-aura' across the file), replacing any occurrences of 'tryaura' with
'try-aura' in __(), _x(), and similar functions so all translations resolve
consistently.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (2)

174-175: Invalid Tailwind class and missing i18n text domain.

Line 174: w-[500] is not a valid Tailwind class (missing unit).
Line 175: The __() call is missing the 'try-aura' text domain parameter.


187-189: Inconsistent text domain: use 'try-aura' instead of 'tryaura'.

The placeholder uses 'tryaura' while the rest of the file uses 'try-aura'. This inconsistency prevents proper translation lookup.

src/admin/enhancer/PreviewSections/ConfigFooter.tsx (1)

14-15: Inconsistent i18n text domains must be standardized.

The text domain is inconsistent: lines 14-15 use 'tryaura', while lines 30, 36-37 use 'try-aura'. This breaks translation lookups. Standardize all calls to use 'try-aura' throughout the file.

Also applies to: 30-30, 36-37

🧹 Nitpick comments (1)
src/admin/enhancer/PreviewSections/ConfigFooter.tsx (1)

18-31: Consider simplifying Download button disabled state.

The Download button uses multiple overlapping mechanisms for the disabled state: conditional href/download, aria-disabled, inline styles for pointerEvents/opacity, and the disabled prop. While this works correctly, it could potentially be simplified depending on the Button component's capabilities.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1992d1 and 6741bfe.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewSections/ConfigFooter.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (1)
src/components/index.tsx (1)
  • ModernSelect (8-8)
src/admin/enhancer/PreviewSections/ConfigFooter.tsx (1)
src/components/index.tsx (1)
  • Button (7-7)
🔇 Additional comments (2)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (2)

140-163: Duration control properly fixed!

The previous bug where Duration was incorrectly bound to imageSize has been successfully resolved. The control now correctly uses videoConfigData.duration for both value and onChange.


195-201: ConfigFooter integration looks good.

The footer is properly wired with all necessary props, and the downloadName prop provides appropriate naming for the video download.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (2)

174-176: Invalid Tailwind class and missing i18n text domain.

w-[500] is not a valid Tailwind arbitrary value (needs units like px), and __() is missing the text domain.

-			<span className="w-[500] text-[14px] mb-[8px]">
-				{ __( 'Prompt (Optional)' ) }
+			<span className="font-medium text-[14px] mb-[8px]">
+				{ __( 'Prompt (Optional)', 'try-aura' ) }

187-190: Inconsistent text domain: use 'try-aura' instead of 'tryaura'.

The placeholder uses 'tryaura' while the rest of the file uses 'try-aura'. This will cause translation lookup failures.

 				placeholder={ __(
 					'Add any specific instructions (optional)',
-					'tryaura'
+					'try-aura'
 				) }
🧹 Nitpick comments (2)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (2)

1-1: Remove unused Button import.

Button is imported but never used in this component.

-import { Button, ModernSelect } from "../../../components";
+import { ModernSelect } from "../../../components";

21-28: Add TypeScript types for component props.

This is a .tsx file but the component props lack type definitions. Adding an interface improves type safety and developer experience.

+interface VideoConfigData {
+	styles: string;
+	cameraMotion: string;
+	aspectRatio: string;
+	duration: string;
+	optionalPrompt: string;
+}
+
+interface VideoConfigInputsProps {
+	videoConfigData: VideoConfigData;
+	setVideoConfigData: ( data: VideoConfigData ) => void;
+	videoUrl: string;
+	doGenerateVideo: () => void;
+	isVideoBusy: boolean;
+	videoUploading: boolean;
+}
+
-function VideoConfigInputs( {
+function VideoConfigInputs( {
 	videoConfigData,
 	setVideoConfigData,
-															videoUrl,
-															doGenerateVideo,
-															isVideoBusy,
-															videoUploading,
-} ) {
+	videoUrl,
+	doGenerateVideo,
+	isVideoBusy,
+	videoUploading,
+}: VideoConfigInputsProps ) {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1992d1 and 6741bfe.

📒 Files selected for processing (4)
  • src/admin/enhancer/PreviewSections/ConfigFooter.tsx
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/admin/enhancer/PreviewSections/ConfigSettings.tsx
  • src/admin/enhancer/PreviewSections/ImageConfigInputs.tsx
  • src/admin/enhancer/PreviewSections/ConfigFooter.tsx
🔇 Additional comments (3)
src/admin/enhancer/PreviewSections/VideoConfigInputs.tsx (3)

32-108: LGTM!

The Styles and Camera Motion controls are well-structured with proper state management, consistent i18n text domains, and appropriate icon usage.


110-164: LGTM!

The Aspect Ratio and Duration controls are correctly implemented with proper state bindings.


195-201: LGTM!

The ConfigFooter integration is clean with well-named props and a descriptive download filename.

Aunshon and others added 2 commits January 1, 2026 16:10
… it with Try-On functionality, and refine frontend logic for user authentication and custom login redirects.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (5)
inc/Assets.php (2)

189-193: Remove unused loop variable.

The $script variable is assigned but never used in the loop body.

🔎 Proposed fix
 	public function enqueue_scripts( $scripts ) {
-		foreach ( $scripts as $handle => $script ) {
+		foreach ( array_keys( $scripts ) as $handle ) {
 			wp_enqueue_script( $handle );
 		}
 	}

114-129: Critical: Undefined variable $deps on line 117.

The code attempts to use $deps as a fallback value before it's defined, which will cause a PHP notice. While the script handle collision has been fixed (now using try-aura-enhancer), this undefined variable issue remains.

🔎 Proposed fix
 		$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
 		if ( file_exists( $asset_file ) ) {
 			$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
-			$deps  = $asset['dependencies'] ?? $deps;
+			$deps  = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
 			// Ensure media-views is present so wp.media is available.
 			if ( ! in_array( 'media-views', $deps, true ) ) {
 				$deps[] = 'media-views';
 			}
 			$version = $asset['version'] ?? '1.0.0';
 
 			$scripts['try-aura-enhancer'] = array(
 				'version' => $version,
 				'src'     => $asset_url . 'build/admin/enhancer/index.js',
 				'deps'    => $deps,
 			);
 		}
inc/Plugin.php (1)

36-40: Fix typo in constant name.

The constant name TRYAURA_PLUGIN_ASSEST on line 39 contains a typo. It should be TRYAURA_PLUGIN_ASSETS (missing 'S').

🔎 Proposed fix
-		defined( 'TRYAURA_PLUGIN_ASSEST' ) || define( 'TRYAURA_PLUGIN_ASSEST', plugins_url( 'build', TRYAURA_FILE ) );
+		defined( 'TRYAURA_PLUGIN_ASSETS' ) || define( 'TRYAURA_PLUGIN_ASSETS', plugins_url( 'build', TRYAURA_FILE ) );
src/frontend/tryon/TryOnModal.tsx (2)

303-312: Remove @ts-ignore by using typed global declaration.

Lines 303 and 306 use @ts-ignore to access window.tryAura. A global type declaration already exists in src/frontend/tryon/index.tsx (lines 8-18). Remove these type suppressions:

🔎 Proposed fix
-			// @ts-ignore
 			const restUrl = window?.tryAura?.restUrl;
-			// @ts-ignore
 			const nonce = window?.tryAura?.nonce;

Ensure the global declaration is accessible by adding a reference or re-declaring it in this file if needed.


341-347: Remove @ts-ignore for productId access.

Line 342 uses @ts-ignore to access window.tryAura.productId. This is the same typing issue mentioned in earlier review comments. Use the global type declaration instead.

🧹 Nitpick comments (7)
inc/Rest/GenerateController.php (1)

76-89: Consider adding generation config and safety settings.

The request body only includes responseModalities. Consider adding:

  • Safety settings to filter harmful content
  • Generation config options (temperature, topK, topP) for better control
  • maxOutputTokens limit to prevent excessive responses
🔎 Example enhancement with safety settings
 		$body = array(
 			'contents'         => array( array( 'parts' => $parts ) ),
 			'generationConfig' => array( 'responseModalities' => array( 'IMAGE' ) ),
+			'safetySettings'   => array(
+				array( 'category' => 'HARM_CATEGORY_HARASSMENT', 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE' ),
+				array( 'category' => 'HARM_CATEGORY_HATE_SPEECH', 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE' ),
+				array( 'category' => 'HARM_CATEGORY_SEXUALLY_EXPLICIT', 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE' ),
+				array( 'category' => 'HARM_CATEGORY_DANGEROUS_CONTENT', 'threshold' => 'BLOCK_MEDIUM_AND_ABOVE' ),
+			),
 		);
inc/Plugin.php (1)

21-29: Consider making version property private.

The $version property is declared public but doesn't appear to need external access. Making it private would better encapsulate the implementation.

🔎 Proposed change
 	/**
 	 * Plugin version
 	 *
 	 * @var string
 	 */
-	public $version = '1.0.0';
+	private $version = '1.0.0';
src/frontend/tryon/index.tsx (5)

8-18: Fix eslint-disable comment placement.

Line 10 contains // eslint-disable-line but it appears to be intended for line 9. In TypeScript, when disabling the @typescript-eslint/consistent-type-definitions rule for preferring type over interface, the comment should be on the same line as the declaration or use eslint-disable-next-line on the preceding line.

🔎 Proposed fix
 declare global {
-	interface Window {
-		// eslint-line @typescript-eslint/consistent-type-definitions
+	// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
+	interface Window {
 		tryAura?: {
 			restUrl?: string;
 			nonce?: string;
 			apiKey?: string;
 			productId?: number;
+			loginUrl?: string;
 		};
 	}
 }

Note: Also consider adding loginUrl?: string; to the type definition since it's used on line 54.


24-43: Consider typing HTMLImageElement.dataset properly.

Lines 36 and 38 use (img as any) to access dataset and currentSrc, but both properties are part of the standard HTMLImageElement type. The type assertions can be removed:

🔎 Proposed refactor
 	const urls: string[] = imgs
 		.map( ( img ) => {
 			const large =
-				( img as any ).dataset?.large_image ||
+				img.dataset?.large_image ||
 				img.getAttribute( 'data-large_image' );
-			const cs = ( img as any ).currentSrc || img.getAttribute( 'src' );
+			const cs = img.currentSrc || img.getAttribute( 'src' );
 			return large || cs || '';
 		} )
 		.filter( Boolean ) as string[];

Note: TypeScript may warn about dataset.large_image because dataset keys use camelCase. If you need kebab-case access, keep the getAttribute approach instead:

const large = img.getAttribute('data-large_image') || img.dataset.largeImage;

45-50: Fix variable naming typo.

Lines 46-47 have inconsistent spelling: logedIn vs domLogedin. These should be loggedIn and domLoggedIn for consistency and correctness.

🔎 Proposed fix
 const isUserLoggedIn = (): boolean => {
-	const logedIn = select( 'core' ).getCurrentUser();
-	const domLogedin = document.body.classList.contains( 'logged-in' );
+	const loggedIn = select( 'core' ).getCurrentUser();
+	const domLoggedIn = document.body.classList.contains( 'logged-in' );
 
-	return logedIn && domLogedin;
+	return loggedIn && domLoggedIn;
 };

82-87: Remove unnecessary type cast and improve error handling.

Line 82 uses (createRoot as any) unnecessarily. The @wordpress/element package properly exports createRoot with correct types. Additionally, line 86 has an empty catch block that silently swallows unmount errors.

🔎 Proposed refactor
-	const root: any = ( createRoot as any )( container );
+	const root = createRoot( container );
 	const handleClose = () => {
 		try {
 			root.unmount?.();
-		} catch {}
+		} catch ( err ) {
+			// Log unmount errors in development
+			if ( process.env.NODE_ENV === 'development' ) {
+				// eslint-disable-next-line no-console
+				console.warn( 'Try-On modal unmount error:', err );
+			}
+		}
 		container?.remove();
 	};

149-153: Consider throttling or narrowing MutationObserver scope.

The MutationObserver on line 151 watches the entire document.body with subtree: true, which can trigger frequently on dynamic pages. For better performance, consider:

  1. Throttling the callback with requestAnimationFrame or a debounce utility
  2. Narrowing the observation to the product container (if known)
  3. Using a more specific mutation filter
🔎 Alternative implementation with throttling
 function init() {
 	injectButton();
+	let rafId: number | null = null;
+	const throttledInject = () => {
+		if ( rafId ) return;
+		rafId = requestAnimationFrame( () => {
+			injectButton();
+			rafId = null;
+		} );
+	};
-	const observer = new MutationObserver( () => injectButton() );
+	const observer = new MutationObserver( throttledInject );
 	observer.observe( document.body, { childList: true, subtree: true } );
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6741bfe and d3d6ec4.

📒 Files selected for processing (7)
  • inc/Assets.php
  • inc/Plugin.php
  • inc/Rest/GenerateController.php
  • inc/Rest/SettingsController.php
  • inc/TryOn.php
  • src/frontend/tryon/TryOnModal.tsx
  • src/frontend/tryon/index.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
inc/Plugin.php (7)
inc/Rest/GenerateController.php (2)
  • GenerateController (15-120)
  • __construct (26-28)
inc/Assets.php (2)
  • __construct (12-14)
  • Assets (8-194)
inc/Rest/SettingsController.php (2)
  • __construct (43-46)
  • SettingsController (16-115)
inc/TryOn.php (1)
  • __construct (16-19)
inc/Admin.php (2)
  • __construct (23-27)
  • Admin (12-104)
inc/Enhancer.php (2)
  • __construct (17-19)
  • Enhancer (12-48)
inc/WooCommerce.php (2)
  • __construct (22-39)
  • WooCommerce (12-157)
🪛 PHPMD (2.15.0)
inc/TryOn.php

77-77: Avoid unused parameters such as '$user'. (undefined)

(UnusedFormalParameter)

inc/Assets.php

190-190: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (17)
inc/Rest/SettingsController.php (4)

10-46: LGTM!

The class properties are properly typed, and the constructor correctly registers the REST routes on initialization.


51-75: LGTM!

The REST route registration properly secures both GET and POST endpoints with admin permissions and includes appropriate sanitization for POST parameters.


80-82: LGTM!

The permission check correctly restricts access to administrators only.


89-114: LGTM!

Both methods handle their responsibilities correctly:

  • get_settings() signature simplified appropriately (request not needed)
  • update_settings() gracefully handles missing parameters and applies proper sanitization
inc/TryOn.php (3)

16-19: LGTM!

The constructor properly registers both the enqueue action and the WooCommerce login redirect filter.


24-54: LGTM!

The enqueue logic correctly:

  • Restricts to WooCommerce product pages
  • Implements per-product opt-out via meta key (defaulting to enabled)
  • Provides necessary frontend data (REST URL, nonce, product ID, login URL)

61-67: LGTM!

The helper correctly prioritizes WooCommerce account page with an appropriate fallback to WordPress login.

inc/Plugin.php (1)

45-61: LGTM!

The plugin initialization correctly instantiates all components in the proper order, with appropriate admin-only instantiation for admin-specific features.

inc/Assets.php (5)

12-27: LGTM!

The constructor and registration flow are well-structured, with an extensibility hook (tryaura_register_scripts) for additional customization.


34-76: LGTM!

The style registration properly validates file existence and uses file modification time for cache busting. Dependency handling is correct.


88-99: LGTM!

The admin settings script registration includes appropriate WordPress dependencies with sensible fallbacks.


101-112: LGTM!

The components library registration is correct with appropriate React dependencies.


131-143: LGTM!

The frontend Try-On script properly merges dependencies and includes all necessary WordPress data/API dependencies.

src/frontend/tryon/index.tsx (1)

103-133: LGTM! Clean button injection with proper hooks.

The button injection logic is well-structured:

  • Uses WordPress filters for customization points
  • Prevents duplicate injections
  • Preserves styling from existing button
  • Properly fires action hooks for extensibility
src/frontend/tryon/TryOnModal.tsx (3)

38-44: LGTM! Proper lifecycle management.

The effect correctly adds a body class for styling and ensures camera cleanup on unmount, preventing resource leaks.


237-262: LGTM! Robust image data conversion.

The function correctly handles both data URLs and remote images, with proper base64 encoding for the AI API.


354-425: LGTM! Well-structured modal UI.

The modal layout is clean and accessible:

  • Proper ARIA labels on interactive elements
  • Correct disabled state logic based on inputs and busy status
  • All user-facing strings are internationalized
  • Good component composition with child sections

Comment on lines +52 to +74
public function handle_generation( WP_REST_Request $request ) {
$params = $request->get_json_params();
$prompt = $params['prompt'] ?? '';
$ref_images = $params['images'] ?? array();

$api_key = get_option( 'try_aura_api_key', '' );

$model = 'gemini-2.5-flash-image';
$api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}";

$prepared_images = array();
foreach ( $ref_images as $base64_data ) {
if ( preg_match( '/^data:image\/(\w+);base64,/', $base64_data, $type ) ) {
$base64_data = substr( $base64_data, strpos( $base64_data, ',' ) + 1 );
$mime_type = 'image/' . $type[1];
} else {
$mime_type = 'image/jpeg';
}
$prepared_images[] = array(
'mime_type' => $mime_type,
'data' => $base64_data,
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Missing input validation and API key checks.

Several validation issues that could cause failures or security problems:

  1. No API key validation: If try_aura_api_key is empty, the API call will fail with an unclear error
  2. No prompt validation: Empty prompts may be accepted
  3. No image array validation: Malformed base64 or non-array data could cause errors
  4. Silent mime type fallback: Line 68 defaults to image/jpeg when regex fails, potentially hiding data format issues
  5. No size limits: Large images or many images could exceed API limits or timeout
🔎 Proposed fix for API key and basic validation
 	public function handle_generation( WP_REST_Request $request ) {
 		$params     = $request->get_json_params();
 		$prompt     = $params['prompt'] ?? '';
 		$ref_images = $params['images'] ?? array();
 
 		$api_key = get_option( 'try_aura_api_key', '' );
+		
+		if ( empty( $api_key ) ) {
+			return new WP_REST_Response( 
+				array( 'error' => 'API key not configured. Please configure the API key in settings.' ), 
+				400 
+			);
+		}
+		
+		if ( empty( $prompt ) ) {
+			return new WP_REST_Response( 
+				array( 'error' => 'Prompt is required.' ), 
+				400 
+			);
+		}
+		
+		if ( ! is_array( $ref_images ) ) {
+			return new WP_REST_Response( 
+				array( 'error' => 'Images must be an array.' ), 
+				400 
+			);
+		}
+		
+		// Limit number of images to prevent abuse
+		if ( count( $ref_images ) > 10 ) {
+			return new WP_REST_Response( 
+				array( 'error' => 'Maximum 10 images allowed.' ), 
+				400 
+			);
+		}
 
 		$model   = 'gemini-2.5-flash-image';
📝 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.

Suggested change
public function handle_generation( WP_REST_Request $request ) {
$params = $request->get_json_params();
$prompt = $params['prompt'] ?? '';
$ref_images = $params['images'] ?? array();
$api_key = get_option( 'try_aura_api_key', '' );
$model = 'gemini-2.5-flash-image';
$api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}";
$prepared_images = array();
foreach ( $ref_images as $base64_data ) {
if ( preg_match( '/^data:image\/(\w+);base64,/', $base64_data, $type ) ) {
$base64_data = substr( $base64_data, strpos( $base64_data, ',' ) + 1 );
$mime_type = 'image/' . $type[1];
} else {
$mime_type = 'image/jpeg';
}
$prepared_images[] = array(
'mime_type' => $mime_type,
'data' => $base64_data,
);
}
public function handle_generation( WP_REST_Request $request ) {
$params = $request->get_json_params();
$prompt = $params['prompt'] ?? '';
$ref_images = $params['images'] ?? array();
$api_key = get_option( 'try_aura_api_key', '' );
if ( empty( $api_key ) ) {
return new WP_REST_Response(
array( 'error' => 'API key not configured. Please configure the API key in settings.' ),
400
);
}
if ( empty( $prompt ) ) {
return new WP_REST_Response(
array( 'error' => 'Prompt is required.' ),
400
);
}
if ( ! is_array( $ref_images ) ) {
return new WP_REST_Response(
array( 'error' => 'Images must be an array.' ),
400
);
}
// Limit number of images to prevent abuse
if ( count( $ref_images ) > 10 ) {
return new WP_REST_Response(
array( 'error' => 'Maximum 10 images allowed.' ),
400
);
}
$model = 'gemini-2.5-flash-image';
$api_url = "https://generativelanguage.googleapis.com/v1beta/models/{$model}:generateContent?key={$api_key}";
$prepared_images = array();
foreach ( $ref_images as $base64_data ) {
if ( preg_match( '/^data:image\/(\w+);base64,/', $base64_data, $type ) ) {
$base64_data = substr( $base64_data, strpos( $base64_data, ',' ) + 1 );
$mime_type = 'image/' . $type[1];
} else {
$mime_type = 'image/jpeg';
}
$prepared_images[] = array(
'mime_type' => $mime_type,
'data' => $base64_data,
);
}
🤖 Prompt for AI Agents
In inc/Rest/GenerateController.php around lines 52-74, add input validation and
API key checks: verify get_option('try_aura_api_key') is non-empty and return a
WP_REST_Response/WP_Error with a clear 4xx status if missing; validate $prompt
by trimming and rejecting empty prompts (optionally enforce a max length);
ensure $ref_images is an array and enforce limits (e.g., max image count and
per-image size in bytes after base64 decode) to prevent oversized payloads; for
each image, require a base64 data URL matching
/^data:image\/(png|jpeg|jpg|gif);base64,/, decode and verify base64 validity and
decoded size, only accept allowed mime types, and if the regex fails do not
silently default to image/jpeg but instead return a validation error; sanitize
inputs and return clear REST errors for any validation failures before
proceeding to prepare the request for the external API.

Comment on lines +100 to +108
if ( is_wp_error( $response ) ) {
return new WP_REST_Response( array( 'error' => $response->get_error_message() ), 500 );
}

$data = json_decode( wp_remote_retrieve_body( $response ), true );

if ( isset( $data['error'] ) ) {
return new WP_REST_Response( $data['error'], 400 );
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Improve error handling and logging.

Current error handling has issues:

  1. Incorrect status code: Line 107 returns 400 for API errors, but external service failures should return 500 (server error)
  2. No logging: API failures and errors should be logged for debugging
  3. Error structure assumption: Assumes $data['error'] exists without validation
  4. Limited error details: Original error from API not preserved for admin debugging
🔎 Proposed improvements
 		if ( is_wp_error( $response ) ) {
+			error_log( 'Gemini API WP_Error: ' . $response->get_error_message() );
 			return new WP_REST_Response( array( 'error' => $response->get_error_message() ), 500 );
 		}
 
 		$data = json_decode( wp_remote_retrieve_body( $response ), true );
+		
+		$http_code = wp_remote_retrieve_response_code( $response );
+		if ( $http_code < 200 || $http_code >= 300 ) {
+			$error_message = $data['error']['message'] ?? 'API request failed';
+			error_log( sprintf( 'Gemini API error (HTTP %d): %s', $http_code, wp_json_encode( $data ) ) );
+			return new WP_REST_Response( 
+				array( 'error' => $error_message ), 
+				500 
+			);
+		}
 
 		if ( isset( $data['error'] ) ) {
-			return new WP_REST_Response( $data['error'], 400 );
+			error_log( 'Gemini API error response: ' . wp_json_encode( $data['error'] ) );
+			return new WP_REST_Response( $data['error'], 500 );
 		}
📝 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.

Suggested change
if ( is_wp_error( $response ) ) {
return new WP_REST_Response( array( 'error' => $response->get_error_message() ), 500 );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( isset( $data['error'] ) ) {
return new WP_REST_Response( $data['error'], 400 );
}
if ( is_wp_error( $response ) ) {
error_log( 'Gemini API WP_Error: ' . $response->get_error_message() );
return new WP_REST_Response( array( 'error' => $response->get_error_message() ), 500 );
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
$http_code = wp_remote_retrieve_response_code( $response );
if ( $http_code < 200 || $http_code >= 300 ) {
$error_message = $data['error']['message'] ?? 'API request failed';
error_log( sprintf( 'Gemini API error (HTTP %d): %s', $http_code, wp_json_encode( $data ) ) );
return new WP_REST_Response(
array( 'error' => $error_message ),
500
);
}
if ( isset( $data['error'] ) ) {
error_log( 'Gemini API error response: ' . wp_json_encode( $data['error'] ) );
return new WP_REST_Response( $data['error'], 500 );
}
🤖 Prompt for AI Agents
In inc/Rest/GenerateController.php around lines 100-108, update the error
handling so external service failures return HTTP 500 instead of 400, log both
the WP_Error and any API error details, validate that json_decode returned an
array before accessing $data['error'], and preserve the original API error
message for admin debugging: on is_wp_error($response) log the
WP_Error->get_error_message() and return a 500 with a generic error; after
decoding, if $data is not an array log the raw body and return 500; if
$data['error'] exists validate its type, log the full API error payload (and
HTTP code/response body), return a 500 to clients but include more detailed
'details' only when current_user_can('manage_options') so admins can debug while
normal users get a generic message.

Comment on lines +110 to +112
$image = $data['candidates'][0]['content']['parts'][0]['inlineData']['data'] ?? null;
return new WP_REST_Response( array( 'image' => $image ), 200 );
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate image data before returning success.

The code returns HTTP 200 even if $image is null (when the expected response structure is missing). This could lead to frontend errors when trying to use the null image data.

🔎 Proposed fix
 		$image = $data['candidates'][0]['content']['parts'][0]['inlineData']['data'] ?? null;
+		
+		if ( null === $image || empty( $image ) ) {
+			error_log( 'Gemini API returned unexpected response structure: ' . wp_json_encode( $data ) );
+			return new WP_REST_Response( 
+				array( 'error' => 'No image data in API response' ), 
+				500 
+			);
+		}
+		
 		return new WP_REST_Response( array( 'image' => $image ), 200 );
 	}
📝 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.

Suggested change
$image = $data['candidates'][0]['content']['parts'][0]['inlineData']['data'] ?? null;
return new WP_REST_Response( array( 'image' => $image ), 200 );
}
$image = $data['candidates'][0]['content']['parts'][0]['inlineData']['data'] ?? null;
if ( null === $image || empty( $image ) ) {
error_log( 'Gemini API returned unexpected response structure: ' . wp_json_encode( $data ) );
return new WP_REST_Response(
array( 'error' => 'No image data in API response' ),
500
);
}
return new WP_REST_Response( array( 'image' => $image ), 200 );
}
🤖 Prompt for AI Agents
In inc/Rest/GenerateController.php around lines 110 to 112, validate the
extracted $image before returning success: if $image is null (meaning the
expected response structure is missing) return an error REST response (or
WP_Error) with a non-200 status and a clear message (e.g. 400/422 or 500)
instead of returning a 200 with null data; otherwise return the 200 response
with the image. Ensure the error response includes a descriptive message and
consistent response shape so the frontend can handle failures gracefully.

Comment on lines +114 to +119
/**
* Check if the current user is logged in.
*/
public function permissions_check() {
return is_user_logged_in();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's read the full GenerateController.php file
cat -n inc/Rest/GenerateController.php

Repository: getdokan/tryAura

Length of output: 3821


🏁 Script executed:

# Search for rate limiting or quota management
rg -i "rate|limit|quota|throttle" --type php

Repository: getdokan/tryAura

Length of output: 546


🏁 Script executed:

# Check for any permission-related code in the Rest directory
fd . inc/Rest --type f | head -20

Repository: getdokan/tryAura

Length of output: 123


🏁 Script executed:

# Search for permission checks in other endpoints
rg "permissions_check" --type php -A 3

Repository: getdokan/tryAura

Length of output: 1261


🏁 Script executed:

# Look for any external API integration or cost tracking
rg -i "api|external|key|token" --type php inc/Rest

Repository: getdokan/tryAura

Length of output: 1964


Restrict image generation endpoint to admin users or implement rate limiting.

The endpoint permits any authenticated user to call Google's Generative Language API (Gemini model) without rate limiting or quota checks. Since this external API incurs usage costs, this creates risk for:

  • Uncontrolled API cost exposure
  • Quota exhaustion from legitimate or malicious usage
  • No mechanism to prevent abuse

Compare with SettingsController, which correctly restricts to manage_options capability. Either require admin-level permissions or implement per-user rate limiting with quota management.

🤖 Prompt for AI Agents
In inc/Rest/GenerateController.php around lines 114 to 119, the
permissions_check currently allows any authenticated user to call the
image-generation endpoint; change it to require admin-level capability or add
per-user rate/quota checks. Replace the simple is_user_logged_in() check with
either a capability check such as current_user_can('manage_options') to restrict
to admins, or integrate a rate-limiting/quota mechanism that tracks calls per
user (and enforces limits, resets, and logging) before forwarding requests to
the external API; ensure the chosen approach returns proper
WP_REST_Response/WP_Error codes for unauthorized or rate-limited requests and
include tests/documentation for the new behavior.

Comment on lines +77 to +83
public function redirect_to_try_on( $redirect, $user ) {
if ( ! empty( $_REQUEST['tryaura_redirect_to'] ) ) {
return esc_url_raw( wp_unslash( $_REQUEST['tryaura_redirect_to'] ) );
}

return $redirect;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Add validation to prevent open redirect vulnerability.

The method accepts a redirect URL from $_REQUEST without verifying it's an internal URL. This could enable open redirect attacks where malicious actors redirect users to external phishing sites after login.

Note: The unused $user parameter is required by the woocommerce_login_redirect filter signature, so the static analysis warning is a false positive.

🔎 Proposed fix to validate redirect URL
 	public function redirect_to_try_on( $redirect, $user ) {
 		if ( ! empty( $_REQUEST['tryaura_redirect_to'] ) ) {
-			return esc_url_raw( wp_unslash( $_REQUEST['tryaura_redirect_to'] ) );
+			$redirect_to = esc_url_raw( wp_unslash( $_REQUEST['tryaura_redirect_to'] ) );
+			
+			// Only allow internal redirects to prevent open redirect attacks
+			if ( wp_validate_redirect( $redirect_to, false ) ) {
+				return $redirect_to;
+			}
 		}
 
 		return $redirect;
 	}
📝 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.

Suggested change
public function redirect_to_try_on( $redirect, $user ) {
if ( ! empty( $_REQUEST['tryaura_redirect_to'] ) ) {
return esc_url_raw( wp_unslash( $_REQUEST['tryaura_redirect_to'] ) );
}
return $redirect;
}
public function redirect_to_try_on( $redirect, $user ) {
if ( ! empty( $_REQUEST['tryaura_redirect_to'] ) ) {
$redirect_to = esc_url_raw( wp_unslash( $_REQUEST['tryaura_redirect_to'] ) );
// Only allow internal redirects to prevent open redirect attacks
if ( wp_validate_redirect( $redirect_to, false ) ) {
return $redirect_to;
}
}
return $redirect;
}
🧰 Tools
🪛 PHPMD (2.15.0)

77-77: Avoid unused parameters such as '$user'. (undefined)

(UnusedFormalParameter)

Comment on lines +52 to +64
const goToLogin = () => {
const loginUrl = ( window.location.href =
window?.tryAura?.loginUrl ?? '/wp-login.php' );

const currentUrl = new URL( window.location.href );

// add your custom param
currentUrl.searchParams.set( 'tryOnAutoOpen', 'true' );
// Redirect to My Account with the current product URL as a parameter
window.location.href = `${ loginUrl }?tryaura_redirect_to=${ encodeURIComponent(
currentUrl.toString()
) }`;
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix critical logic error in goToLogin.

Line 53 has a logic error that causes immediate redirection. The assignment operator is in the wrong position:

const loginUrl = ( window.location.href = window?.tryAura?.loginUrl ?? '/wp-login.php' );

This assigns to window.location.href first (triggering immediate navigation), then stores that result in loginUrl. The assignment to window.location.href should be removed:

🔎 Proposed fix
 const goToLogin = () => {
-	const loginUrl = ( window.location.href =
-		window?.tryAura?.loginUrl ?? '/wp-login.php' );
+	const loginUrl = window?.tryAura?.loginUrl ?? '/wp-login.php';
 
 	const currentUrl = new URL( window.location.href );
🤖 Prompt for AI Agents
In src/frontend/tryon/index.tsx around lines 52 to 64, the code mistakenly
assigns window.location.href when creating loginUrl which causes an immediate
navigation; change loginUrl to be read-only (const loginUrl =
window?.tryAura?.loginUrl ?? '/wp-login.php'), compute currentUrl from the
existing window.location.href before any assignment, then set
currentUrl.searchParams and finally perform a single assignment to
window.location.href to redirect to
`${loginUrl}?tryaura_redirect_to=${encodeURIComponent(currentUrl.toString())}`.

await video.play?.();
} catch {}
}
setMessage( 'Camera active — click Capture when ready' );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Internationalize hardcoded status message.

Line 66 contains a hardcoded English string that should be wrapped with __() for i18n support:

🔎 Proposed fix
-			setMessage( 'Camera active — click Capture when ready' );
+			setMessage( __( 'Camera active — click Capture when ready', 'try-aura' ) );
🤖 Prompt for AI Agents
In src/frontend/tryon/TryOnModal.tsx around line 66, replace the hardcoded
English status string setMessage('Camera active — click Capture when ready')
with a call to the i18n helper (e.g. setMessage(__('Camera active — click
Capture when ready'))); ensure the translation function is imported or
referenced consistently with the codebase (add the import if missing) and add
the new key/string to the translation catalogue so the message is translatable.

canvas.toDataURL( 'image/jpeg', 0.95 )
);
setUserImages( [ dataUrl ] );
setMessage( 'Photo captured. Click Try to generate.' );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Internationalize hardcoded status message.

Line 172 has a hardcoded English string that needs i18n:

🔎 Proposed fix
-		setMessage( 'Photo captured. Click Try to generate.' );
+		setMessage( __( 'Photo captured. Click Try to generate.', 'try-aura' ) );
📝 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.

Suggested change
setMessage( 'Photo captured. Click Try to generate.' );
setMessage( __( 'Photo captured. Click Try to generate.', 'try-aura' ) );
🤖 Prompt for AI Agents
In src/frontend/tryon/TryOnModal.tsx around line 172, replace the hardcoded
English status string passed to setMessage with a translated string (e.g.,
setMessage(t('tryOn.photoCaptured'))), ensure the i18n hook or translation util
(such as useTranslation from react-i18next or your project's i18n helper) is
imported and available in the component, and add the corresponding translation
key (tryOn.photoCaptured: "Photo captured. Click Try to generate.") to the
locale files (at minimum en.json) so the message is internationalized.

Aunshon and others added 3 commits January 1, 2026 18:25
…dd Dashboard and Settings pages, enhance menu handling, and update dependencies.
…dd Dashboard and Settings pages, enhance menu handling, and update dependencies.
…lerplate

enhance/admin-settings-setup-boilerplate
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (5)
inc/Assets.php (2)

114-129: Still unresolved: undefined $deps variable on Line 117.

This critical issue from the previous review remains unfixed. Line 117 references $deps in the fallback expression before it's defined in this block, which will trigger an undefined variable warning.

🔎 Proposed fix
 	$asset_file = $asset_path . 'build/admin/enhancer/index.asset.php';
 	if ( file_exists( $asset_file ) ) {
 		$asset = include $asset_file; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
-		$deps  = $asset['dependencies'] ?? $deps;
+		$deps  = $asset['dependencies'] ?? array( 'wp-element', 'try-aura-components' );
 		// Ensure media-views is present so wp.media is available.
 		if ( ! in_array( 'media-views', $deps, true ) ) {
 			$deps[] = 'media-views';
 		}
 		$version = $asset['version'] ?? '1.0.0';
 
 		$scripts['try-aura-enhancer'] = array(
 			'version' => $version,
 			'src'     => $asset_url . 'build/admin/enhancer/index.js',
 			'deps'    => $deps,
 		);
 	}

189-192: Still unresolved: unused loop variable.

This issue from the previous review remains unfixed. The $script variable is assigned but never used in the loop body.

🔎 Proposed fix
 public function enqueue_scripts( $scripts ) {
-	foreach ( $scripts as $handle => $script ) {
+	foreach ( array_keys( $scripts ) as $handle ) {
 		wp_enqueue_script( $handle );
 	}
 }
package.json (3)

21-24: Still unresolved: WordPress package versions exceed published releases.

This critical issue from the previous review remains unfixed. The specified versions for these WordPress packages don't exist on npm:

  • @wordpress/eslint-plugin ^22.22.0 (latest stable: 18.0.0)
  • @wordpress/prettier-config ^4.36.0 (latest stable: 4.31.0)
  • @wordpress/stylelint-config ^23.28.0 (latest stable: 23.22.0)

These will cause dependency resolution failures during npm install.

🔎 Proposed fix
 "devDependencies": {
-  "@wordpress/eslint-plugin": "^22.22.0",
-  "@wordpress/prettier-config": "^4.36.0",
+  "@wordpress/eslint-plugin": "^18.0.0",
+  "@wordpress/prettier-config": "^4.31.0",
   "@wordpress/scripts": "^31.1.0",
-  "@wordpress/stylelint-config": "^23.28.0",
+  "@wordpress/stylelint-config": "^23.22.0",
   "fs-extra": "^11.3.3",
   "replace-in-file": "^8.4.0",
   "typescript": "^5.6.2"
 }
What are the latest stable versions of @wordpress/eslint-plugin, @wordpress/prettier-config, and @wordpress/stylelint-config on npm as of January 2026?

35-37: Still unresolved: WordPress package versions exceed published releases.

This critical issue from the previous review remains unfixed. The specified versions don't exist on npm:

  • @wordpress/api-fetch ^7.36.0 (latest stable: 7.30.0)
  • @wordpress/components ^30.9.0 (latest stable: 30.3.0)
  • @wordpress/element ^6.36.0 (latest stable: 6.32.0)

These will cause dependency resolution failures during npm install.

🔎 Proposed fix
 "dependencies": {
   "@google/genai": "^0.3.1",
   "@tailwindcss/postcss": "^4.1.18",
-  "@wordpress/api-fetch": "^7.36.0",
-  "@wordpress/components": "^30.9.0",
-  "@wordpress/element": "^6.36.0",
+  "@wordpress/api-fetch": "^7.30.0",
+  "@wordpress/components": "^30.3.0",
+  "@wordpress/element": "^6.32.0",
   "@wordpress/hooks": "^4.37.0",
   "@wordpress/plugins": "^7.37.0",
   "lucide-react": "^0.561.0",
What are the latest stable versions of @wordpress/api-fetch, @wordpress/components, and @wordpress/element on npm as of January 2026?

26-26: Still unresolved: replace-in-file version doesn't exist.

This issue from the previous review remains unfixed. Version ^8.4.0 of replace-in-file doesn't exist on npm—the latest stable release is 8.3.0. This will cause installation failures.

🔎 Proposed fix
   "fs-extra": "^11.3.3",
-  "replace-in-file": "^8.4.0",
+  "replace-in-file": "^8.3.0",
   "typescript": "^5.6.2"
What is the latest stable version of replace-in-file on npm as of January 2026?
🧹 Nitpick comments (12)
inc/Admin.php (1)

48-49: Consider using add_submenu_page() instead of direct $submenu manipulation.

While directly modifying the $submenu global works, using WordPress's add_submenu_page() function is more maintainable and future-proof. This ensures proper capability checks, URL sanitization, and compatibility with WordPress updates.

🔎 Proposed refactor
-	$submenu[ $slug ][] = array( __( 'Dashboard', 'try-aura' ), $capability, 'admin.php?page=' . $slug . '#/' );
-	$submenu[ $slug ][] = array( __( 'Settings', 'try-aura' ), $capability, 'admin.php?page=' . $slug . '#/settings' );
+	add_submenu_page(
+		$slug,
+		__( 'Dashboard', 'try-aura' ),
+		__( 'Dashboard', 'try-aura' ),
+		$capability,
+		$slug . '#/',
+		array( $this, 'render_page' )
+	);
+
+	add_submenu_page(
+		$slug,
+		__( 'Settings', 'try-aura' ),
+		__( 'Settings', 'try-aura' ),
+		$capability,
+		$slug . '#/settings',
+		array( $this, 'render_page' )
+	);
src/admin/dashboard/pages/Dashboard.tsx (1)

1-5: Placeholder implementation needs to be completed.

This component contains only stub/placeholder content and should be implemented with actual dashboard functionality before release.

Do you want me to help scaffold a proper dashboard component structure with API integration, or should this be tracked in a separate issue?

src/admin/dashboard/App.tsx (3)

9-14: Optional: Unused parent field in TryAuraAdminRoute type.

The parent field is defined but never referenced in route construction or rendering logic. Consider removing it until hierarchical routing is implemented, or add a comment explaining its reserved purpose.


48-48: Fix typo in variable name.

The variable mapedRoutes should be mappedRoutes for correct spelling.

🔎 Proposed fix
-	const mapedRoutes = routes.map( ( route ) => {
+	const mappedRoutes = routes.map( ( route ) => {
 		const WithRouterComponent = withRouter( route.element );
 
 		return {
 			path: route.path,
 			element: (
 				<Layout route={ route }>
 					<WithRouterComponent />
 				</Layout>
 			),
 		};
 	} );
 
-	const router = createHashRouter( mapedRoutes );
+	const router = createHashRouter( mappedRoutes );

63-67: Remove unnecessary fragment wrapper.

The empty fragment <>...</> around RouterProvider serves no purpose and can be removed for cleaner code.

🔎 Proposed fix
 	const router = createHashRouter( mappedRoutes );
 
-	return (
-		<>
-			<RouterProvider router={ router } />
-		</>
-	);
+	return <RouterProvider router={ router } />;
 }
src/admin/dashboard/utils/menu-fix.js (1)

8-32: Consider converting to TypeScript for type safety.

This utility is part of a TypeScript codebase but remains a .js file. Converting to .ts would provide:

  • Type safety for the slug parameter
  • Better IDE support and refactoring tools
  • Consistency with the rest of the dashboard codebase
src/utils/router.ts (3)

18-18: Add type annotation for Component parameter.

The Component parameter lacks type annotation, reducing type safety. Consider adding a type for better TypeScript support.

🔎 Proposed fix
-export function withRouter( Component ) {
+export function withRouter( Component: React.ComponentType<any> | React.ReactElement ) {
 	function ComponentWithRouterProp( props ) {

Alternatively, use a generic type:

-export function withRouter( Component ) {
+export function withRouter<P extends object>( Component: React.ComponentType<P> | React.ReactElement ) {
-	function ComponentWithRouterProp( props ) {
+	function ComponentWithRouterProp( props: P ) {

26-37: Reconsider including hook functions and loader utilities in routerProps.

The routerProps object includes:

  1. Hook functions (useParams, createSearchParams, useSearchParams) that components should call directly, not receive as props
  2. Loader/action utilities (redirect, replace) that typically aren't used in components

This creates confusion about when to use hooks vs. props. Standard practice is to only inject hook results (navigate, params, location, matches, navigation), not the hooks themselves.

🔎 Recommended simplification
 		const routerProps = {
 			navigate,
 			params,
 			location,
-			redirect,
-			replace,
 			matches,
 			navigation,
-			useParams,
-			createSearchParams,
-			useSearchParams,
 		};

If components need search params, they can call useSearchParams() directly.


18-53: Consider passing component functions to withRouter instead of JSX elements.

The implementation calls withRouter(route.element) where route.element is a JSX element like <Dashboard /> (App.tsx, line 49). This passes pre-instantiated element objects to the HOC, which is unconventional. The withRouter function handles both elements and components via isValidElement() and cloneElement(), making it work, but HOCs should typically wrap component functions, not element instances.

Refactor routes to use component references:

Change the route definition from:

element: <Dashboard />

to:

component: Dashboard

Then update withRouter to accept component functions and update App.tsx to pass route.component instead of route.element. This follows standard HOC patterns and avoids the extra element cloning logic.

src/admin/dashboard/pages/Settings.tsx (3)

79-84: Simplify error message extraction.

The error handling on lines 80-84 is overly complex. The WordPress apiFetch library throws errors with a consistent shape that can be typed more cleanly.

🔎 Proposed simplification
-		} catch ( e: unknown ) {
-			const msg =
-				e && typeof e === 'object' && 'message' in e
-					? String( ( e as any ).message )
-					: __( 'Something went wrong', 'try-aura' );
-			setError( msg );
+		} catch ( e ) {
+			const message = e instanceof Error ? e.message : __( 'Something went wrong', 'try-aura' );
+			setError( message );
 		} finally {

This is cleaner and avoids the any cast.


113-121: Consider using password input type for API key.

The API key is displayed in a plain text input. Depending on the security requirements, you might want to:

  1. Use type="password" to mask the value
  2. Add a toggle to show/hide the value
  3. Keep it as plain text if visibility is needed for verification

This is context-dependent, but worth considering for security-sensitive environments where screen sharing or shoulder surfing is a concern.

🔎 Example with password type
 				<TextControl
 					label={ __( 'API Key', 'try-aura' ) }
+					type="password"
 					value={ apiKey }
 					onChange={ setApiKey }
 					help={ __(
 						'Paste the API key provided by TryAura.',
 						'try-aura'
 					) }
 				/>

34-58: Optional: Remove redundant initial fetch since data is already available.

The component initializes apiKey state with data.apiKey from window.tryAura (line 34), then immediately fetches the same value from the API (lines 40-58). This creates an unnecessary API request on every mount.

If the localized window.tryAura.apiKey value is guaranteed to be current, the fetch can be removed. If you need to handle stale data, consider:

  1. Keeping the fetch but removing the initial state value
  2. Adding a "Refresh" button instead of auto-fetching
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3d6ec4 and d792484.

📒 Files selected for processing (12)
  • inc/Admin.php
  • inc/Assets.php
  • package.json
  • src/admin/dashboard/App.tsx
  • src/admin/dashboard/Layout/Layout.tsx
  • src/admin/dashboard/index.tsx
  • src/admin/dashboard/pages/Dashboard.tsx
  • src/admin/dashboard/pages/Settings.tsx
  • src/admin/dashboard/style.scss
  • src/admin/dashboard/utils/menu-fix.js
  • src/utils/router.ts
  • webpack.config.js
✅ Files skipped from review due to trivial changes (1)
  • src/admin/dashboard/style.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • webpack.config.js
🧰 Additional context used
🧬 Code graph analysis (1)
src/admin/dashboard/App.tsx (1)
src/utils/router.ts (1)
  • withRouter (18-53)
🪛 PHPMD (2.15.0)
inc/Assets.php

190-190: Avoid unused local variables such as '$script'. (undefined)

(UnusedLocalVariable)

🔇 Additional comments (1)
src/admin/dashboard/App.tsx (1)

1-7: The React Router v7 APIs are correctly implemented. Both createHashRouter and RouterProvider are valid v7.11.0 exports. The usage at lines 61–67 correctly passes a routes array to createHashRouter and provides the resulting router to RouterProvider. React Router v7 was released in November 2024, which is within the knowledge cutoff (March 2025), so no verification concerns apply here.

Comment on lines +77 to +101
public function enqueue_assets( string $hook ): void {
if ( 'toplevel_page_try-aura' !== $hook ) {
return;
}

$script_url = plugin_dir_url( __DIR__ ) . 'build/index.js';
// Localize data for the app.
wp_localize_script(
'try-aura-admin',
'tryAura',
array(
'restUrl' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'apiKey' => get_option( $this->option_key, '' ),
'optionKey' => $this->option_key,
)
);

wp_register_script( 'try-aura-admin', $script_url, $deps, $version, true );
// Ensure WP component styles are present.
if ( wp_style_is( 'wp-components', 'registered' ) ) {
wp_enqueue_style( 'wp-components' );
}

// Localize data for the app.
wp_localize_script( 'try-aura-admin', 'tryAura', [
'restUrl' => esc_url_raw( rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'apiKey' => get_option( $this->option_key, '' ),
'optionKey'=> $this->option_key,
] );
wp_enqueue_script( 'try-aura-admin' );
wp_enqueue_style( 'try-aura-admin' );
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: wp_localize_script called before script is enqueued.

Lines 83-92 call wp_localize_script() on the 'try-aura-admin' handle before wp_enqueue_script() is called on Line 99. While the script is registered during the init hook, it's not guaranteed to be available at the time wp_localize_script() runs because both Assets::register_all_scripts() and this method hook into init at the same priority (10). WordPress doesn't guarantee execution order for same-priority hooks, which can cause the localization to fail.

🔎 Proposed fix

Move the wp_enqueue_script() call before wp_localize_script() to ensure the script handle exists:

 public function enqueue_assets( string $hook ): void {
 	if ( 'toplevel_page_try-aura' !== $hook ) {
 		return;
 	}
 
+	// Enqueue script first to ensure handle exists.
+	wp_enqueue_script( 'try-aura-admin' );
+	wp_enqueue_style( 'try-aura-admin' );
+
 	// Localize data for the app.
 	wp_localize_script(
 		'try-aura-admin',
 		'tryAura',
 		array(
 			'restUrl'   => esc_url_raw( rest_url() ),
 			'nonce'     => wp_create_nonce( 'wp_rest' ),
 			'apiKey'    => get_option( $this->option_key, '' ),
 			'optionKey' => $this->option_key,
 		)
 	);
 
 	// Ensure WP component styles are present.
 	if ( wp_style_is( 'wp-components', 'registered' ) ) {
 		wp_enqueue_style( 'wp-components' );
 	}
-
-	wp_enqueue_script( 'try-aura-admin' );
-	wp_enqueue_style( 'try-aura-admin' );
 }
🤖 Prompt for AI Agents
In inc/Admin.php around lines 77 to 101, wp_localize_script() is being called
before the 'try-aura-admin' script is guaranteed to be registered/enqueued which
can fail because Assets::register_all_scripts() runs on the same init priority;
fix by ensuring the script handle exists before localizing — call
wp_enqueue_script('try-aura-admin') (or wp_register_script first) before
wp_localize_script(), then enqueue styles and any remaining assets afterward so
localization always attaches to a registered/enqueued script.

Comment on lines +13 to +14
"makepot": "wp i18n make-pot --domain='try-aura' --include='build,inc,lib,templates,try-aura.php' . --headers='{\"Report-Msgid-Bugs-To\":\"https://dokan.co/contact/\"}' --file-comment=\"Copyright (c) $(date +'%Y') Dokan Inc. All Rights Reserved.\" languages/try-aura.pot",
"zip": "rm -rf try-aura try-aura.zip && mkdir try-aura && cp -r build inc templates vendor try-aura.php try-aura/ && zip -r try-aura.zip try-aura && rm -rf try-aura",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check directories referenced in package.json scripts
echo "Checking for directories referenced in scripts..."
for dir in build inc templates vendor lib; do
  if [ -d "$dir" ]; then
    echo "$dir exists"
    # Show what's in it (first few items)
    ls -la "$dir" 2>/dev/null | head -5
  else
    echo "$dir missing"
  fi
done

# Also check overall repo structure
echo -e "\n=== Repository structure ==="
ls -la | grep "^d"

Repository: getdokan/tryAura

Length of output: 1065


Update scripts to remove references to non-existent directories or create them during the build process.

The makepot and zip scripts reference directories that don't exist: build, vendor, and lib. These will cause runtime failures when the scripts execute. Either create these directories as part of the build setup, or remove them from the script references at lines 13-14 (the makepot script removes lib and build, the zip script removes vendor and build).

🤖 Prompt for AI Agents
In package.json around lines 13-14, the makepot and zip npm scripts reference
non-existent folders (build, lib, vendor) causing failures; update the scripts
to either create those directories before use or remove them from the
include/copy lists—specifically: for the "makepot" script remove "build" and
"lib" from the --include list or prefix the command with mkdir -p build lib so
they exist, and for the "zip" script either remove "build" and "vendor" from the
cp and rm operations or add mkdir -p build vendor at the start of the script so
the cp/rm steps succeed; ensure resulting script preserves existing behavior for
real files and exits cleanly if folders are intentionally absent.

Comment on lines +30 to +34
// @ts-ignore
routes = applyFilters(
'tryaura-admin-dashboard-routes',
routes
) as Array< TryAuraAdminRoute >;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Replace @ts-ignore with a type assertion for safer type checking.

The @ts-ignore directive suppresses all type errors, including potential real issues. Since applyFilters returns unknown, use a type assertion instead to maintain some type safety.

🔎 Proposed fix
-	// @ts-ignore
-	routes = applyFilters(
+	routes = ( applyFilters(
 		'tryaura-admin-dashboard-routes',
 		routes
-	) as Array< TryAuraAdminRoute >;
+	) ?? routes ) as Array< TryAuraAdminRoute >;

This preserves type checking on surrounding code and adds a nullish coalescing fallback.

📝 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.

Suggested change
// @ts-ignore
routes = applyFilters(
'tryaura-admin-dashboard-routes',
routes
) as Array< TryAuraAdminRoute >;
routes = ( applyFilters(
'tryaura-admin-dashboard-routes',
routes
) ?? routes ) as Array< TryAuraAdminRoute >;
🤖 Prompt for AI Agents
In src/admin/dashboard/App.tsx around lines 30 to 34, replace the // @ts-ignore
that suppresses type checking for the applyFilters call: assert the result of
applyFilters as Array<TryAuraAdminRoute> (e.g. use a type assertion) and add a
nullish coalescing fallback to an empty array to avoid runtime null/undefined;
keep the descriptive filter name and ensure the variable retains the
Array<TryAuraAdminRoute> type instead of globally ignoring TypeScript errors.

Comment on lines +7 to +16
domReady( () => {
const dashboardDomNode = document.getElementById(
'try-aura-settings-root'
);
if ( dashboardDomNode ) {
const dashboardRoot = createRoot( dashboardDomNode! );
dashboardRoot.render( <App /> );
}
} );
menuFix( 'try-aura' );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: menuFix executes before DOM is ready.

The menuFix utility requires jQuery and manipulates DOM elements (accessing #toplevel_page_ + slug, binding click handlers, and iterating submenu anchors). Calling it on line 16 outside the domReady callback creates a race condition where jQuery selectors may fail if the admin menu hasn't rendered yet.

🔎 Proposed fix
 domReady( () => {
 	const dashboardDomNode = document.getElementById(
 		'try-aura-settings-root'
 	);
 	if ( dashboardDomNode ) {
 		const dashboardRoot = createRoot( dashboardDomNode! );
 		dashboardRoot.render( <App /> );
 	}
+	menuFix( 'try-aura' );
 } );
-menuFix( 'try-aura' );
🤖 Prompt for AI Agents
In src/admin/dashboard/index.tsx around lines 7 to 16, menuFix('try-aura') is
called outside the domReady callback causing a race where jQuery/admin menu may
not be available; move the menuFix('try-aura') invocation inside the domReady
callback (after the dashboardRoot.render(<App />) and/or after confirming the
admin menu is present) so it runs only when DOM and jQuery are ready; optionally
guard with a check for window.jQuery or retry logic if needed.

Comment on lines +4 to +11
const Layout = ( { children, route } ) => {
return (
<SlotFillProvider>
{ children }
<PluginArea scope={ 'tryaura-admin-dashboard-' + route.id } />
</SlotFillProvider>
);
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add TypeScript types for better type safety.

The component props lack TypeScript type definitions, which reduces type safety and IDE support. Define an interface for the props including the route object's expected shape.

🔎 Proposed refactor
 import { SlotFillProvider } from '@wordpress/components';
 import { PluginArea } from '@wordpress/plugins';
+import { ReactNode } from 'react';
 
-const Layout = ( { children, route } ) => {
+interface LayoutProps {
+	children: ReactNode;
+	route: {
+		id: string;
+		[key: string]: unknown;
+	};
+}
+
+const Layout = ( { children, route }: LayoutProps ) => {
 	return (
 		<SlotFillProvider>
 			{ children }
-			<PluginArea scope={ 'tryaura-admin-dashboard-' + route.id } />
+			<PluginArea scope={ `tryaura-admin-dashboard-${ route.id }` } />
 		</SlotFillProvider>
 	);
 };
📝 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.

Suggested change
const Layout = ( { children, route } ) => {
return (
<SlotFillProvider>
{ children }
<PluginArea scope={ 'tryaura-admin-dashboard-' + route.id } />
</SlotFillProvider>
);
};
import { SlotFillProvider } from '@wordpress/components';
import { PluginArea } from '@wordpress/plugins';
import { ReactNode } from 'react';
interface LayoutProps {
children: ReactNode;
route: {
id: string;
[key: string]: unknown;
};
}
const Layout = ( { children, route }: LayoutProps ) => {
return (
<SlotFillProvider>
{ children }
<PluginArea scope={ `tryaura-admin-dashboard-${ route.id }` } />
</SlotFillProvider>
);
};
🤖 Prompt for AI Agents
In src/admin/dashboard/Layout/Layout.tsx around lines 4 to 11, the Layout
component is untyped; define a props interface (e.g. LayoutProps) that at
minimum types children as React.ReactNode and route as an object with an id:
string (plus index signature or explicit optional fields if needed), import any
necessary React types, and annotate the component signature (for example const
Layout: React.FC<LayoutProps> = ({ children, route }) => { ... }); update the
file to use this interface so the component has proper TypeScript types and
IDE/type-checker support.

Comment on lines +25 to +32
const Settings = () => {
const data = window.tryAura!;

useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add runtime validation for window.tryAura to prevent crashes.

Line 26 uses a non-null assertion (window.tryAura!) without any runtime check. If the PHP localization script fails to run or loads after this component, the application will crash with a TypeError.

🔎 Proposed fix
 const Settings = () => {
+	if ( ! window.tryAura ) {
+		return (
+			<Notice status="error" isDismissible={ false }>
+				{ __( 'Configuration data not available.', 'try-aura' ) }
+			</Notice>
+		);
+	}
+	
 	const data = window.tryAura!;

Alternatively, provide fallback values:

-	const data = window.tryAura!;
+	const data = window.tryAura ?? {
+		restUrl: '/wp-json',
+		nonce: '',
+		apiKey: '',
+		optionKey: 'try_aura_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.

Suggested change
const Settings = () => {
const data = window.tryAura!;
useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
const Settings = () => {
if ( ! window.tryAura ) {
return (
<Notice status="error" isDismissible={ false }>
{ __( 'Configuration data not available.', 'try-aura' ) }
</Notice>
);
}
const data = window.tryAura!;
useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
Suggested change
const Settings = () => {
const data = window.tryAura!;
useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
const Settings = () => {
const data = window.tryAura ?? {
restUrl: '/wp-json',
nonce: '',
apiKey: '',
optionKey: 'try_aura_api_key',
};
useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/Settings.tsx around lines 25 to 32, the code uses
window.tryAura! with a non-null assertion which can throw if the localized PHP
script isn't present; add a runtime check for window.tryAura (e.g., if
(!window.tryAura) handle gracefully), provide safe fallback values for restUrl
and nonce or return early/show a fallback UI, and then use the validated values
to call apiFetch.createRootURLMiddleware and createNonceMiddleware; ensure you
type-guard or narrow the value before using it so no non-null assertion is
needed.

Comment on lines +28 to +32
useEffect( () => {
// Attach REST middlewares: root + nonce for admin context.
apiFetch.use( apiFetch.createRootURLMiddleware( data.restUrl ) );
apiFetch.use( apiFetch.createNonceMiddleware( data.nonce ) );
}, [] );
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Move apiFetch middleware setup to a higher level to avoid duplication.

Setting up middlewares inside the component with an empty dependency array means they're configured every time a Settings instance mounts. If the Settings component ever unmounts and remounts, or if multiple instances exist, middlewares will be added repeatedly, potentially causing unexpected behavior.

Consider moving middleware initialization to a module-level setup or a singleton initialization pattern.

🔎 Recommended approach

Create a separate initialization module:

// src/admin/dashboard/api-setup.ts
import apiFetch from '@wordpress/api-fetch';

let initialized = false;

export function initializeAPI() {
	if ( initialized || ! window.tryAura ) {
		return;
	}
	
	apiFetch.use( apiFetch.createRootURLMiddleware( window.tryAura.restUrl ) );
	apiFetch.use( apiFetch.createNonceMiddleware( window.tryAura.nonce ) );
	initialized = true;
}

Then call it once from index.tsx before rendering:

// src/admin/dashboard/index.tsx
import { initializeAPI } from './api-setup';

domReady( () => {
	initializeAPI();
	// ... rest of initialization
} );
🤖 Prompt for AI Agents
In src/admin/dashboard/pages/Settings.tsx around lines 28-32, the apiFetch
middlewares are registered inside a component useEffect which can cause
duplicate middleware registration on remounts or multiple instances; move this
setup to a single initialization module (e.g., src/admin/dashboard/api-setup.ts)
that exports an initializeAPI function which guards with an initialized flag and
reads restUrl/nonce from the global/window payload, register the
createRootURLMiddleware and createNonceMiddleware once there, call initializeAPI
from the app entry (e.g., src/admin/dashboard/index.tsx) before rendering, and
remove the apiFetch.use calls from the Settings component.

Comment on lines +8 to +14
function menuFix( slug ) {
const $ = jQuery;

const menuRoot = $( '#toplevel_page_' + slug );
const currentUrl = window.location.href;
const currentPath = currentUrl.substr( currentUrl.indexOf( 'admin.php' ) );

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add jQuery availability check and fix deprecated substr usage.

Two issues:

  1. Line 9 assumes jQuery is globally available without validation, which will throw a ReferenceError if WordPress admin scripts aren't loaded.
  2. Line 13 uses the deprecated substr method and doesn't handle the case where indexOf('admin.php') returns -1.
🔎 Proposed fix
 function menuFix( slug ) {
+	if ( typeof jQuery === 'undefined' ) {
+		return;
+	}
+	
 	const $ = jQuery;
 
 	const menuRoot = $( '#toplevel_page_' + slug );
 	const currentUrl = window.location.href;
-	const currentPath = currentUrl.substr( currentUrl.indexOf( 'admin.php' ) );
+	const adminPhpIndex = currentUrl.indexOf( 'admin.php' );
+	const currentPath = adminPhpIndex !== -1 ? currentUrl.substring( adminPhpIndex ) : '';
📝 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.

Suggested change
function menuFix( slug ) {
const $ = jQuery;
const menuRoot = $( '#toplevel_page_' + slug );
const currentUrl = window.location.href;
const currentPath = currentUrl.substr( currentUrl.indexOf( 'admin.php' ) );
function menuFix( slug ) {
if ( typeof jQuery === 'undefined' ) {
return;
}
const $ = jQuery;
const menuRoot = $( '#toplevel_page_' + slug );
const currentUrl = window.location.href;
const adminPhpIndex = currentUrl.indexOf( 'admin.php' );
const currentPath = adminPhpIndex !== -1 ? currentUrl.substring( adminPhpIndex ) : '';
🤖 Prompt for AI Agents
In src/admin/dashboard/utils/menu-fix.js around lines 8–14, the code assumes
jQuery exists and uses deprecated substr without handling indexOf returning -1;
update the function to first check for jQuery availability (e.g., if (typeof
jQuery === 'undefined') return or accept a passed-in $) and bail out gracefully
if missing, then replace currentUrl.substr(...) with a safe implementation using
indexOf result and String.prototype.slice (e.g., const idx =
currentUrl.indexOf('admin.php'); const currentPath = idx === -1 ? '' :
currentUrl.slice(idx)); ensure menuRoot selection only runs after confirming $
is defined.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
inc/Admin.php (1)

83-100: Move wp_enqueue_script() before wp_localize_script() for reliability.

This issue was flagged in a previous review. While the script is registered during init (in Assets.php) before admin_enqueue_scripts runs, it's safer and clearer to enqueue the script before localizing to ensure the handle is definitely available.

🧹 Nitpick comments (1)
inc/Admin.php (1)

48-49: Consider using add_submenu_page() instead of direct $submenu manipulation.

Direct manipulation of the global $submenu array bypasses WordPress's standard capability checks and hook system. While functional, using add_submenu_page() provides more consistent behavior.

🔎 Suggested refactor
-		$submenu[ $slug ][] = array( __( 'Dashboard', 'try-aura' ), $capability, 'admin.php?page=' . $slug . '#/' );
-		$submenu[ $slug ][] = array( __( 'Settings', 'try-aura' ), $capability, 'admin.php?page=' . $slug . '#/settings' );
+		add_submenu_page(
+			$slug,
+			__( 'Dashboard', 'try-aura' ),
+			__( 'Dashboard', 'try-aura' ),
+			$capability,
+			$slug . '#/',
+			array( $this, 'render_page' )
+		);
+		add_submenu_page(
+			$slug,
+			__( 'Settings', 'try-aura' ),
+			__( 'Settings', 'try-aura' ),
+			$capability,
+			$slug . '#/settings',
+			array( $this, 'render_page' )
+		);

Note: Hash-based routing in slugs may not work directly with add_submenu_page(). If the current approach is intentional for SPA routing, you may keep it as-is.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d792484 and a7c48fb.

📒 Files selected for processing (1)
  • inc/Admin.php
🧰 Additional context used
🧬 Code graph analysis (1)
inc/Admin.php (3)
inc/Assets.php (1)
  • __construct (12-14)
inc/Rest/SettingsController.php (1)
  • __construct (43-46)
inc/Enhancer.php (1)
  • __construct (17-19)
🔇 Additional comments (2)
inc/Admin.php (2)

55-72: Well-structured settings registration.

Good use of register_setting() with proper sanitization, REST exposure, and auth callback for capability checking.


106-110: LGTM!

Standard WordPress admin page container with a React mount point. Clean and appropriate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants