-
-
Notifications
You must be signed in to change notification settings - Fork 365
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
base: main
Are you sure you want to change the base?
Conversation
…r key it randomly selects theme
@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. |
WalkthroughA new component, Changes
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
Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 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 withuseMemo
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
⛔ 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
Nice! Would you be interested in building it? |
…mo to to the available theme
Yes sure , Would love to ! |
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: This could also evolve into a cmd+k panel (like on ui.shadcn.com) if that's something you're interested in building! |
…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
This reverts commit 88261b5.
* 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]>
…r key it randomly selects theme
…mo to to the available theme
…aa5hi5h/tweakcn into feature/theme-hotkey-generation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 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 unusedfun
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 hardcodestrue
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 usesduration-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 incomponents/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 animationWhile 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 configurableThe 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
⛔ 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 explicitlabel
andhref
properties provide better type safety and clarity.
72-85
: Smart conditional scroll handling implementation.The conditional application of
handleScrollToSection
based onitem.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
androunded-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 ofTextPart
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:
- The wrong files were included in this review
- The PR objectives description is incorrect
- 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
Thez-[1000]
oncomponents/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 containerThe
className
you pass toControlSection
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 includesclassName
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 extrap-3
inside a container that already hasp-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 incomponents/header.tsx
(line 28).- No other components mix
useSession
withuseSubscription
.- 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 importingTabsTriggerProps
ensures the component props stay perfectly synchronized with the actualTabsTrigger
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 toundefined
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 fromutils/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 consolidationGood refactoring to use the local UI components instead of external primitives, which improves consistency and maintainability.
138-221
: Well-structured tab content layout improvementsThe 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 cardsGood 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 handlingThe 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 timeoutExcellent implementation using Promise.race for timeout handling and proper error catching. The function gracefully handles unavailable fonts and timeout scenarios.
const cachedFetchGoogleFonts = unstable_cache(fetchGoogleFonts, ["google-fonts-catalogue"], { | ||
tags: ["google-fonts-catalogue"], | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add 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.
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.
const fontSans = themeState.styles.light["font-sans"]; | ||
const fontSerif = themeState.styles.light["font-serif"]; | ||
const fontMono = themeState.styles.light["font-mono"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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", "/"] }, | ||
] | ||
} | ||
]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 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.
{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> | ||
))} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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, | ||
}); | ||
} | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance SVG data URL parsing robustness.
The SVG decoding logic is innovative but could be more robust. Consider these improvements:
-
More precise base64 detection: Instead of
image.url.includes("base64")
, use a more specific pattern like/data:image\/svg\+xml;base64,/.test(image.url)
-
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];
- 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({
- 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.
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.
family: "Space Grotesk", | ||
category: "serif", | ||
variants: ["400", "600", "700"], | ||
variable: false, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ 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.
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.
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
Video demo
Beautiful.themes.for.shadcn_ui.tweakcn._.Theme.Editor.Generator.-.Brave.2025-07-17.19-39-32.1.1.mp4
Checklist
Summary by CodeRabbit