Skip to content

feat: Added hotkey shortcut to change themes #170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

aa5hi5h
Copy link

@aa5hi5h aa5hi5h commented Jul 18, 2025

What does this PR do?

This PR added the feature of enabling keyboard shortcut to change the theme of the website. On the homepage there were preset selector rows which upon clicking load those theme to the site but this PR added the feature of doing the same thing by pressing "Space" key from the keyboard.

Documentation

  • Added the theme-hotkey-handler which checks if "Space" key is pressed and then randomly selects theme and then apply them
  • added the useClient hook - this was not needed but without this it was causing ssr and hydration error due to Math.Random() function

Video demo

Beautiful.themes.for.shadcn_ui.tweakcn._.Theme.Editor.Generator.-.Brave.2025-07-17.19-39-32.1.1.mp4

Checklist

  • I have not added any new packages or change them but I can see a lot file change in my branch in the package.lock.json file , before making any commits i was looking for best practices to follow and run this cmd to sync my repo to your main repo and run these cmd => git remote add upstream <URL_OF_ORIGINAL_REPO> => git fetch upstream => git checkout main => git merge upstream/main => git push origin main , I was cautious but I think because of this cmd those changes reflect in the package.lock.json.
  • This is my first time contributing so I don't know if this is common or not but if this rises concern or it's not good practise to follow or can break things in the production then you can let me know I will create new PR adding the same feature .

Summary by CodeRabbit

  • New Features
    • Added a keyboard shortcut (spacebar) to instantly switch to a random theme preset, except when typing in input fields.
    • Introduced a testimonials section with smooth marquee animation showcasing user feedback.
    • Added a font picker component enabling search, filtering, and selection of Google Fonts with live loading.
    • Included a keyboard shortcuts overlay modal accessible via Ctrl + / for quick reference.
    • Implemented dynamic Google Fonts loading based on the current theme settings.
    • Added a new API endpoint to browse and search Google Fonts with filtering and pagination.
  • Improvements
    • Enhanced the page by wrapping content with a handler for theme hotkey support.
    • Updated 404 page with theme toggle, improved styling, and quick navigation.
    • Refined pricing page with testimonials and updated metadata.
    • Improved header navigation with centralized menu items and conditional scroll behavior.
    • Improved image uploader to accept specific image types with SVG validation and optimization.
    • Reduced AI request limits and image upload constraints for better resource management.
    • Updated theme preview and color preview panels with improved styling and layout.
    • Simplified theme font selection UI by replacing custom selectors with the new font picker.
    • Enhanced font loading utilities and theme script to preload fonts for active themes.
    • Improved call-to-action buttons with added "Get Pro" option and responsive layout.
  • Bug Fixes
    • Fixed font loading errors by adding proper error handling and logging.
    • Resolved inconsistent styling in image previews and upload components.
  • Other
    • Minor formatting and code cleanup across multiple components.
    • Removed deprecated or unused UI elements such as the "Sponsor" link and certain demo tabs.
    • Updated environment example with Google Fonts API key instructions.
    • Adjusted sitemap to include the pricing page.

Copy link

vercel bot commented Jul 18, 2025

@aa5hi5h is attempting to deploy a commit to the tweakcn OSS program Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Jul 18, 2025

Walkthrough

A new component, ThemeHotKeyHandler, is introduced to wrap the main page content and enable theme switching by pressing the spacebar. Supporting this, a custom hook useClient is added to detect client-side rendering. The main page is updated to utilize the new handler, with minor formatting adjustments.

Changes

File(s) Change Summary
components/home/theme-hotkey-handler.tsx Added new ThemeHotKeyHandler component to handle spacebar-triggered theme changes.
hooks/use-client.ts Introduced useClient hook to detect client-side rendering.
app/page.tsx Wrapped page content with ThemeHotKeyHandler and made a minor formatting fix.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ThemeHotKeyHandler
    participant EditorStore

    User->>ThemeHotKeyHandler: Presses spacebar
    ThemeHotKeyHandler->>ThemeHotKeyHandler: Checks event target (not input/textarea)
    ThemeHotKeyHandler->>EditorStore: Selects and applies a random theme preset
Loading

Suggested reviewers

  • jnsahaj

Poem

In the garden of code, a new trick appears,
Press spacebar—watch themes change, bring cheers!
With a handler so clever and a hook to detect,
The page now responds in ways you’d expect.
Hop, skip, and jump—let colors take flight,
Rabbits love features that work just right! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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 (2)
components/home/theme-hotkey-handler.tsx (2)

20-20: Use strict equality for consistency.

Consider using strict equality (!==) instead of loose equality (!=) for better consistency with modern JavaScript practices.

-        const otherThemes = availableThemes.filter(theme => theme != currentTheme)
+        const otherThemes = availableThemes.filter(theme => theme !== currentTheme)

12-12: Consider memoizing availableThemes for performance.

Since Object.keys(defaultPresets) creates a new array on every render, consider memoizing it with useMemo to avoid unnecessary re-computations.

+import { useEffect, useMemo } from "react"

-      const availableThemes = Object.keys(defaultPresets)
+      const availableThemes = useMemo(() => Object.keys(defaultPresets), [])
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ada4ae and ff97b1b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • app/page.tsx (4 hunks)
  • components/home/theme-hotkey-handler.tsx (1 hunks)
  • hooks/use-client.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
app/page.tsx (1)
components/home/theme-hotkey-handler.tsx (1)
  • ThemeHotKeyHandler (8-60)
components/home/theme-hotkey-handler.tsx (3)
store/editor-store.ts (1)
  • useEditorStore (34-231)
utils/theme-presets.ts (1)
  • defaultPresets (3-3422)
hooks/use-client.ts (1)
  • useClient (5-15)
🔇 Additional comments (6)
hooks/use-client.ts (1)

5-15: Good approach for handling SSR/hydration issues.

The hook correctly implements client-side detection by using useEffect to set the flag after mount, which is the standard pattern for avoiding hydration mismatches.

app/page.tsx (3)

12-12: Good import of the new ThemeHotKeyHandler component.

The import is correctly placed and follows the existing import pattern.


35-52: Excellent integration of the hotkey handler.

Wrapping the entire page content with ThemeHotKeyHandler is the correct approach for global hotkey functionality. This ensures the spacebar shortcut works across all page elements while maintaining the existing component hierarchy.


22-22: Minor formatting improvement.

The removal of the extra space before the brace improves code consistency.

components/home/theme-hotkey-handler.tsx (2)

33-45: Excellent event handling implementation.

The event handler correctly:

  • Checks for valid event target
  • Uses event.code === "Space" for reliable key detection
  • Excludes input fields, textareas, and contenteditable elements
  • Prevents default behavior to avoid unwanted scrolling
  • Applies the random theme change

This is a robust implementation that handles edge cases well.


15-27: Solid random theme selection logic.

The function correctly:

  • Guards against non-client execution
  • Filters out the current theme to ensure a change
  • Handles the case where no other themes are available
  • Uses proper random selection logic

@jnsahaj
Copy link
Owner

jnsahaj commented Jul 18, 2025

Nice!
I was planning to add a whole bunch of keyboard shortcuts in the theme editor. It would be like a global shortcuts manager (something like what happens when you press "?" on YouTube)

Would you be interested in building it?

@aa5hi5h
Copy link
Author

aa5hi5h commented Jul 18, 2025

Nice! I was planning to add a whole bunch of keyboard shortcuts in the theme editor. It would be like a global shortcuts manager (something like what happens when you press "?" on YouTube)

Would you be interested in building it?

Yes sure , Would love to !

@jnsahaj
Copy link
Owner

jnsahaj commented Jul 18, 2025

Perfect! You can get started with whatever feels good to you. Just make sure the keybinds don't conflict with existing browser stuff too much.

Some I can think of:
Start AI chat: cmd+shift+o
Undo/redo: cmd+z/y
Copy css code
Copy theme command
Save theme
Cycle themes

This could also evolve into a cmd+k panel (like on ui.shadcn.com) if that's something you're interested in building!

llanesluis and others added 22 commits July 18, 2025 19:43
…aj#171)

* fix: border radius and overflow in ImagePreview component

* fix: Proper support for SVGs and UI feedback when errors occur

* SVG files are treated and displayed as Images in the UI
* SVG files are processed as Markup Text to send to the API
* Remove support for GIFs

* chore: improve system prompt to handle SVGs properly

* styles: Add border radius to uploaded image preview when loading
- Add floating theme toggle and theme presets
- Use playful headline and friendlier message
- Improve layout and user experience for lost visitors
* Beautiful twitter testimonials on Home page jnsahaj#126 added (jnsahaj#128)

* Beautiful twitter testimonials on Home page jnsahaj#126 added

* Beautiful twitter testimonials on Home page jnsahaj#126 added

* Beautiful twitter testimonials on Home page jnsahaj#126 added

* Beautiful twitter testimonials on Home page jnsahaj#126 added

---------

Co-authored-by: sanwalsulehri <[email protected]>
Co-authored-by: Sahaj Jain <[email protected]>

* feat: Add real testimonial tweets

* add testimonials to pricing page

* use avatar

---------

Co-authored-by: Sanwal Sulehrii <[email protected]>
Co-authored-by: sanwalsulehri <[email protected]>
* bug(layout): fix all erros in next.js devtools

* fix react hook warning in testimonials component

* refactor coderabbit suggestions
* fix: Small chart overflow in starts card

* feat: add color scheme light dark

* refactor: use shadcn TabsContent instead of the Radix primitive

* refactor: preview panel tabs content and components

* fix: Solve hydration errors from nested buttons

---------

Co-authored-by: Sahaj Jain <[email protected]>
* chore: allow overriding ControlSection classNames

* feat: Support Google Fonts API and load fonts dynamically

* feat: Enforce System Prompt to use Google Fonts API if needed

* styles: Improve GoogleFontPicker styles

* feat: Pull Fonts from fileSystem instead of hitting the Google Fonts API every time

* feat: Improve System Prompt to reference Google Fonts

* refactor: Use Next.js Cache instead of manually fetching and reading from the filesystem

* feat: Only enable Font picker fetch when it's open

* chore: Remove JSON for Google Fonts catalogue

* feat: Scroll to selected font if it's already fetched and add clean search button

* chore: update imports

* fix: dashboard preview

---------

Co-authored-by: Luis Llanes <[email protected]>
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: 10

🧹 Nitpick comments (14)
components/home/header.tsx (1)

155-158: Consider conditional scroll handling in mobile menu.

The mobile menu always calls handleScrollToSection regardless of the link type, while the desktop menu conditionally applies it. For consistency and to prevent potential issues with external links, consider applying the same conditional logic here.

                onClick={(e) => {
-                  handleScrollToSection(e);
+                  if (item.href.startsWith("#")) {
+                    handleScrollToSection(e);
+                  }
                  setMobileMenuOpen(false);
                }}
utils/ai/image-upload.ts (1)

9-29: Strong SVG security validation with minor enhancement opportunity.

The security validation covers the major XSS attack vectors in SVG files. The dangerous patterns effectively block script injection, JavaScript URIs, event handlers, and embedded objects.

Consider adding these additional security patterns for more comprehensive protection:

 const dangerousPatterns = [
   /<script/i,
   /javascript:/i,
   /on\w+\s*=/i, // onclick, onload, etc.
   /<embed/i,
   /<object/i,
   /<iframe/i,
+  /<use[^>]*href\s*=\s*["'][^"']*javascript:/i, // javascript in xlink:href
+  /<foreignobject/i, // can embed HTML
+  /data:text\/html/i, // HTML data URIs
 ];
hooks/use-image-upload.ts (1)

78-141: Comprehensive SVG processing pipeline with robust error handling.

The implementation properly handles SVG files with security validation, optimization, and size constraints. The error handling ensures failed operations are cleaned up appropriately.

Consider extracting the SVG processing logic into a separate function for better readability:

+const processSvgFile = async (file: File, result: string): Promise<string> => {
+  const isValidSvg = validateSvgContent(result);
+  if (!isValidSvg) {
+    // Warning toast logic here
+  }
+  
+  const optimizedSvg = optimizeSvgContent(result);
+  const encodedSvg = encodeURIComponent(optimizedSvg);
+  
+  if (encodedSvg.length > MAX_SVG_FILE_SIZE) {
+    throw new Error('SVG too large after encoding');
+  }
+  
+  return `data:image/svg+xml,${encodedSvg}`;
+};

 const handleSuccess = (result: string) => {
   let finalUrl: string;
   
   if (file.type === "image/svg+xml") {
     try {
-      const isValidSvg = validateSvgContent(result);
-      if (!isValidSvg) {
-        toast({
-          title: "Potentially unsafe SVG",
-          description: `"${file.name}" may contain unsafe content but will be processed anyway.`,
-        });
-      }
-
-      const optimizedSvg = optimizeSvgContent(result);
-      const encodedSvg = encodeURIComponent(optimizedSvg);
-
-      if (encodedSvg.length > MAX_SVG_FILE_SIZE) {
-        handleError();
-        return;
-      }
-
-      finalUrl = `data:image/svg+xml,${encodedSvg}`;
+      finalUrl = await processSvgFile(file, result);
     } catch (error) {
       handleError();
       return;
     }
   } else {
     finalUrl = result;
   }
hooks/use-debounced-callback.ts (1)

17-24: Consider cleanup dependency optimization.

The cleanup effect currently only depends on [wait], but it's primarily needed for component unmount. Consider if this dependency is necessary since the cleanup function will run on unmount regardless.

You could simplify to:

  useEffect(() => {
    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
- }, [wait]);
+ }, []);
components/editor/keyboard-shortcut-overlay.tsx (1)

32-32: Remove unused fun property.

The shortcut item has an unused fun property that's inconsistent with other items in the shortcuts array.

- { action: "Open AI tab", fun: () => console.log("Open AI Tab (Ctrl+Shift+O)"), keys: ["Ctrl", "Shift", "O"] },
+ { action: "Open AI tab", keys: ["Ctrl", "Shift", "O"] },
store/action-store.ts (2)

35-42: Consider making code panel toggle more flexible.

The triggerCodePanelOpen method hardcodes true as the argument. Consider making it more flexible to support both opening and closing.

- triggerCodePanelOpen: () => {
+ triggerCodePanelOpen: (open: boolean = true) => {
    const handler = get()._setCodePanelOpenRef;
    if (handler) { 
-     handler(true); 
+     handler(open); 
    } else { 
      console.warn("Code Panel handler not set."); 
    }
  },

45-56: Fix inconsistent code formatting.

There are inconsistent spaces in the formatting compared to other methods.

- setHandleRestClick: (handler) =>  {
-   set({_handleResetClickRef: handler});
+ setHandleResetClick: (handler) => {
+   set({ _handleResetClickRef: handler });
  },
  triggerResetTheme: () => {
    const handler = get()._handleResetClickRef;
-   if(handler){
+   if (handler) {
      handler();
-   }else{
-     console.warn("Reset handler not set")
+   } else {
+     console.warn("Reset handler not set.");
    }
  },
.env.example (1)

27-31: Fix formatting consistency issues.

Static analysis detected formatting inconsistencies with quote characters and missing blank line at end of file.

- GROQ_API_KEY="YOUR_GROQ_API_KEY"
+ GROQ_API_KEY=YOUR_GROQ_API_KEY

  ###### GOOGLE FONTS ######
  # Get a Google Fonts API Key: https://developers.google.com/fonts/docs/developer_api
- GOOGLE_FONTS_API_KEY="YOUR_GOOGLE_FONTS_API_KEY"
+ GOOGLE_FONTS_API_KEY=YOUR_GOOGLE_FONTS_API_KEY
+ 
components/dynamic-font-loader.tsx (2)

32-32: Hard-coded font weights may not be optimal for all fonts.

The hard-coded weights ["400", "500", "600", "700"] might not be available for all Google Fonts, potentially causing loading failures or suboptimal font rendering.

Consider making font weights configurable or dynamically determined based on the font family:

- const weights = getDefaultWeights(["400", "500", "600", "700"]);
+ const weights = getDefaultWeights(getFontWeights(fontFamily) || ["400", "500", "600", "700"]);

36-38: Error handling could be more specific.

The current error handling only logs a generic warning. Consider providing more specific error information for debugging.

- console.warn("DynamicFontLoader: Failed to load Google fonts:", e);
+ console.warn(`DynamicFontLoader: Failed to load font "${fontFamily}":`, e);
components/home/ai-generation-cta.tsx (1)

51-61: Minor inconsistency in transition durations.

The "Try it Free" button uses duration-250 while the "Get Pro" button uses duration-300. Consider using consistent timing for better user experience.

- className="border-primary/20 hover:border-primary/50 h-12 cursor-pointer rounded-full px-8 text-base transition-transform duration-250 hover:translate-y-[-2px]"
+ className="border-primary/20 hover:border-primary/50 h-12 cursor-pointer rounded-full px-8 text-base transition-transform duration-300 hover:translate-y-[-2px]"
app/not-found.tsx (1)

19-35: Consider reusing the existing ThemeToggle component.

While the theme toggle implementation works well, there's potential code duplication with the existing ThemeToggle component in components/editor/action-bar/components/theme-toggle.tsx.

Consider reusing the existing component:

+ import { ThemeToggle } from "@/components/editor/action-bar/components/theme-toggle";

  <div className="fixed top-4 right-4 z-50">
-   <Tooltip>
-     <TooltipTrigger asChild>
-       <Button
-         variant="ghost"
-         size="sm"
-         aria-label="Toggle theme"
-         onClick={(e) => toggleTheme({ x: e.clientX, y: e.clientY })}
-       >
-         {theme === "light" ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />}
-       </Button>
-     </TooltipTrigger>
-     <TooltipContent side="bottom">
-       <p className="text-xs">Toggle theme</p>
-     </TooltipContent>
-   </Tooltip>
+   <ThemeToggle />
  </div>
components/home/testimonials.tsx (1)

135-160: Consider performance optimization for continuous animation

While the animation implementation is solid with proper cleanup and reduced motion support, continuous animations can impact performance on lower-end devices. Consider adding an Intersection Observer to pause animations when the component is not in viewport.

Add viewport detection to pause off-screen animations:

+  const [isInView, setIsInView] = useState(false);
+
+  useEffect(() => {
+    const observer = new IntersectionObserver(
+      ([entry]) => setIsInView(entry.isIntersecting),
+      { threshold: 0.1 }
+    );
+    
+    if (containerRef.current) {
+      observer.observe(containerRef.current);
+    }
+    
+    return () => observer.disconnect();
+  }, []);

  useEffect(() => {
-    if (shouldReduceMotion) return;
+    if (shouldReduceMotion || !isInView) return;

    const animate = (time: number) => {
utils/fonts/google-fonts.ts (1)

34-38: Consider making display parameter configurable

The function correctly builds the URL with proper encoding. Consider making the display parameter configurable for different font loading strategies.

-export function buildFontCssUrl(family: string, weights: string[] = ["400"]): string {
+export function buildFontCssUrl(
+  family: string, 
+  weights: string[] = ["400"],
+  display: "auto" | "block" | "swap" | "fallback" | "optional" = "swap"
+): string {
   const encodedFamily = encodeURIComponent(family);
   const weightsParam = weights.join(";"); // Use semicolon for Google Fonts API v2
-  return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${weightsParam}&display=swap`;
+  return `https://fonts.googleapis.com/css2?family=${encodedFamily}:wght@${weightsParam}&display=${display}`;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a2b4ce and 2dbbea7.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (52)
  • .env.example (1 hunks)
  • app/ai/components/ai-announcement.tsx (1 hunks)
  • app/ai/page.tsx (1 hunks)
  • app/api/generate-theme/route.ts (2 hunks)
  • app/api/google-fonts/route.ts (1 hunks)
  • app/globals.css (1 hunks)
  • app/layout.tsx (3 hunks)
  • app/not-found.tsx (1 hunks)
  • app/pricing/page.tsx (2 hunks)
  • app/sitemap.ts (1 hunks)
  • components/dynamic-font-loader.tsx (1 hunks)
  • components/editor/action-bar/components/theme-toggle.tsx (2 hunks)
  • components/editor/ai/chat-image-preview.tsx (1 hunks)
  • components/editor/ai/drag-and-drop-image-uploader.tsx (1 hunks)
  • components/editor/ai/image-uploader.tsx (2 hunks)
  • components/editor/ai/message.tsx (1 hunks)
  • components/editor/ai/uploaded-image-preview.tsx (1 hunks)
  • components/editor/control-section.tsx (2 hunks)
  • components/editor/font-picker.tsx (1 hunks)
  • components/editor/keyboard-shortcut-overlay.tsx (1 hunks)
  • components/editor/theme-control-panel.tsx (3 hunks)
  • components/editor/theme-font-select.tsx (2 hunks)
  • components/editor/theme-preview-panel.tsx (5 hunks)
  • components/editor/theme-preview/color-preview.tsx (8 hunks)
  • components/editor/theme-preview/tabs-trigger-pill.tsx (1 hunks)
  • components/examples/cards/stats.tsx (1 hunks)
  • components/examples/dashboard/index.tsx (1 hunks)
  • components/examples/pricing/pricing.tsx (1 hunks)
  • components/examples/typography/typography-demo.tsx (1 hunks)
  • components/get-pro-cta.tsx (1 hunks)
  • components/header.tsx (0 hunks)
  • components/home/ai-generation-cta.tsx (1 hunks)
  • components/home/features.tsx (1 hunks)
  • components/home/header.tsx (3 hunks)
  • components/home/testimonials.tsx (1 hunks)
  • components/theme-script.tsx (2 hunks)
  • hooks/use-debounced-callback.ts (1 hunks)
  • hooks/use-font-search.ts (1 hunks)
  • hooks/use-image-upload-reducer.ts (2 hunks)
  • hooks/use-image-upload.ts (3 hunks)
  • lib/constants.ts (1 hunks)
  • lib/shared.ts (0 hunks)
  • lib/subscription.ts (1 hunks)
  • store/action-store.ts (1 hunks)
  • types/fonts.ts (1 hunks)
  • utils/ai/generate-theme.tsx (1 hunks)
  • utils/ai/image-upload.ts (1 hunks)
  • utils/ai/message-converter.ts (2 hunks)
  • utils/fonts/google-fonts.ts (1 hunks)
  • utils/fonts/index.ts (1 hunks)
  • utils/subscription.ts (0 hunks)
  • utils/theme-fonts.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • components/header.tsx
  • lib/shared.ts
  • utils/subscription.ts
✅ Files skipped from review due to trivial changes (16)
  • components/examples/cards/stats.tsx
  • lib/subscription.ts
  • app/globals.css
  • components/examples/dashboard/index.tsx
  • app/api/generate-theme/route.ts
  • components/editor/theme-font-select.tsx
  • components/home/features.tsx
  • components/editor/ai/uploaded-image-preview.tsx
  • app/sitemap.ts
  • app/layout.tsx
  • components/examples/typography/typography-demo.tsx
  • app/ai/page.tsx
  • components/editor/ai/chat-image-preview.tsx
  • lib/constants.ts
  • types/fonts.ts
  • components/examples/pricing/pricing.tsx
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: in the responsivedialog implementation (revola.tsx), selectcontent components within dialog content ...
Learnt from: SameerJS6
PR: jnsahaj/tweakcn#208
File: components/editor/code-panel.tsx:188-188
Timestamp: 2025-08-03T11:11:04.835Z
Learning: In the ResponsiveDialog implementation (revola.tsx), SelectContent components within dialog content use z-99999 to ensure proper layering above ResponsiveDialogOverlay which uses z-50. This high z-index is intentional for the responsive dialog system layering.

Applied to files:

  • components/editor/keyboard-shortcut-overlay.tsx
🪛 dotenv-linter (3.3.0)
.env.example

[warning] 27-27: [QuoteCharacter] The value has quote characters (', ")


[warning] 31-31: [EndingBlankLine] No blank line at the end of the file


[warning] 31-31: [QuoteCharacter] The value has quote characters (', ")

🪛 ast-grep (0.38.6)
components/theme-script.tsx

[warning] 99-99: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
components/theme-script.tsx

[error] 100-100: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🔇 Additional comments (50)
components/home/header.tsx (2)

20-41: LGTM! Good refactoring for maintainability.

The introduction of the navbarItems constant array centralizes navigation configuration and makes the code more maintainable. The explicit label and href properties provide better type safety and clarity.


72-85: Smart conditional scroll handling implementation.

The conditional application of handleScrollToSection based on item.href.startsWith("#") is well-implemented. This ensures that hash links trigger smooth scrolling while external/page navigation links work normally.

components/editor/ai/image-uploader.tsx (2)

5-5: LGTM! Security enhancement with centralized image type validation.

The import of ALLOWED_IMAGE_TYPES centralizes the allowed image type definitions, making it easier to maintain consistency across image upload components.


36-36: Excellent security improvement!

Replacing the generic "image/*" with a specific list of allowed MIME types prevents users from uploading potentially malicious files disguised as images. This change aligns with security best practices for file uploads.

components/editor/ai/message.tsx (2)

155-157: Verify visual impact of removed styling classes.

The removal of overflow-hidden and rounded-lg classes from the single image container may affect the visual appearance. Please ensure that:

  • Images still display properly without visual artifacts
  • The UI remains consistent with the overall design system
  • No content overflow issues are introduced

163-169: Verify visual consistency for multiple image layout.

Similar to the single image case, the removal of styling classes may impact the visual presentation of multiple images. Please verify that the grid layout still looks polished and maintains proper spacing/alignment.

components/editor/ai/drag-and-drop-image-uploader.tsx (1)

21-27: Great security improvement with proper react-dropzone configuration!

The change from generic "image/*" to specific MIME types enhances security by preventing malicious file uploads. The configuration correctly uses the react-dropzone accept format with MIME type keys and empty arrays as values.

Note: Both "image/jpeg" and "image/jpg" are included for compatibility, though "image/jpeg" is the standard MIME type. This redundancy ensures broad browser support.

hooks/use-image-upload-reducer.ts (2)

26-28: Excellent addition of URL-based removal functionality!

The new "REMOVE_BY_URL" action provides a more robust way to remove images by URL instead of relying on array indices, which can change during state updates. The implementation correctly filters out the matching image.


46-51: Proper synchronization logic for the new action type.

The sync condition correctly includes the new "REMOVE_BY_URL" action, ensuring that external state remains consistent when images are removed by URL.

utils/ai/message-converter.ts (1)

3-3: LGTM! Proper import adjustments for new functionality.

The removal of unused ImagePart and addition of TextPart correctly reflects the changes in image processing logic.

utils/ai/image-upload.ts (2)

1-7: LGTM! Well-defined allowed image types.

The constant properly defines supported image MIME types. Note that "image/jpg" is technically redundant since JPEG files should use "image/jpeg", but including both ensures compatibility with different implementations.


31-40: Effective SVG optimization with proper error handling.

The optimization function appropriately removes comments and unnecessary whitespace while maintaining a safe fallback. The regex patterns are correct for SVG content cleanup.

hooks/use-image-upload.ts (4)

2-8: Well-organized imports for enhanced functionality.

The new imports properly support the enhanced image validation and SVG processing capabilities.


16-16: Good addition for granular file removal.

The new REMOVE_BY_URL action type enables removing files by their temporary URL during error scenarios, which is more precise than index-based removal.


48-54: Enhanced file type validation with user feedback.

The strict validation against ALLOWED_IMAGE_TYPES with descriptive error messages improves user experience compared to generic file type checking.


1-167: Inconsistency with PR objectives detected.

The code changes implement image upload functionality, but the PR objectives describe a theme hotkey shortcut feature. This suggests either:

  1. The wrong files were included in this review
  2. The PR objectives description is incorrect
  3. This is part of a larger changeset not fully represented

Please verify that these image upload changes are part of the intended PR #170 for theme hotkey shortcuts, or if there's been a mixup with the files under review.

Likely an incorrect or invalid review comment.

hooks/use-debounced-callback.ts (1)

1-41: Well-implemented debounced callback hook with proper cleanup.

The hook correctly uses refs to store the latest callback and timeout ID, properly cleans up timeouts, and returns a memoized debounced function. The generic typing provides good flexibility for different callback signatures.

components/editor/keyboard-shortcut-overlay.tsx (2)

12-95: Well-structured keyboard shortcuts overlay with good accessibility.

The component properly manages event listeners, prevents body scrolling, handles outside clicks, and includes good accessibility features with ARIA attributes and semantic roles.


114-114: No z-index conflict detected for KeyboardShortcutOverlay
The z-[1000] on components/editor/keyboard-shortcut-overlay.tsx sits above all existing overlays and toasts (which range from z-50 to z-100) and will layer correctly. The only higher value (z-99999) is used internally by SelectContent within responsive dialogs and does not intersect with this standalone overlay. No change required.

store/action-store.ts (1)

1-56: Well-structured action store with proper null checking.

The Zustand store provides a clean centralized way to manage action handlers with proper null checking and warning logs.

utils/ai/generate-theme.tsx (3)

8-15: Enhanced visual content analysis instructions.

The expanded guidance for image and SVG analysis provides clear direction for extracting visual elements and translating them into theme tokens. The emphasis on matching colors, border radius, and shadows closely is particularly valuable.


16-26: Comprehensive typography guidance with Google Fonts integration.

The typography instructions provide excellent guidance for font selection, considering mood, style, and compatibility. The emphasis on popular, well-established fonts and matching font styles to visual content will improve theme quality.


29-36: Improved base theme handling and output formatting.

The clarified rules for base theme handling and stricter output formatting requirements (HEX colors only, shadow handling) will ensure more consistent and reliable theme generation.

.env.example (1)

29-31: Good addition of Google Fonts API key with documentation.

The new environment variable is well-documented with a helpful link to the Google Fonts Developer API documentation, supporting the new Google Fonts integration features.

components/editor/control-section.tsx (1)

18-18: Potential breaking change: className prop now targets inner content container

The className you pass to ControlSection is no longer merged onto the outer wrapper (mb-4 overflow-hidden rounded-lg border). It’s now applied solely to the inner content <div className={cn("bg-background border-t p-3", className)}>.

Please verify that no consumer relies on applying layout, margin, or positioning styles via className on the wrapper.

Affected locations:

  • components/editor/control-section.tsx
    • The outer <div> at line 18 no longer includes className from props.
    • Props’ className is merged into the inner content <div> at line 28.

  • components/editor/theme-control-panel.tsx
    • Usage at line 10: <ControlSection … className="p-3"> now applies an extra p-3 inside a container that already has p-3, rather than on the wrapper.

Next steps:

  • Audit all ControlSection usages for layout/margin dependencies on the outer container.
  • If any rely on wrapper‐level styles, refactor consumers or restore prop merging on the outer <div>.
components/get-pro-cta.tsx (1)

15-17: Verify GetProCTA behavior across all auth states

  • Confirmed GetProCTA is only imported and used in components/header.tsx (line 28).
  • No other components mix useSession with useSubscription.
  • Removing the session check leaves rendering solely to subscription status.

Please manually test these scenarios to ensure nothing regressed:
• Unauthenticated users (CTA should render)
• Pending subscriptions (nothing)
• Pro subscribers (nothing)

app/pricing/page.tsx (2)

16-17: Good SEO and import additions.

Adding proper metadata with title and robots directive improves SEO. The Testimonials import supports the new testimonials section.

Also applies to: 19-22


139-141: Testimonials section added with spacing adjustment.

The testimonials component is properly integrated with negative top margin to adjust spacing between sections.

components/editor/theme-preview/tabs-trigger-pill.tsx (1)

3-3: Improved type safety with direct component prop derivation.

Using React.ComponentPropsWithoutRef<typeof TabsTrigger> instead of importing TabsTriggerProps ensures the component props stay perfectly synchronized with the actual TabsTrigger component implementation.

Also applies to: 5-9

app/ai/components/ai-announcement.tsx (2)

8-13: Good subscription-based conditional rendering.

Hiding the announcement for Pro users or during loading states improves user experience by avoiding irrelevant upgrade prompts.


18-18: Updated to promote pricing instead of AI editor.

The change from promoting AI editor features to directing users to pricing aligns with the subscription-focused user journey.

Also applies to: 22-22

components/home/ai-generation-cta.tsx (1)

47-66: Good structural improvement and responsive design.

The refactoring improves semantic structure by having Link components wrap Button elements, and the responsive layout (flex-col to md:flex-row) enhances mobile experience. The addition of the "Get Pro" button aligns well with the subscription focus.

components/editor/action-bar/components/theme-toggle.tsx (1)

17-34: Excellent refactoring for consistency and maintainability.

The replacement of manual Tooltip components with TooltipWrapper and the use of the cn() utility for dynamic styling significantly improves code consistency and maintainability. The conditional styling logic is clean and appropriate.

app/not-found.tsx (1)

37-59: Well-designed 404 page with excellent UX.

The enhanced 404 page provides great user experience with clear messaging, theme switching capability, and theme preset exploration. The styling is consistent and the interactive elements add value beyond a typical error page.

hooks/use-font-search.ts (1)

1-48: Well-implemented font search hook.

The hook is well-structured with proper use of React Query, appropriate caching strategy (1 day stale time), and clean parameter interface. The pagination and error handling are implemented correctly.

Note: This component appears to be part of broader font management improvements rather than the core hotkey theme switching feature mentioned in the PR objectives.

components/editor/theme-control-panel.tsx (2)

3-27: Import reorganization looks good.

The migration to absolute imports using the @/ alias improves maintainability and the addition of font-related utilities aligns well with the new FontPicker integration.


360-405: FontPicker integration is well implemented.

The component properly handles font selection for each category (sans-serif, serif, monospace) with appropriate value extraction, font family building, and style updates. The use of getAppliedThemeFont with fallback to undefined ensures proper state management.

utils/theme-fonts.ts (1)

80-97: Improved font extraction logic.

The refactored getAppliedThemeFont function correctly:

  • Uses the current theme mode instead of defaulting to "light"
  • Handles both old and new font string formats
  • Properly filters out system fonts to prevent unnecessary loading attempts

This makes the font handling more robust and mode-aware.

components/theme-script.tsx (2)

7-41: Font loading utilities are properly implemented.

The inline JavaScript utilities correctly handle font extraction, URL building, and dynamic loading. While extractFontFamily duplicates logic from utils/theme-fonts.ts, this is necessary since inline scripts cannot import TypeScript modules.


78-96: Font loading integration is well placed.

The immediate font loading after theme application prevents FOUC, and the error handling ensures theme initialization continues even if font loading fails. The approach correctly handles all font categories.

components/editor/theme-preview/color-preview.tsx (1)

18-40: UI improvements enhance the color preview experience.

The styling updates provide better responsive behavior and improved visual hierarchy with the vertically stacked action buttons and refined spacing.

components/editor/font-picker.tsx (4)

38-85: Component setup and state management is well-structured.

Good implementation of debounced search, scroll management, and state handling. The auto-scroll to selected font enhances user experience.


94-116: Infinite scroll implementation is robust.

Proper use of Intersection Observer with appropriate cleanup and early loading trigger via rootMargin.


118-134: Font selection handler properly manages loading states.

Good error handling that logs issues without breaking the selection flow, ensuring resilient user experience even if font loading fails.


245-247: Smart preloading optimization.

Preloading fonts on hover reduces perceived latency when selecting, improving user experience without excessive bandwidth usage.

Also applies to: 255-255

components/editor/theme-preview-panel.tsx (2)

5-12: LGTM! Clean import consolidation

Good refactoring to use the local UI components instead of external primitives, which improves consistency and maintainability.


138-221: Well-structured tab content layout improvements

The consistent use of ScrollArea components and proper sizing classes improves the scroll behavior and visual containment. The explicit horizontal scrollbars on wide content (dashboard and mail demos) is a thoughtful UX improvement.

components/home/testimonials.tsx (1)

189-219: Well-implemented testimonial cards

Good accessibility implementation with proper link handling, image lazy loading, and avatar fallbacks. The text truncation with responsive line-clamp is a nice touch.

utils/fonts/google-fonts.ts (1)

5-31: LGTM! Robust API fetching with proper error handling

The function properly validates the API key, handles errors appropriately, and transforms the data to the internal format correctly.

utils/fonts/index.ts (1)

237-257: Well-implemented font loading with timeout

Excellent implementation using Promise.race for timeout handling and proper error catching. The function gracefully handles unavailable fonts and timeout scenarios.

Comment on lines +7 to +9
const cachedFetchGoogleFonts = unstable_cache(fetchGoogleFonts, ["google-fonts-catalogue"], {
tags: ["google-fonts-catalogue"],
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider using stable caching alternatives

The unstable_cache API is experimental and may change or be removed in future Next.js versions. Consider using a stable caching solution like Redis, in-memory cache with LRU, or Next.js ISR for production stability.

🤖 Prompt for AI Agents
In app/api/google-fonts/route.ts around lines 7 to 9, the use of the
experimental unstable_cache API poses a risk for future compatibility. Replace
unstable_cache with a stable caching solution such as Redis, an in-memory LRU
cache, or implement Next.js Incremental Static Regeneration (ISR) to ensure
production stability and maintainability.

Comment on lines +14 to +17
const query = searchParams.get("q")?.toLowerCase() || "";
const category = searchParams.get("category")?.toLowerCase();
const limit = Math.min(Number(searchParams.get("limit")) || 50, 100);
const offset = Number(searchParams.get("offset")) || 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

Add input validation for query parameters

The query parameter handling could be more robust. Number() returns NaN for invalid inputs, and optional chaining on searchParams.get() might not handle all edge cases.

-    const query = searchParams.get("q")?.toLowerCase() || "";
-    const category = searchParams.get("category")?.toLowerCase();
-    const limit = Math.min(Number(searchParams.get("limit")) || 50, 100);
-    const offset = Number(searchParams.get("offset")) || 0;
+    const query = (searchParams.get("q") || "").toLowerCase();
+    const category = searchParams.get("category")?.toLowerCase();
+    const limitParam = searchParams.get("limit");
+    const offsetParam = searchParams.get("offset");
+    
+    const limit = Math.min(
+      limitParam && !isNaN(Number(limitParam)) ? Number(limitParam) : 50,
+      100
+    );
+    const offset = offsetParam && !isNaN(Number(offsetParam)) ? Number(offsetParam) : 0;
📝 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 query = searchParams.get("q")?.toLowerCase() || "";
const category = searchParams.get("category")?.toLowerCase();
const limit = Math.min(Number(searchParams.get("limit")) || 50, 100);
const offset = Number(searchParams.get("offset")) || 0;
const query = (searchParams.get("q") || "").toLowerCase();
const category = searchParams.get("category")?.toLowerCase();
const limitParam = searchParams.get("limit");
const offsetParam = searchParams.get("offset");
const limit = Math.min(
limitParam && !isNaN(Number(limitParam)) ? Number(limitParam) : 50,
100
);
const offset = offsetParam && !isNaN(Number(offsetParam)) ? Number(offsetParam) : 0;
🤖 Prompt for AI Agents
In app/api/google-fonts/route.ts around lines 14 to 17, improve input validation
for query parameters by explicitly checking if the values retrieved from
searchParams are valid before converting them. For numeric parameters like limit
and offset, verify that the parsed numbers are not NaN and fall within expected
ranges, defaulting to safe values if invalid. For string parameters like query
and category, ensure they are non-null strings before applying toLowerCase,
handling cases where searchParams.get() returns null or unexpected types.

Comment on lines +13 to +15
const fontSans = themeState.styles.light["font-sans"];
const fontSerif = themeState.styles.light["font-serif"];
const fontMono = themeState.styles.light["font-mono"];
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider loading fonts from both light and dark theme styles.

Currently, only fonts from the light theme are loaded. If dark and light themes use different fonts, users switching themes might experience loading delays or missing fonts.

Consider loading fonts from both theme modes:

  const fontSans = themeState.styles.light["font-sans"];
  const fontSerif = themeState.styles.light["font-serif"];
  const fontMono = themeState.styles.light["font-mono"];
+ const darkFontSans = themeState.styles.dark["font-sans"];
+ const darkFontSerif = themeState.styles.dark["font-serif"];
+ const darkFontMono = themeState.styles.dark["font-mono"];
🤖 Prompt for AI Agents
In components/dynamic-font-loader.tsx around lines 13 to 15, the code only loads
fonts from the light theme styles, which can cause issues when switching to dark
theme if it uses different fonts. Update the code to also load fonts from the
dark theme styles by accessing themeState.styles.dark for "font-sans",
"font-serif", and "font-mono" and ensure both sets of fonts are included to
prevent loading delays or missing fonts when switching themes.

Comment on lines +16 to +49
const shortcuts = [
{
category: "EDITING",
items: [
{ action: "Apply random theme", keys: ["Space"] },
{ action: "Undo", keys: ["Ctrl", "Z"] },
{ action: "Redo", keys: ["Ctrl", "Y"] },
{ action: "Reset to current preset", keys: ["Ctrl", "R"] },
{ action: "Save theme", keys: ["Ctrl", "S"] },
]
},
{
category: "NAVIGATION",
items: [
{ action: "Next theme", keys: ["Ctrl", "→"] },
{ action: "Previous theme", keys: ["Ctrl", "←"] },
{ action: "Open AI tab", fun: () => console.log("Open AI Tab (Ctrl+Shift+O)"), keys: ["Ctrl", "Shift", "O"] },
{ action: "Toggle code panel", keys: ["Ctrl", "B"] },
]
},
{
category: "COPY",
items: [
{ action: "Copy theme CSS", keys: ["Ctrl", "Shift", "C"] },
{ action: "Copy registry command", keys: ["Ctrl", "Alt", "C"] },
]
},
{
category: "HELP",
items: [
{ action: "Show/hide shortcuts", keys: ["Ctrl", "/"] },
]
}
];
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider implementation status of listed shortcuts.

The overlay lists many keyboard shortcuts that may not be implemented yet. Ensure all listed shortcuts have corresponding functionality or mark unimplemented ones appropriately.


🏁 Script executed:

#!/bin/bash
# Description: Verify implementation of keyboard shortcuts mentioned in the overlay

# Search for Ctrl+Z, Ctrl+Y, Ctrl+R, Ctrl+S implementations
echo "Searching for undo/redo implementations..."
rg -A 5 -B 5 "Ctrl.*[ZY]|undo|redo" --type tsx --type ts

echo "Searching for save/reset implementations..."
rg -A 5 -B 5 "Ctrl.*[SR]|save.*theme|reset.*theme" --type tsx --type ts

echo "Searching for copy implementations..."
rg -A 5 -B 5 "Ctrl.*Shift.*C|copy.*css|copy.*registry" --type tsx --type ts

Length of output: 571


#!/bin/bash
set -e

echo "Searching for useHotkeys in codebase..."
rg -n "useHotkeys" -g ".ts" -g ".tsx" || echo "No useHotkeys found"

echo "Searching for document.addEventListener('keydown' in codebase..."
rg -n "addEventListener.keydown" -g ".ts" -g "*.tsx" || echo "No keydown listeners found"

declare -a patterns=(
"undo("
"redo("
"saveTheme"
"reset.*preset"
"applyRandomTheme"
"nextTheme"
"previousTheme"
"toggleCodePanel"
"copy.*css"
"copy.*registry"
"openAiTab"
"showHideShortcuts"
)

for p in "${patterns[@]}"; do
echo "Searching for handler pattern: $p"
rg -n "$p" -g ".ts" -g ".tsx" || echo " ↳ No matches for '$p'"
done


#!/bin/bash
set -e

Search for hook- or listener-based shortcut registrations

echo "🔍 Searching for useHotkeys calls..."
rg -n "useHotkeys" -g "*.{ts,tsx}" || echo " ↳ No useHotkeys hook found"

echo "🔍 Searching for direct keydown listeners..."
rg -n "addEventListener.keydown" -g ".{ts,tsx}" || echo " ↳ No direct keydown listeners found"

Define likely handler names derived from overlay actions

handlers=(
"applyRandomTheme"
"undo"
"redo"
"resetToCurrentPreset"
"saveTheme"
"nextTheme"
"previousTheme"
"openAiTab"
"toggleCodePanel"
"copyThemeCSS"
"copyRegistryCommand"
"showHideShortcuts"
)

Search for each handler in TS/TSX files

for h in "${handlers[@]}"; do
echo "🔍 Searching for handler: $h"
rg -n "$h" -g "*.{ts,tsx}" || echo " ↳ No matches for '$h'"
done


🏁 Script executed:

#!/usr/bin/env bash
set -e

echo "🔍 Searching for useHotkeys calls..."
rg -n "useHotkeys" -g "*.ts" -g "*.tsx" || echo "  ↳ No useHotkeys found"

echo "🔍 Searching for direct keydown listeners..."
rg -n "addEventListener.*keydown" -g "*.ts" -g "*.tsx" || echo "  ↳ No direct keydown listeners found"

handlers=(
  "applyRandomTheme"
  "undo"
  "redo"
  "resetToCurrentPreset"
  "saveTheme"
  "nextTheme"
  "previousTheme"
  "openAiTab"
  "toggleCodePanel"
  "copyThemeCSS"
  "copyRegistryCommand"
  "showHideShortcuts"
)

for h in "${handlers[@]}"; do
  echo "🔍 Searching for handler: $h"
  rg -n "$h" -g "*.ts" -g "*.tsx" || echo "  ↳ No matches for '$h'"
done

Length of output: 6882


Ensure listed shortcuts have corresponding handlers or are marked as unimplemented

The keyboard-shortcut overlay in components/editor/keyboard-shortcut-overlay.tsx (lines 16–49) currently shows several shortcuts that don’t have a matching implementation:

• NAVIGATION
– Next theme (Ctrl + →) — no nextTheme handler found
– Previous theme (Ctrl + ←) — no previousTheme handler found
• Open AI tab (Ctrl + Shift + O) — only logs to console via inline fun; needs a real openAiTab implementation or should be marked as “coming soon”
• Toggle code panel (Ctrl + B) — no toggleCodePanel handler found
• Copy theme CSS (Ctrl + Shift + C) — no copyThemeCSS function found
• Show/hide shortcuts (Ctrl + /) — overlay toggles itself via handleKeyDown, but there’s no dedicated showHideShortcuts handler; consider extracting this into a named function or removing it from the list

The following are properly hooked up:
• Apply random theme (applyRandomTheme)
• Undo/Redo (undo/redo)
• Reset to current preset (resetToCurrentPreset)
• Save theme (saveTheme)
• Copy registry command (copyRegistryCommand)

Action items:

  • Implement the missing handlers in their respective modules (e.g. theme-hotkey-handler, editorStore, code-panel)
  • Or remove/mark as unimplemented the shortcuts above until functionality is ready
🤖 Prompt for AI Agents
In components/editor/keyboard-shortcut-overlay.tsx between lines 16 and 49,
several keyboard shortcuts are listed without corresponding handler
implementations, such as nextTheme, previousTheme, toggleCodePanel,
copyThemeCSS, and a proper openAiTab function. To fix this, either implement
these missing handlers in their appropriate modules or mark these shortcuts as
unimplemented or coming soon in the UI. Additionally, extract the show/hide
shortcuts toggle logic into a dedicated named function or remove it from the
shortcuts list to maintain clarity and consistency.

Comment on lines +148 to 164
{navbarItems.map((item, i) => (
<motion.a
key={item}
key={item.label}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.2, delay: i * 0.05 }}
href={`#${item.toLowerCase().replace(/\s+/g, "-")}`}
href={item.href}
onClick={(e) => {
handleScrollToSection(e);
setMobileMenuOpen(false);
}}
className="group relative overflow-hidden py-2 text-sm font-medium"
>
<span className="relative z-10">{item}</span>
<span className="relative z-10">{item.href}</span>
<span className="bg-primary absolute bottom-0 left-0 h-0.5 w-0 transition-all duration-300 group-hover:w-full"></span>
</motion.a>
))}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix mobile menu text display bug.

Line 161 displays item.href instead of item.label, creating an inconsistency with the desktop menu where labels are shown. This will display URLs like "#examples" and "/pricing" instead of user-friendly text like "Examples" and "Pricing".

-                <span className="relative z-10">{item.href}</span>
+                <span className="relative z-10">{item.label}</span>
🤖 Prompt for AI Agents
In components/home/header.tsx between lines 148 and 164, the mobile menu
incorrectly displays item.href instead of item.label, causing URLs to show
instead of user-friendly labels. Update the span on line 161 to render
item.label so the mobile menu text matches the desktop menu labels.

triggerCodePanelOpen: () => void;

_handleResetClickRef: (() => void) | null;
setHandleRestClick: (handler: () => void) => void;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix method name typo.

The method name has a typo: setHandleRestClick should be setHandleResetClick.

- setHandleRestClick: (handler: () => void) => void;
+ setHandleResetClick: (handler: () => void) => void;
📝 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
setHandleRestClick: (handler: () => void) => void;
setHandleResetClick: (handler: () => void) => void;
🤖 Prompt for AI Agents
In store/action-store.ts at line 13, the method name setHandleRestClick contains
a typo. Rename the method to setHandleResetClick to correct the spelling.

},

_handleResetClickRef: null,
setHandleRestClick: (handler) => {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix method name typo in implementation.

The method implementation also has the same typo.

- setHandleRestClick: (handler) =>  {
+ setHandleResetClick: (handler) => {
📝 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
setHandleRestClick: (handler) => {
setHandleResetClick: (handler) => {
🤖 Prompt for AI Agents
In store/action-store.ts at line 45, the method name setHandleRestClick contains
a typo. Rename the method to setHandleResetClick to correct the spelling in both
the method definition and any references within the file.

Comment on lines +16 to +44
promptData.images.forEach((image) => {
if (image.url.startsWith("data:image/svg+xml")) {
try {
const dataUrlPart = image.url.split(",")[1];
let svgMarkup: string;

if (image.url.includes("base64")) {
svgMarkup = atob(dataUrlPart);
} else {
svgMarkup = decodeURIComponent(dataUrlPart);
}

content.push({
type: "text",
text: `Here is an SVG image for analysis:\n\`\`\`svg\n${svgMarkup}\n\`\`\``,
});
} catch (error) {
content.push({
type: "image",
image: image.url,
});
}
} else {
content.push({
type: "image",
image: image.url,
});
}
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance SVG data URL parsing robustness.

The SVG decoding logic is innovative but could be more robust. Consider these improvements:

  1. More precise base64 detection: Instead of image.url.includes("base64"), use a more specific pattern like /data:image\/svg\+xml;base64,/.test(image.url)

  2. Better data URL parsing: The current string splitting assumes a specific format. Consider using a more robust approach:

- const dataUrlPart = image.url.split(",")[1];
+ const match = image.url.match(/^data:image\/svg\+xml(?:;base64)?,(.+)$/);
+ if (!match) throw new Error("Invalid SVG data URL format");
+ const dataUrlPart = match[1];
  1. Add error logging: The catch block silently falls back to image handling. Consider logging the error for debugging:
} catch (error) {
+ console.warn("Failed to decode SVG data URL:", error);
  content.push({
  1. SVG sanitization: Consider validating the decoded SVG content for security before including it in the text prompt.
📝 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
promptData.images.forEach((image) => {
if (image.url.startsWith("data:image/svg+xml")) {
try {
const dataUrlPart = image.url.split(",")[1];
let svgMarkup: string;
if (image.url.includes("base64")) {
svgMarkup = atob(dataUrlPart);
} else {
svgMarkup = decodeURIComponent(dataUrlPart);
}
content.push({
type: "text",
text: `Here is an SVG image for analysis:\n\`\`\`svg\n${svgMarkup}\n\`\`\``,
});
} catch (error) {
content.push({
type: "image",
image: image.url,
});
}
} else {
content.push({
type: "image",
image: image.url,
});
}
});
promptData.images.forEach((image) => {
if (image.url.startsWith("data:image/svg+xml")) {
try {
// Use a strict regex to pull out the data part
const match = image.url.match(/^data:image\/svg\+xml(?:;base64)?,(.+)$/);
if (!match) throw new Error("Invalid SVG data URL format");
const dataUrlPart = match[1];
let svgMarkup: string;
if (image.url.includes("base64")) {
svgMarkup = atob(dataUrlPart);
} else {
svgMarkup = decodeURIComponent(dataUrlPart);
}
content.push({
type: "text",
text: `Here is an SVG image for analysis:\n\`\`\`svg\n${svgMarkup}\n\`\`\``,
});
} catch (error) {
console.warn("Failed to decode SVG data URL:", error);
content.push({
type: "image",
image: image.url,
});
}
} else {
content.push({
type: "image",
image: image.url,
});
}
});
🤖 Prompt for AI Agents
In utils/ai/message-converter.ts between lines 16 and 44, improve SVG data URL
parsing by replacing the base64 check with a precise regex test for
"data:image/svg+xml;base64,". Instead of splitting the URL string by a comma,
parse the data URL more robustly to extract the encoded data part. Add error
logging inside the catch block to record any decoding errors for debugging.
Additionally, before pushing the decoded SVG markup into content, implement a
sanitization step to validate and clean the SVG content for security.

Comment on lines +130 to +134
family: "Space Grotesk",
category: "serif",
variants: ["400", "600", "700"],
variable: false,
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix font category for Space Grotesk

Space Grotesk is a sans-serif font, not a serif font. This miscategorization could affect font filtering and fallback selection.

   {
     family: "Space Grotesk",
-    category: "serif",
+    category: "sans-serif",
     variants: ["400", "600", "700"],
     variable: false,
   },
📝 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
family: "Space Grotesk",
category: "serif",
variants: ["400", "600", "700"],
variable: false,
},
{
family: "Space Grotesk",
category: "sans-serif",
variants: ["400", "600", "700"],
variable: false,
},
🤖 Prompt for AI Agents
In utils/fonts/index.ts around lines 130 to 134, the font category for Space
Grotesk is incorrectly set to "serif". Change the category value from "serif" to
"sans-serif" to correctly reflect the font's classification and ensure proper
font filtering and fallback behavior.

Comment on lines +188 to +200
export function extractFontFamily(fontFamilyValue: string): string | null {
if (!fontFamilyValue) return null;

// Split by comma and get the first font
const firstFont = fontFamilyValue.split(",")[0].trim();

// Remove quotes if present
const cleanFont = firstFont.replace(/['"]/g, "");

// Skip system fonts
if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;
return cleanFont;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add validation for empty font family after processing

The function should validate that the extracted font family is not empty after processing.

 export function extractFontFamily(fontFamilyValue: string): string | null {
   if (!fontFamilyValue) return null;
 
   // Split by comma and get the first font
   const firstFont = fontFamilyValue.split(",")[0].trim();
 
   // Remove quotes if present
   const cleanFont = firstFont.replace(/['"]/g, "");
+  
+  // Check if font is empty after cleaning
+  if (!cleanFont) return null;
 
   // Skip system fonts
   if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;
   return cleanFont;
 }
📝 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
export function extractFontFamily(fontFamilyValue: string): string | null {
if (!fontFamilyValue) return null;
// Split by comma and get the first font
const firstFont = fontFamilyValue.split(",")[0].trim();
// Remove quotes if present
const cleanFont = firstFont.replace(/['"]/g, "");
// Skip system fonts
if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;
return cleanFont;
}
export function extractFontFamily(fontFamilyValue: string): string | null {
if (!fontFamilyValue) return null;
// Split by comma and get the first font
const firstFont = fontFamilyValue.split(",")[0].trim();
// Remove quotes if present
const cleanFont = firstFont.replace(/['"]/g, "");
// Check if font is empty after cleaning
if (!cleanFont) return null;
// Skip system fonts
if (SYSTEM_FONTS.includes(cleanFont.toLowerCase())) return null;
return cleanFont;
}
🤖 Prompt for AI Agents
In utils/fonts/index.ts around lines 188 to 200, the extractFontFamily function
does not check if the extracted font family string is empty after trimming and
removing quotes. Add a validation step after cleaning the font name to return
null if the resulting string is empty, ensuring the function only returns valid,
non-empty font family names.

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.

5 participants