Conversation
…xport animations.
…dd an exporting state indicator.
…sage limit checks to use Supabase RPC functions.
…for shared links.
… collection creation dialogs, and update usage limit service.
…utility for resolving subscription plan IDs.
…ript with a CI workflow to ensure consistency.
…ompts, and harden function search paths
…n for accurate tracking.
… improved user resolution to the Stripe webhook handler.
…ates for profiles, snippets, and usage limits.
… database triggers.
…component, client error boundary, and Next.js error pages.
…n actions by adding a new validation utility and integrating it into existing actions.
…hentication for routes and actions.
…directs, and refine CORS configurations.
…d upgrade prompts and backend reconciliation.
…ation and update friendly error component UI.
…able documentation from README.
…xes, and a script to verify index usage.
…ibute for buttons, refine video export count logic, and correct snippet update action return.
…ing a junction table.
…s, remote image patterns, and Cache-Control headers for static assets.
…ds, enhance validation schema for title, and integrate usage banner in navigation and home components.
… routes, enhancing security and access control. Additionally, improve rate limiting logic in enforceRateLimit function to handle user ID requirements more robustly.
…ionality in billing dialog. Enhance usage banner performance with memoization and improve rate limiter configuration for user ID handling.
…t Query v5, enhance type safety with UsageSummary, and make title in CreateAnimationInput optional.
…ponse management. Implement try-catch for image response creation, ensuring fallback to a default image on failure. Remove unused Logo component to streamline the code.
…ic and replay controls. Implement loop animation feature for smoother transitions upon completion. Improve state management for animation playback and user interactions.
…ng and theme color mapping for code snippets. Introduce utility functions for truncating code and rendering styled segments, improving visual presentation in generated images. Add a new module for theme colors to support various syntax highlighting styles.
…xport components. Implement hooks for managing watermark display in the AnimationDownloadMenu, GifExporter, VideoExporter, and EmbedView. Enhance user options in the EditorOptionsMenu to toggle watermark visibility based on user plan, improving customization for exports and embeds.
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
src/features/animation/video-exporter.tsx (1)
279-282: Theme name mismatch:nightOwlvsnight-owl.Same issue as in
embed-view.tsx- theisDarkBackgroundarray uses"nightOwl"but the theme key is"night-owl"insrc/lib/themes-options.ts.Apply this diff:
- const isDarkBackground = ['sublime', 'hyper', 'dracula', 'monokai', 'nord', 'gotham', 'blue', 'nightOwl'].includes( + const isDarkBackground = ['sublime', 'hyper', 'dracula', 'monokai', 'nord', 'gotham', 'blue', 'night-owl'].includes( editorSettings.backgroundTheme );src/app/api/og-image/route.tsx (2)
124-134: Theme key mismatch:nightOwlvsnight-owl.The
themeBackgroundsmap usesnightOwl(camelCase) butogThemeColorsinog-theme-colors.tsusesnight-owl(kebab-case). This mismatch causes the night-owl theme to always fall back to the default gradient.Additionally, several themes defined in
ogThemeColors(github-dark, candy, peach, teal, flamingo, creamy, ice) have no corresponding gradient inthemeBackgrounds.const themeBackgrounds: Record<string, string> = { sublime: "linear-gradient(135deg, #f472b6 0%, #a78bfa 50%, #60a5fa 100%)", hyper: "linear-gradient(135deg, #ec4899 0%, #8b5cf6 50%, #3b82f6 100%)", dracula: "linear-gradient(135deg, #ff79c6 0%, #bd93f9 50%, #6272a4 100%)", monokai: "linear-gradient(135deg, #f92672 0%, #66d9ef 50%, #a6e22e 100%)", nord: "linear-gradient(135deg, #88c0d0 0%, #81a1c1 50%, #5e81ac 100%)", gotham: "linear-gradient(135deg, #2aa889 0%, #599cab 50%, #4e5165 100%)", blue: "linear-gradient(135deg, #60a5fa 0%, #3b82f6 50%, #2563eb 100%)", - nightOwl: "linear-gradient(135deg, #c792ea 0%, #7fdbca 50%, #82aaff 100%)", + "night-owl": "linear-gradient(135deg, #c792ea 0%, #7fdbca 50%, #82aaff 100%)", };
267-274: Consider consolidating duplicate error handling or differentiating fallback behavior.Both the inner (line 267-270) and outer (line 271-274) catch blocks return identical empty
ImageResponse. If the intent is to handle Satori-specific errors differently, consider logging distinct messages or returning a more informative fallback image. Otherwise, the inner try/catch could be removed.If keeping both, differentiate the error context:
} catch (error) { - console.error("OG image generation failed", error); + console.error("Satori/ImageResponse rendering failed:", error); return new ImageResponse(<div />, size); } } catch (error) { - console.error("OG image generation failed", error); + console.error("OG image request processing failed:", error); return new ImageResponse(<div />, size); }
♻️ Duplicate comments (2)
src/features/code-editor/editor.tsx (2)
320-326: Add missing dependencies to useEffect.This was flagged in a previous review. The
useEffectis missingrefetchUsageandisUsageLoadingfrom its dependency array. React Query'srefetchfunctions are stable and safe to include.Apply this diff:
useEffect(() => { if (user_id && !isUsageLoading) { refetchUsage(); } - }, [user_id]); // Only depend on user_id to trigger on user change + }, [user_id, refetchUsage, isUsageLoading]);
608-616: Ref merging is correct but consider a comment.This was noted in a previous review. The merged ref pattern handles both function and object refs correctly. A brief comment explaining the purpose would improve maintainability.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (12)
src/actions/user/update-watermark-preference.ts(1 hunks)src/app/api/og-image/route.tsx(7 hunks)src/features/animation/animation-download-menu.tsx(1 hunks)src/features/animation/embed-view.tsx(2 hunks)src/features/animation/gif-exporter.tsx(3 hunks)src/features/animation/hooks/use-watermark-visibility.ts(1 hunks)src/features/animation/share-dialog/export-overlay.tsx(3 hunks)src/features/animation/video-exporter.tsx(3 hunks)src/features/code-editor/editor.tsx(10 hunks)src/features/user-tools/ui/editor-options-menu/index.tsx(6 hunks)src/features/user/queries.ts(2 hunks)src/lib/og-theme-colors.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
src/features/**
📄 CodeRabbit inference engine (AGENTS.md)
Feature flows are grouped in
src/features/*(e.g.,code-editor,snippets,share-code); keep new domain logic in its own feature folder.
Files:
src/features/animation/share-dialog/export-overlay.tsxsrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/animation/animation-download-menu.tsxsrc/features/animation/gif-exporter.tsxsrc/features/animation/embed-view.tsxsrc/features/animation/hooks/use-watermark-visibility.tssrc/features/user/queries.tssrc/features/code-editor/editor.tsxsrc/features/animation/video-exporter.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Files:
src/features/animation/share-dialog/export-overlay.tsxsrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/animation/animation-download-menu.tsxsrc/lib/og-theme-colors.tssrc/actions/user/update-watermark-preference.tssrc/app/api/og-image/route.tsxsrc/features/animation/gif-exporter.tsxsrc/features/animation/embed-view.tsxsrc/features/animation/hooks/use-watermark-visibility.tssrc/features/user/queries.tssrc/features/code-editor/editor.tsxsrc/features/animation/video-exporter.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Let Prettier (via ESLint) handle formatting; avoid manual style deviations.
Files:
src/features/animation/share-dialog/export-overlay.tsxsrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/animation/animation-download-menu.tsxsrc/lib/og-theme-colors.tssrc/actions/user/update-watermark-preference.tssrc/app/api/og-image/route.tsxsrc/features/animation/gif-exporter.tsxsrc/features/animation/embed-view.tsxsrc/features/animation/hooks/use-watermark-visibility.tssrc/features/user/queries.tssrc/features/code-editor/editor.tsxsrc/features/animation/video-exporter.tsx
src/{lib,utils}/**
📄 CodeRabbit inference engine (AGENTS.md)
Cross-cutting helpers live in
src/lib(config, analytics, query client) andsrc/utils(Supabase client, formatting).
Files:
src/lib/og-theme-colors.ts
src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Next.js App Router lives in
src/app(routes, layouts, metadata, providers). Shared stores sit insrc/app/store.
Files:
src/app/api/og-image/route.tsx
🧠 Learnings (5)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.
Applied to files:
src/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/animation/animation-download-menu.tsxsrc/features/user/queries.tssrc/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.
Applied to files:
src/features/user/queries.tssrc/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
Applied to files:
src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Applied to files:
src/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).
Applied to files:
src/features/code-editor/editor.tsx
🧬 Code graph analysis (8)
src/features/user-tools/ui/editor-options-menu/index.tsx (2)
src/app/store/index.ts (1)
useUserStore(46-53)src/features/user/queries.ts (3)
useWatermarkPreference(102-122)useUserPlan(55-79)useUpdateWatermarkPreference(124-177)
src/features/animation/animation-download-menu.tsx (5)
src/lib/config/plans.ts (1)
PlanId(8-8)src/features/login/index.tsx (1)
LoginDialog(28-107)src/components/ui/upgrade-dialog/index.tsx (1)
UpgradeDialog(105-369)src/features/animation/gif-exporter.tsx (1)
GifExporter(34-317)src/features/animation/share-dialog/export-overlay.tsx (1)
ExportOverlay(23-83)
src/actions/user/update-watermark-preference.ts (2)
src/lib/config/plans.ts (1)
PlanId(8-8)src/lib/services/user-preferences.ts (1)
updateWatermarkPreference(76-138)
src/features/animation/embed-view.tsx (4)
src/features/animation/hooks/use-watermark-visibility.ts (1)
useWatermarkVisibility(19-46)src/lib/themes-options.ts (1)
themes(1-74)src/lib/fonts-options.ts (1)
fonts(1-58)src/lib/utils/index.ts (1)
cn(10-12)
src/features/animation/hooks/use-watermark-visibility.ts (1)
src/features/user/queries.ts (1)
useWatermarkPreference(102-122)
src/features/user/queries.ts (5)
src/lib/services/usage-limits.ts (1)
UsageSummary(30-55)src/lib/sentry-utils.ts (1)
reportQueryError(11-32)src/lib/config/plans.ts (1)
PlanId(8-8)src/lib/services/user-preferences.ts (2)
WatermarkPreference(4-8)getWatermarkPreference(16-66)src/actions/user/update-watermark-preference.ts (1)
updateWatermarkPreferenceAction(17-81)
src/features/code-editor/editor.tsx (6)
src/actions/utils/action-result.ts (1)
error(18-20)src/app/store/index.ts (2)
EditorState(14-23)useEditorStore(68-162)src/features/animation/hooks/use-watermark-visibility.ts (1)
useWatermarkVisibility(19-46)src/features/user/queries.ts (2)
useUserUsage(31-53)USAGE_QUERY_KEY(11-11)src/features/snippets/queries.ts (1)
CreateSnippetProps(44-52)src/lib/services/usage-limits-cache.ts (1)
getUsageLimitsCacheProvider(155-160)
src/features/animation/video-exporter.tsx (3)
src/features/animation/index.ts (1)
AnimationFrame(6-6)src/lib/utils/index.ts (1)
cn(10-12)src/components/ui/logo/index.tsx (1)
Logo(7-134)
🔇 Additional comments (34)
src/actions/user/update-watermark-preference.ts (3)
1-11: LGTM!The server directive, imports, and result type definition are well-structured. The
UpdateWatermarkPreferenceResulttype provides a clear contract for callers with appropriate optional fields.
17-33: LGTM!Authentication flow correctly uses the Supabase server client and handles both auth errors and missing user cases with a clear error message.
73-81: LGTM!Success return and catch block follow consistent patterns. The error logging includes a helpful context tag for debugging.
src/features/code-editor/editor.tsx (6)
69-116: Well-structured utility for upgrade error detection.The
isUpgradeErrorfunction properly handles multiple error shapes (objects with code/type/message, strings) and uses explicit keyword matching. This is a clean approach for classifying errors that should trigger upgrade prompts.
118-139: Good extraction of rollback logic.The
revertEditorStatehelper consolidates the rollback pattern previously duplicated inonErrorandonSettled. The null-filtering with type guard is correct.
169-202: MutationObserver pattern is appropriate for cross-component DOM sync.The observer correctly watches for
data-exportingattribute changes set by the export menu. The cleanup disconnects properly. However, the initial check on line 196 happens synchronously before the observer is attached, which is fine for initialization.
352-433: Mutation typing and rollback pattern look correct.The
useMutationnow properly typesActionResult<Snippet>and storespreviousEditorStateandeditorIdin context for rollback. TheonSettledhandler correctly checks foractionResult?.errorbefore reverting state. Cache clearing before query invalidation prevents race conditions.
552-563: Good defensive checks before save.The validation for user session and usage data loading state prevents saves when data isn't ready. The error messages are clear and actionable.
792-811: Watermark rendering looks correct.The watermark conditionally renders based on
shouldShowWatermarkand usesgroup-data-[exporting=true]/export:flexto show only during exports. The positioning and styling are appropriate.src/features/animation/share-dialog/export-overlay.tsx (1)
20-34: Clean prop forwarding for watermark visibility.The
hideWatermarkprop is correctly added to the interface with a sensible default offalseand properly forwarded toVideoExporter.src/features/animation/gif-exporter.tsx (2)
26-43: Watermark visibility control properly implemented.The
hideWatermarkprop is correctly added with defaultfalseand used to conditionally render the watermark block.
281-312: Watermark rendering logic is correct.The conditional
!hideWatermarkcheck properly gates watermark rendering. The inline styles ensure consistent rendering during GIF export where CSS classes may not be captured correctly by the canvas.src/features/animation/embed-view.tsx (1)
218-235: Watermark renders correctly for embeds.The watermark is conditionally rendered based on
shouldShowWatermark. SinceuseWatermarkVisibility()is called without a userId (embed context doesn't have the owner's ID), it defaults to showing the watermark, which is the safe behavior noted in the comment on line 46-47.src/features/animation/hooks/use-watermark-visibility.ts (1)
1-46: Well-designed hook with safe defaults.The hook correctly implements watermark visibility logic:
- Shows watermark by default when loading or no data (safe default)
- Only hides when user has PRO plan AND has explicitly enabled the preference
- Uses
useMemoappropriately for the computed result- Good JSDoc documentation
src/features/animation/video-exporter.tsx (2)
27-51: Clean prop addition and dimension extraction.The
hideWatermarkprop is correctly added with defaultfalse. Extractingwidthandheightat the component level fromgetResolutionDimensionsis cleaner and avoids redundant calls.
346-371: Watermark conditional rendering is correct.The
!hideWatermarkcheck properly gates watermark rendering. The inline styles ensure consistent capture during video export.src/features/animation/animation-download-menu.tsx (5)
36-52: State initialization is well-organized.The component correctly initializes watermark visibility and UI state. The
upgradeContextstate properly tracks limit information for the upgrade dialog.
95-154: Optimistic increment pattern addresses the TOCTOU race condition.The export flow now performs an atomic increment via
incrementAndVerifyExportLimit()before starting the export, with proper rollback viadecrementExportCount()on failure. This addresses the previously identified race condition where multiple concurrent exports could bypass limits.
270-276: Consistent cancellation handling for GIF exports.The
onCancelledcallback properly decrements the export count, addressing the previously identified inconsistency between GIF and video export cancellation handling.
295-300: Consistent cancellation handling for video exports.The
ExportOverlay'sonCancellednow includes the same decrement logic as the GIF exporter, ensuring consistent behavior across both export paths.
186-222: Clean dialog and dropdown integration.The
LoginDialogandUpgradeDialogare correctly wired with their respective state. The dropdown menu items properly triggerhandleExportwith the appropriate format.src/features/user/queries.ts (6)
1-9: LGTM!Imports are well-organized and all appear necessary for the hooks and utilities defined in this file.
14-29: LGTM!The fetch function correctly handles user ID resolution with proper authentication checks.
31-53: LGTM!The error handling pattern via
useEffectcorrectly adapts to React Query v5's removal ofonErrorfromuseQuery, and properly delegates to the sharedreportQueryErrorutility. ThestaleTimeandenabledconfigurations are appropriate.
55-79: LGTM!Consistent implementation with
useUserUsage, correctly deriving the plan from the usage data with appropriate error handling.
102-122: LGTM!Consistent implementation following the established patterns in this file for query hooks with proper error handling.
147-156: Note: Optimistic update skipped when preference data is null.If
previousPreferenceisnull(e.g., user hasn't fetched preferences yet or column doesn't exist), the optimistic update won't apply. This is acceptable defensive behavior, but users in this state won't see immediate UI feedback until the mutation completes.src/lib/og-theme-colors.ts (1)
400-408: LGTM!The fallback mechanism and helper function are well-structured. The
getThemeColorsfunction correctly returns theme-specific colors or falls back to the default.src/app/api/og-image/route.tsx (4)
2-3: LGTM!The imports for syntax highlighting utilities and theme colors are correctly added and align with the cross-cutting helpers guideline for
src/lib. Based on coding guidelines, these utilities are appropriately placed.
107-122: LGTM!The syntax highlighting pipeline is well-structured with clear separation: truncation → highlighting → rendering. The theme colors integration correctly applies the background from the resolved theme.
240-256: LGTM!The watermark implementation uses only Satori-compatible CSS properties (position, color, fontSize, fontWeight, letterSpacing). The previous review concern about unsupported
filterandtransformproperties no longer applies to this simplified implementation.
221-236: LGTM!The dynamic rendering of syntax-highlighted segments is correctly implemented. The empty line handling with a transparent space character (line 234) ensures consistent line heights. Using array indices as keys is acceptable here since the content is static during render.
src/features/user-tools/ui/editor-options-menu/index.tsx (2)
98-124: Well-structured watermark preference handler.The sign-in check, PRO-only gating, and user feedback via toasts are well implemented. The mutation callbacks appropriately layer user-facing feedback on top of the data-layer error handling already present in the hook.
Note: The PRO check on line 104 relies on
isProbeing accurate, which is affected by the race condition flagged in the previous comment (lines 63-67). Once that is addressed, this handler will work correctly.
172-191: Clean watermark control UI with appropriate feature gating.The conditional rendering, PRO indicator, and state-based disabling create a clear user experience. The menu item correctly communicates the PRO-only nature of the feature.
The
disabledcondition on line 181 should also include plan loading state as noted in the earlier comment (lines 63-67) to prevent interaction during plan data fetch.
| if (!result.success) { | ||
| return { | ||
| success: false, | ||
| error: result.error || 'Failed to update preference', | ||
| requiresUpgrade: result.error?.includes('PRO'), | ||
| }; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Fragile string matching for requiresUpgrade detection.
Line 69 uses result.error?.includes('PRO') to detect if an upgrade is required. This is brittle — if the service's error message changes (e.g., "Upgrade to Pro plan" or localization), this detection will fail silently.
Consider either:
- Have
updateWatermarkPreferencereturn arequiresUpgradefield explicitly. - Use a more specific pattern match or error code.
- requiresUpgrade: result.error?.includes('PRO'),
+ // Note: relies on service error containing 'PRO' or 'Upgrade'
+ requiresUpgrade: result.error?.toLowerCase().includes('upgrade'),Or better, extend the service result type:
// In user-preferences.ts
Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>🤖 Prompt for AI Agents
In src/actions/user/update-watermark-preference.ts around lines 65 to 71, the
code uses fragile string matching result.error?.includes('PRO') to set
requiresUpgrade; update the service and callsite so the service returns an
explicit requiresUpgrade boolean (or an error code) in its result type (e.g.,
Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>) and
then use result.requiresUpgrade at the callsite; adjust the types/imports
accordingly and fall back to a safer pattern match only if the explicit flag is
absent.
…g Sentry integration and simplifying OG image generation. Refactor syntax highlighting to use lightweight pattern matching instead of highlight.js, and streamline theme color mappings for improved performance. Enhance Next.js configuration for package imports to further reduce bundle size.
…pet organization. Introduce multi-slide code animations export options and improve code snippet management by allowing users to save and organize snippets into collections. Update changelog component for better display and error handling in API requests.
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/api/og-image/route.tsx (1)
38-55: Empty catch block silently suppresses database fetch errors.The dynamic import and database fetch at lines 38-54 are wrapped in a try-catch that silently ignores all errors (line 55). This makes debugging difficult when slug-based fetches fail due to import errors, network issues, or database problems.
Add minimal error logging:
- } catch {} + } catch (error) { + console.error("Failed to fetch shared link data:", error); + }next.config.mjs (1)
132-156: InvalidX-Frame-Optionsvalue.
X-Frame-Options: ALLOWALLis not a valid value. The standard only definesDENY,SAMEORIGIN, orALLOW-FROM(obsolete). Browsers will ignore this invalid header.Since you already have
Content-Security-Policy: frame-ancestors *which allows embedding from any origin, you should either omitX-Frame-Optionsor remove it entirely as CSP takes precedence in modern browsers.Apply this diff to remove the invalid header:
{ source: "/animate/embed/:slug", headers: [ { key: "Content-Security-Policy", value: "frame-ancestors *", }, - { - key: "X-Frame-Options", - value: "ALLOWALL", - }, ], }, { source: "/animate/shared/:slug", headers: [ { key: "Content-Security-Policy", value: "frame-ancestors *", }, - { - key: "X-Frame-Options", - value: "ALLOWALL", - }, ], },
♻️ Duplicate comments (1)
src/app/api/og-image/route.tsx (1)
10-11: Type definition is incomplete foreditorproperty.A previous review noted that
backgroundThemeshould be included in thePayloadtype'seditorproperty. While the current code doesn't accessbackgroundTheme(uses hardcoded"sublime"), adding it now would enable the theme flexibility suggested in lines 72-74.Update the type to support theme customization:
-type Payload = { slides?: Slide[]; editor?: { fontFamily?: string } }; +type Payload = { slides?: Slide[]; editor?: { fontFamily?: string; backgroundTheme?: string } };
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (8)
README.md(3 hunks)instrumentation.ts(1 hunks)next.config.mjs(2 hunks)src/app/api/og-image/route.tsx(4 hunks)src/features/changelog/index.tsx(2 hunks)src/features/changelog/styles.ts(1 hunks)src/lib/og-theme-colors.ts(1 hunks)src/lib/services/changelog.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Files:
instrumentation.tssrc/app/api/og-image/route.tsxsrc/lib/services/changelog.tssrc/features/changelog/index.tsxsrc/lib/og-theme-colors.tssrc/features/changelog/styles.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Let Prettier (via ESLint) handle formatting; avoid manual style deviations.
Files:
instrumentation.tssrc/app/api/og-image/route.tsxsrc/lib/services/changelog.tssrc/features/changelog/index.tsxsrc/lib/og-theme-colors.tssrc/features/changelog/styles.ts
src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Next.js App Router lives in
src/app(routes, layouts, metadata, providers). Shared stores sit insrc/app/store.
Files:
src/app/api/og-image/route.tsx
src/{lib,utils}/**
📄 CodeRabbit inference engine (AGENTS.md)
Cross-cutting helpers live in
src/lib(config, analytics, query client) andsrc/utils(Supabase client, formatting).
Files:
src/lib/services/changelog.tssrc/lib/og-theme-colors.ts
src/features/**
📄 CodeRabbit inference engine (AGENTS.md)
Feature flows are grouped in
src/features/*(e.g.,code-editor,snippets,share-code); keep new domain logic in its own feature folder.
Files:
src/features/changelog/index.tsxsrc/features/changelog/styles.ts
🧠 Learnings (7)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Applied to files:
instrumentation.tsnext.config.mjs
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
Applied to files:
next.config.mjsREADME.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Run locally with `pnpm dev` (Next dev server). Production build: `pnpm build`; serve build: `pnpm start`. Lint & format: `pnpm lint` (ESLint with Prettier rules); auto-fix common issues: `pnpm lint:fix`.
Applied to files:
next.config.mjsREADME.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/app/** : Next.js App Router lives in `src/app` (routes, layouts, metadata, providers). Shared stores sit in `src/app/store`.
Applied to files:
next.config.mjs
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to .env.local : Store secrets in `.env.local`; never commit keys. Required keys include Supabase (`NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`) and Sentry/PostHog credentials where applicable.
Applied to files:
README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Install dependencies using `pnpm install` (repo tracks `pnpm-lock.yaml`).
Applied to files:
README.md
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use the `src/components/ui` primitives and `cn` helper for styling; align with existing Tailwind tokens and CSS variables in `src/app/globals.css`.
Applied to files:
src/features/changelog/styles.ts
🧬 Code graph analysis (3)
src/app/api/og-image/route.tsx (2)
src/lib/utils/og-syntax-highlight.ts (3)
truncateCodeForOG(131-141)highlightCodeForOG(62-69)renderStyledSegments(79-126)src/lib/og-theme-colors.ts (1)
getThemeColors(64-66)
src/lib/services/changelog.ts (2)
scripts/check-env.mjs (1)
result(9-9)src/actions/utils/action-result.ts (1)
error(18-20)
src/features/changelog/index.tsx (2)
src/components/ui/popover/index.tsx (1)
PopoverContent(42-42)src/components/ui/separator/index.tsx (1)
Separator(29-29)
🔇 Additional comments (10)
src/features/changelog/index.tsx (1)
37-37: LGTM! Good UI improvements.The addition of
max-h-[500px] overflow-y-autoon the PopoverContent (line 37) prevents the changelog popover from growing excessively and provides smooth scrolling for long content. Themt-3spacing on the Separator (line 108) improves visual separation between entries while correctly avoiding extra margin on the last item.Also applies to: 108-108
src/features/changelog/styles.ts (1)
14-14: LGTM! Layout change to vertical stacking.Changing the title from a horizontal row (
items-center) to a vertical column (flex-col) stacks the badge above the entry title text. This aligns well with the vertical content flow established by the popover height constraints and improves readability.src/lib/services/changelog.ts (1)
49-81: LGTM for client-side implementation.The client-side path correctly uses a relative URL and delegates API key handling to the server-side route. The timeout and error handling are appropriate. Once the server-side return shape is fixed (see previous comment), both paths will be consistent.
src/app/api/og-image/route.tsx (1)
90-92: LGTM! Watermark rendering is now Satori-compatible.The previous review flagged CSS
filterandtransformproperties that were unsupported in Satori. The current implementation uses only basic positioning (position: absolute,bottom,right) and text styling, which are fully supported.README.md (2)
13-16: Clear documentation of the new animations feature.The description effectively communicates the multi-slide animation capability and export options.
44-46: Good addition of environment documentation.Documenting the validation tooling and pointing users to
.env.exampleprovides a clear setup path for developers.next.config.mjs (4)
1-7: Well-structured environment handling.The side-effect import ensures validation runs early, and the fallback chain provides a sensible development default while allowing production override via
CORS_ALLOWED_ORIGINorNEXT_PUBLIC_APP_URL.
11-15: Appropriate use of bundle optimization.Using
optimizePackageImportsforhighlight.jsis a good optimization choice for a code-focused application.
53-80: Excellent cache strategy for static assets.The Cache-Control headers follow best practices: aggressive caching with
immutablefor fingerprinted assets, and shorter TTL withstale-while-revalidatefor dynamic content.
81-130: Secure and well-structured CORS configuration.The tiered approach is correct: public endpoints use wildcard CORS while authenticated APIs restrict to
appOrigin. The addition ofAuthorizationin allowed headers (Line 127) properly supports authenticated requests.
| export const ogThemeColors: Record<string, SyntaxColorMap> = { | ||
| // All themes use the same base colors to minimize bundle size | ||
| sublime: base, | ||
| hyper: base, | ||
| "github-dark": base, | ||
| monokai: base, | ||
| "night-owl": base, | ||
| nightOwl: base, | ||
| blue: base, | ||
| gotham: base, | ||
| candy: base, | ||
| peach: base, | ||
| teal: base, | ||
| flamingo: base, | ||
| creamy: base, | ||
| ice: base, | ||
| dracula: base, | ||
| nord: base, | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Theme consolidation trade-off accepted, but consider documenting the limitation.
All themes now reference the same base palette to minimize Edge Runtime bundle size (as noted in the file header). This resolves the duplication concern from the previous review but means the theme parameter is effectively ignored throughout the application.
Consider adding a JSDoc comment above the export to make this behavior explicit:
/**
* Theme color mappings for OG images.
* Note: All themes currently use the same base palette to optimize Edge Runtime bundle size.
* The theme parameter is accepted for API compatibility but does not affect colors.
*/
export const ogThemeColors: Record<string, SyntaxColorMap> = {🤖 Prompt for AI Agents
In src/lib/og-theme-colors.ts around lines 42 to 60, add a concise JSDoc/TSDoc
comment immediately above the exported ogThemeColors to document that all listed
themes currently reference the same `base` palette to minimize Edge Runtime
bundle size and that the theme parameter is accepted for API compatibility but
does not change colors; include the suggested text (brief title, one-line note
about bundle-size optimization, and one-line note that theme is accepted but
ignored) so consumers and future maintainers understand the limitation without
changing the existing export.
…idation logic from the updateWatermarkPreferenceAction. Enhance OG image generation to support customizable background themes in editor options. Improve user experience in EditorOptionsMenu by handling loading states for user plans and watermark preferences, ensuring smoother interactions during updates.
…uthentication context. Refactor Home component by removing unused Supabase client and user query logic, simplifying state management for loading states in CodeEditor.
…eries upon subscription activation. Update plan configuration retrieval to normalize legacy plan IDs, ensuring compatibility during database migration.
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/app/api/og-image/route.tsx (2)
28-34: Unuseddescriptionvariables are dead code.
descriptionOverride(line 29) anddescriptionFromDb(lines 33, 52) are declared and assigned but never used in the rendered output. Either remove them or include the description in the OG image if intended.const slugParam = searchParams.get("slug"); const titleOverride = searchParams.get("title"); - const descriptionOverride = searchParams.get("description"); let decodedPayload: Payload | null = null; let titleFromDb: string | undefined; - let descriptionFromDb: string | undefined;And remove the assignment on line 52:
titleFromDb = data.title ?? undefined; - descriptionFromDb = data.description ?? undefined;Also applies to: 52-53
36-55: Empty catch block silently masks shared link fetch failures.The
catch {}on line 55 swallows all errors from the dynamic imports andgetSharedLinkcall. This makes debugging OG image issues for shared links extremely difficult since failures leave no trace.} - } catch {} + } catch (error) { + console.error("Failed to fetch shared link for OG image:", error); + } } else if (payloadParam) {
♻️ Duplicate comments (5)
src/app/api/og-image/route.tsx (1)
78-101: Empty catch block masks ImageResponse rendering failures.This was flagged in a previous review. The inner catch (lines 99-100) returns an empty
<div/>whenImageResponseconstruction fails but provides no indication of what went wrong, making it difficult to diagnose rendering issues.Add error logging before returning the fallback:
- } catch { - return new ImageResponse(<div/>,size); + } catch (error) { + console.error("ImageResponse rendering failed:", error); + return new ImageResponse(<div />, size); }src/actions/user/update-watermark-preference.ts (1)
37-43: Good improvement, but fragile string matching remains.✅ Improvement noted: The duplicate PRO plan check has been correctly removed from this action (as suggested in past review), properly delegating plan validation to the service layer.
⚠️ Fragile detection persists: Line 41 still usesresult.error?.includes('PRO')to inferrequiresUpgrade. This string matching is brittle—if the service error message changes (e.g., localization, rewording to "Pro plan required"), the detection silently breaks.Recommended fix: Have the service return an explicit
requiresUpgradefield:Update the service return type in
user-preferences.ts:Promise<{ success: boolean; error?: string; requiresUpgrade?: boolean }>Then update this action to use the explicit flag:
if (!result.success) { return { success: false, error: result.error || 'Failed to update preference', - requiresUpgrade: result.error?.includes('PRO'), + requiresUpgrade: result.requiresUpgrade, }; }src/lib/config/plans.ts (3)
17-22: Docstring still claims function throws, but it returns empty string.The JSDoc says "Throws a clear error if the environment variable is missing" but the implementation logs a warning and returns an empty string (lines 26-33). This documentation mismatch was flagged in a previous review.
Apply this diff to align the documentation:
/** - * Validates and returns a Stripe price ID from environment variables. - * Throws a clear error if the environment variable is missing or empty. - * This ensures configuration errors are caught at startup rather than causing - * silent failures during checkout. + * Reads a Stripe price ID from environment variables. + * Logs a warning and returns an empty string if the variable is missing or empty. + * Validation happens at checkout time in API routes. */
186-187: Remove duplicate comment line.Line 187 duplicates the comment from line 186.
// Helper to get upgrade target (next plan tier) -// Helper to get upgrade target (next plan tier) export function getUpgradeTarget(currentPlan: PlanId): PlanId | null {
188-192: Consider deriving upgrade target fromplanOrderfor maintainability.As noted in a previous review, deriving the upgrade path from
planOrderwould keep the logic automatically in sync when tiers change.export function getUpgradeTarget(currentPlan: PlanId): PlanId | null { - if (currentPlan === 'free') return 'starter'; - if (currentPlan === 'starter') return 'pro'; - return null; // Pro has no upgrade target + const index = planOrder.indexOf(currentPlan); + if (index === -1 || index === planOrder.length - 1) return null; + return planOrder[index + 1]; }
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (10)
src/actions/user/update-watermark-preference.ts(1 hunks)src/app/api/og-image/route.tsx(4 hunks)src/app/checkout/success/checkout-success-client.tsx(1 hunks)src/app/providers.tsx(2 hunks)src/components/auth-provider/index.tsx(1 hunks)src/features/animation/embed-view.tsx(2 hunks)src/features/home/index.tsx(2 hunks)src/features/user-tools/ui/editor-options-menu/index.tsx(6 hunks)src/features/user/queries.ts(1 hunks)src/lib/config/plans.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Next.js App Router lives in
src/app(routes, layouts, metadata, providers). Shared stores sit insrc/app/store.
Files:
src/app/checkout/success/checkout-success-client.tsxsrc/app/providers.tsxsrc/app/api/og-image/route.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Files:
src/app/checkout/success/checkout-success-client.tsxsrc/components/auth-provider/index.tsxsrc/actions/user/update-watermark-preference.tssrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/app/providers.tsxsrc/features/user/queries.tssrc/features/animation/embed-view.tsxsrc/lib/config/plans.tssrc/app/api/og-image/route.tsxsrc/features/home/index.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Let Prettier (via ESLint) handle formatting; avoid manual style deviations.
Files:
src/app/checkout/success/checkout-success-client.tsxsrc/components/auth-provider/index.tsxsrc/actions/user/update-watermark-preference.tssrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/app/providers.tsxsrc/features/user/queries.tssrc/features/animation/embed-view.tsxsrc/lib/config/plans.tssrc/app/api/og-image/route.tsxsrc/features/home/index.tsx
src/components/**
📄 CodeRabbit inference engine (AGENTS.md)
Reusable building blocks are in
src/components(uidesign system, analytics, SEO helpers, session sync). Assets and marketing visuals sit insrc/assets.
Files:
src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/components/**/*.{ts,tsx}: Use thesrc/components/uiprimitives andcnhelper for styling; align with existing Tailwind tokens and CSS variables insrc/app/globals.css.
Surface upgrade prompts viaUpgradeDialogand show current usage withUsageStatsWidget.
Files:
src/components/auth-provider/index.tsx
src/features/**
📄 CodeRabbit inference engine (AGENTS.md)
Feature flows are grouped in
src/features/*(e.g.,code-editor,snippets,share-code); keep new domain logic in its own feature folder.
Files:
src/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/user/queries.tssrc/features/animation/embed-view.tsxsrc/features/home/index.tsx
src/{lib,utils}/**
📄 CodeRabbit inference engine (AGENTS.md)
Cross-cutting helpers live in
src/lib(config, analytics, query client) andsrc/utils(Supabase client, formatting).
Files:
src/lib/config/plans.ts
🧠 Learnings (6)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).
Applied to files:
src/app/checkout/success/checkout-success-client.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
Applied to files:
src/components/auth-provider/index.tsxsrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/user/queries.tssrc/lib/config/plans.tssrc/features/home/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.
Applied to files:
src/actions/user/update-watermark-preference.tssrc/features/user-tools/ui/editor-options-menu/index.tsxsrc/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.
Applied to files:
src/features/user/queries.tssrc/lib/config/plans.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Applied to files:
src/features/user/queries.tssrc/lib/config/plans.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).
Applied to files:
src/lib/config/plans.ts
🧬 Code graph analysis (9)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (1)
USAGE_QUERY_KEY(12-12)
src/components/auth-provider/index.tsx (1)
src/app/store/index.ts (1)
useUserStore(46-53)
src/actions/user/update-watermark-preference.ts (1)
src/lib/services/user-preferences.ts (1)
updateWatermarkPreference(76-138)
src/features/user-tools/ui/editor-options-menu/index.tsx (2)
src/app/store/index.ts (1)
useUserStore(46-53)src/features/user/queries.ts (3)
useWatermarkPreference(105-125)useUserPlan(68-92)useUpdateWatermarkPreference(127-206)
src/app/providers.tsx (2)
src/components/auth-provider/index.tsx (1)
AuthProvider(14-58)src/features/billing/components/portal-return-handler.tsx (1)
PortalReturnHandler(9-65)
src/features/user/queries.ts (3)
src/lib/sentry-utils.ts (1)
reportQueryError(11-32)src/lib/services/user-preferences.ts (2)
WatermarkPreference(4-8)getWatermarkPreference(16-66)src/actions/user/update-watermark-preference.ts (1)
updateWatermarkPreferenceAction(16-53)
src/features/animation/embed-view.tsx (6)
src/features/animation/share-utils.ts (1)
AnimationSharePayload(5-16)src/features/animation/hooks/use-watermark-visibility.ts (1)
useWatermarkVisibility(19-46)src/features/animation/analytics.ts (1)
trackAnimationEvent(7-16)src/lib/themes-options.ts (1)
themes(1-74)src/lib/fonts-options.ts (1)
fonts(1-58)src/lib/utils/index.ts (1)
cn(10-12)
src/app/api/og-image/route.tsx (2)
src/lib/utils/og-syntax-highlight.ts (3)
truncateCodeForOG(131-141)highlightCodeForOG(62-69)renderStyledSegments(79-126)src/lib/og-theme-colors.ts (1)
getThemeColors(64-66)
src/features/home/index.tsx (1)
src/features/code-editor/index.tsx (1)
CodeEditor(17-165)
🔇 Additional comments (16)
src/app/api/og-image/route.tsx (3)
1-11: LGTM - Types and imports are well-structured.The
Payloadtype now correctly includesbackgroundThemein the editor property, addressing a prior review concern. The imports are appropriately organized.
13-21: LGTM - Safe decoding with appropriate fallback.The
safeDecodePayloadfunction correctly returnsnullon decode failures, which is the expected behavior for invalid input. The caller handlesnullgracefully.
60-76: LGTM - Theme extraction and code processing are correct.The
backgroundThemeis now properly extracted from the payload with a sensible default. The code truncation, highlighting, and segment rendering pipeline follows the utility APIs correctly.src/actions/user/update-watermark-preference.ts (2)
19-32: LGTM! Clean authentication pattern.The authentication flow follows the established server action pattern and provides clear error messaging for unauthenticated requests.
45-52: LGTM! Standard error handling.The success path and catch block follow established conventions for server actions. Error logging aids debugging while keeping user-facing messages generic.
src/features/animation/embed-view.tsx (2)
46-49: LGTM! Safe default for embed watermark visibility.The hook correctly defaults to showing the watermark when no userId is provided. The comment accurately documents the current limitation and potential future enhancement to fetch the owner's preference.
52-62: LGTM! Theme backward compatibility addressed.Including both
"night-owl"and"nightOwl"variants ensures the dark background detection works regardless of which theme identifier format is used.src/features/user/queries.ts (2)
15-35: LGTM! Good extraction of user resolution logic.The
resolveUserIdhelper centralizes the authentication check and user ID resolution, reducing duplication across query functions. Good use of JSDoc documentation.
52-63: LGTM! Appropriate error handling pattern for React Query v5.The
useEffect-based error handling correctly adapts to React Query v5's removal ofonErrorfromuseQuery. The pattern is consistently applied across all query hooks.src/features/user-tools/ui/editor-options-menu/index.tsx (3)
61-68: LGTM! Plan loading race condition properly addressed.The implementation correctly:
- Captures
isPlanLoadingfromuseUserPlan(line 63)- Computes
isProonly when plan data has loaded (line 66)- Guards the handler against clicks during loading (lines 104-106)
- Disables the checkbox during all loading states (line 185)
This prevents the race condition where PRO users would see upgrade errors before their plan data loaded.
98-128: LGTM! Well-structured watermark preference handler.The handler properly validates prerequisites (sign-in, plan loaded, PRO status) before mutation, provides clear toast feedback for success and error scenarios, and correctly extracts error messages.
177-195: LGTM! Clean conditional rendering with proper UX.The watermark option is appropriately hidden for unauthenticated users, clearly indicates PRO requirement for non-PRO users, and handles loading/pending states correctly.
src/lib/config/plans.ts (3)
169-174: LGTM! Legacy plan ID normalization with clear documentation.The inline comment on line 171 clearly explains the backward compatibility purpose of the
'started'→'starter'mapping.
194-212: LGTM! Usage utility functions are well-implemented.The
isLimitReached,getUsagePercentage, andgetUsageColorhelpers handle edge cases correctly, including themax === 0scenario wherecurrent > 0returns 100%.
65-167: LGTM! Comprehensive plan configuration.The plan definitions are well-structured with clear limits, feature descriptions, and pricing tiers. The use of
Infinityfor Pro plan limits andnullfor Free pricing is appropriate.src/app/providers.tsx (1)
19-23: LGTM! Provider composition is correct.The nesting order ensures that
AuthProviderandPortalReturnHandlerboth have access toQueryClientProvider, andToasteris rendered within the authenticated context. This setup properly coordinates authentication state, billing portal returns, and UI feedback.
…user_id' for limit checks. Normalize plan values from database to ensure consistency across user plans. Enhance checkout success flow by integrating new query keys for user plan and billing info, improving cache invalidation logic. Refactor AuthProvider to handle auth state changes more robustly, ensuring UI consistency during user session updates.
There was a problem hiding this comment.
Actionable comments posted: 8
♻️ Duplicate comments (4)
src/actions/animations/create-animation.ts (1)
103-104: Redundant Infinity check remains.At line 90,
maxSlidesPerAnimation === Infinityis converted tonull. Therefore,upgradeMaxSlidesat line 103 can only benullor a number. TheupgradeMaxSlides === Infinitycheck at line 104 is unreachable.- const upgradeMaxSlidesDisplay = upgradeMaxSlides === null || upgradeMaxSlides === Infinity ? 'unlimited' : upgradeMaxSlides.toString() + const upgradeMaxSlidesDisplay = upgradeMaxSlides === null ? 'unlimited' : upgradeMaxSlides.toString()src/app/checkout/success/checkout-success-client.tsx (2)
38-46: Toast duration exceeds redirect delay.The toast is configured for 5000ms, but the redirect happens after 1500ms. Users will be redirected before seeing the full toast message, potentially missing important feedback.
Align the durations by reducing toast duration to match redirect delay:
toast.success(`Subscription activated!`, { description: `Your ${plan} subscription is now active.`, - duration: 5000, + duration: 1500, });Or increase redirect delay if you want users to read the full message:
const redirectTimer = setTimeout(() => { router.push("/"); - }, 1500); + }, 5000);
15-15: Remove unusedsessionIdprop.The
sessionIdprop is included in the effect dependency array but is never used in the effect body. With theexecutedRefguard in place, having it in the dependency array provides no functional benefit (the effect body won't re-execute anyway).Remove
sessionIdfrom the component interface:type CheckoutSuccessClientProps = { plan: string; - sessionId: string; }; export function CheckoutSuccessClient({ plan, - sessionId, }: CheckoutSuccessClientProps) { const router = useRouter(); const queryClient = useQueryClient(); const executedRef = useRef(false); useEffect(() => { // ... effect body - }, [plan, sessionId, router, queryClient]); + }, [plan, router, queryClient]);Note: If you implement the empty dependency array fix from the critical issue above, you can remove it from that array as well.
Also applies to: 20-20, 52-52
src/features/billing/components/portal-return-handler.tsx (1)
48-48: This issue has already been flagged in a previous review.The async
syncSubscription()call is not awaited, causing the success toast (lines 51-53) and URL cleanup (lines 56-63) to execute before the subscription sync completes. Please refer to the existing review comment for the recommended fix.
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (9)
src/actions/animations/create-animation.ts(1 hunks)src/actions/snippets/create-snippet.ts(2 hunks)src/app/checkout/success/checkout-success-client.tsx(1 hunks)src/components/auth-provider/index.tsx(1 hunks)src/features/billing/components/portal-return-handler.tsx(1 hunks)src/features/billing/queries.ts(1 hunks)src/features/home/index.tsx(3 hunks)src/features/user/queries.ts(1 hunks)src/lib/sentry-utils.ts(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/features/**
📄 CodeRabbit inference engine (AGENTS.md)
Feature flows are grouped in
src/features/*(e.g.,code-editor,snippets,share-code); keep new domain logic in its own feature folder.
Files:
src/features/billing/components/portal-return-handler.tsxsrc/features/home/index.tsxsrc/features/billing/queries.tssrc/features/user/queries.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Files:
src/features/billing/components/portal-return-handler.tsxsrc/features/home/index.tsxsrc/app/checkout/success/checkout-success-client.tsxsrc/features/billing/queries.tssrc/components/auth-provider/index.tsxsrc/lib/sentry-utils.tssrc/actions/animations/create-animation.tssrc/actions/snippets/create-snippet.tssrc/features/user/queries.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Let Prettier (via ESLint) handle formatting; avoid manual style deviations.
Files:
src/features/billing/components/portal-return-handler.tsxsrc/features/home/index.tsxsrc/app/checkout/success/checkout-success-client.tsxsrc/features/billing/queries.tssrc/components/auth-provider/index.tsxsrc/lib/sentry-utils.tssrc/actions/animations/create-animation.tssrc/actions/snippets/create-snippet.tssrc/features/user/queries.ts
src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Next.js App Router lives in
src/app(routes, layouts, metadata, providers). Shared stores sit insrc/app/store.
Files:
src/app/checkout/success/checkout-success-client.tsx
src/components/**
📄 CodeRabbit inference engine (AGENTS.md)
Reusable building blocks are in
src/components(uidesign system, analytics, SEO helpers, session sync). Assets and marketing visuals sit insrc/assets.
Files:
src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/components/**/*.{ts,tsx}: Use thesrc/components/uiprimitives andcnhelper for styling; align with existing Tailwind tokens and CSS variables insrc/app/globals.css.
Surface upgrade prompts viaUpgradeDialogand show current usage withUsageStatsWidget.
Files:
src/components/auth-provider/index.tsx
src/{lib,utils}/**
📄 CodeRabbit inference engine (AGENTS.md)
Cross-cutting helpers live in
src/lib(config, analytics, query client) andsrc/utils(Supabase client, formatting).
Files:
src/lib/sentry-utils.ts
🧠 Learnings (6)
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
Applied to files:
src/features/home/index.tsxsrc/components/auth-provider/index.tsxsrc/lib/sentry-utils.tssrc/actions/animations/create-animation.tssrc/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.
Applied to files:
src/app/checkout/success/checkout-success-client.tsxsrc/features/billing/queries.tssrc/components/auth-provider/index.tsxsrc/actions/snippets/create-snippet.tssrc/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).
Applied to files:
src/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Applied to files:
src/lib/sentry-utils.tssrc/features/user/queries.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).
Applied to files:
src/actions/animations/create-animation.ts
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.
Applied to files:
src/features/user/queries.ts
🧬 Code graph analysis (7)
src/features/billing/components/portal-return-handler.tsx (3)
src/actions/stripe/checkout.ts (1)
syncSubscription(219-279)src/features/user/queries.ts (2)
BILLING_INFO_QUERY_KEY(13-13)USAGE_QUERY_KEY(11-11)src/actions/utils/action-result.ts (1)
error(18-20)
src/features/home/index.tsx (1)
src/features/code-editor/index.tsx (1)
CodeEditor(17-165)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (3)
USAGE_QUERY_KEY(11-11)USER_PLAN_QUERY_KEY(12-12)BILLING_INFO_QUERY_KEY(13-13)
src/features/billing/queries.ts (2)
src/lib/services/billing.ts (4)
BillingInfo(129-138)getBillingInfo(175-280)PaymentMethodInfo(152-160)InvoiceInfo(162-170)src/features/user/queries.ts (1)
BILLING_INFO_QUERY_KEY(13-13)
src/components/auth-provider/index.tsx (2)
src/app/store/index.ts (1)
useUserStore(46-53)src/actions/utils/action-result.ts (1)
error(18-20)
src/actions/animations/create-animation.ts (7)
src/features/animations/dtos.ts (1)
Animation(3-12)src/lib/services/database/types.ts (1)
Animation(27-37)src/actions/utils/validation.ts (2)
createAnimationInputSchema(91-97)formatZodError(107-112)src/actions/utils/action-result.ts (2)
error(18-20)success(11-13)src/actions/utils/with-auth.ts (1)
withAuthAction(5-11)src/lib/services/usage-limits.ts (1)
UsageLimitCheck(21-28)src/lib/config/plans.ts (2)
getPlanConfig(170-174)getUpgradeTarget(188-192)
src/features/user/queries.ts (7)
src/lib/services/usage-limits.ts (2)
UsageSummary(30-55)getUserUsage(426-632)src/actions/utils/action-result.ts (1)
error(18-20)src/lib/sentry-utils.ts (1)
reportQueryError(12-35)src/lib/config/plans.ts (1)
PlanId(8-8)src/lib/services/user-preferences.ts (2)
WatermarkPreference(4-8)getWatermarkPreference(16-66)scripts/check-env.mjs (1)
result(9-9)src/actions/user/update-watermark-preference.ts (1)
updateWatermarkPreferenceAction(16-53)
🔇 Additional comments (28)
src/actions/animations/create-animation.ts (4)
22-30: LGTM: Clean validation pattern.The early schema validation with proper error formatting follows best practices. The try-catch wrapping the entire function provides a safety net for unexpected errors.
34-48: LGTM: Robust RPC call with proper guards.The withAuthAction wrapper correctly provides authenticated context. The null/object guard at lines 45-48 addresses the previous review concern about missing null checks for RPC responses.
50-71: LGTM: Comprehensive RPC response normalization.The dual snake_case/camelCase mapping handles potential response format variations, and the plan normalization ("started" → "starter") ensures consistency with the plan configuration system.
112-129: LGTM: Clean insertion and revalidation flow.The insertion uses proper defaults for optional fields, returns early on failure, and revalidates both relevant paths. Returning
data[0] as Animationis acceptable given the type assertion aligns with the expected DTO.src/actions/snippets/create-snippet.ts (4)
24-32: LGTM: Consistent validation pattern with animations.The schema validation and error formatting follow the same pattern as
create-animation.ts, maintaining consistency across the codebase.
36-51: LGTM: Robust RPC handling with null guards.The RPC call pattern and null/object validation follow the same structure as the animations action, addressing previous review comments about missing null checks.
81-90: LGTM: Plan-specific error messaging is clear and actionable.The error messages provide context (current count, max allowed, over-limit amount) and clear upgrade guidance, matching the pattern in
create-animation.ts.
92-110: LGTM: Clean insertion and revalidation.The insertion handles optional fields with sensible defaults, checks for empty results, and revalidates appropriate paths. The pattern is consistent with the animations action.
src/features/home/index.tsx (1)
138-138: Loading state now dynamic, but depends on flawed timer.This change addresses the past review finding about hardcoded
isLoading={false}. The loading state is now properly dynamic and will enable the Editor's skeleton UI.However, the loading state depends on the arbitrary 100ms timeout flagged above, which undermines reliability. Once that race condition is fixed with proper auth state synchronization, this implementation will work correctly.
This comment is contingent on fixing the critical issue in lines 39, 44-51.
src/features/billing/queries.ts (6)
1-11: LGTM: Clean imports and appropriate stale time configuration.The imports are well-organized, and the 5-minute stale time is appropriate for billing data. Good practice importing
BILLING_INFO_QUERY_KEYfrom the centralized user queries module.
13-30: LGTM: Proper authentication and user resolution.The function correctly handles both explicit userId and current user scenarios, with appropriate authentication checks before fetching billing data.
32-38: LGTM: Cache key issue properly resolved.The query key now correctly uses the
"current"sentinel when userId is undefined, ensuring cache key consistency with the resolved user ID infetchBillingInfo. This addresses the previously identified cache mismatch issue.
40-58: LGTM: Proper URL encoding and error handling.The function correctly encodes the customerId parameter and handles errors gracefully by returning null as a safe default. The logging aids debugging while maintaining stability.
60-69: LGTM: Well-configured React Query hook.The hook properly gates execution with the
enabledflag and uses conservative retry logic appropriate for external API calls. Configuration is consistent with the file's pattern.
71-88: LGTM: Consistent error handling with safe defaults.The function now consistently returns an empty array for all failure scenarios without throwing, addressing the previously identified inconsistent error handling. The safe default ensures consuming components remain stable.
src/components/auth-provider/index.tsx (1)
1-17: Excellent fix for the Supabase client memoization issue.The
useMemohook ensurescreateClient()is called only once, creating a stable client instance across renders. This resolves the previous critical issue of subscription leaks and prevents the effect from re-running continuously.src/lib/sentry-utils.ts (2)
12-35: LGTM!The production environment guard and browser check are properly in place. The error reporting flow with context, tags, and flush is well-structured. Per coding guidelines, Sentry changes are now correctly guarded behind environment checks.
45-83: LGTM!The billing issue reporter correctly implements production guards, severity levels, and contextual tagging. The error creation with a custom
BillingIssuename aids in Sentry filtering.src/features/user/queries.ts (4)
16-36: LGTM!Good extraction of the user resolution logic into a reusable helper. The JSDoc documentation clearly explains the behavior and error cases.
38-67: LGTM!The
useEffect-based error handling is the correct approach for React Query v5 sinceonErrorwas removed fromuseQueryoptions. Error reporting is properly delegated toreportQueryError.
69-93: LGTM!Consistent error handling pattern with
useUserUsage. The hook efficiently reusesfetchUserUsageto derive the plan.
1-14: LGTM! Clean imports withreportQueryErrorproperly abstracted. Query key constants are well-defined and properly exported for use across billing features.Note:
BILLING_INFO_QUERY_KEYis used insrc/features/billing/queries.tsand billing-related components for cache invalidation during subscription flows—the export pattern is correct.src/app/checkout/success/checkout-success-client.tsx (1)
7-11: Query key constants properly imported and used.The previous review comment about using constants for all query keys has been addressed. All three query keys (USAGE_QUERY_KEY, USER_PLAN_QUERY_KEY, BILLING_INFO_QUERY_KEY) are now imported as constants and used consistently throughout the effect.
Also applies to: 33-35
src/features/billing/components/portal-return-handler.tsx (5)
1-11: LGTM: Clean imports and proper client component setup.The imports are well-organized, and using the query key constants from the centralized location ensures consistency across the codebase.
12-18: LGTM: Proper ref pattern to prevent strict mode double-firing.The
handledRefcorrectly prevents the handler from executing twice during development strict mode while still allowing re-execution if the component remounts.
20-24: LGTM: Guard condition properly prevents duplicate handling.The combination of URL parameter check and ref guard ensures the handler executes exactly once per portal return.
55-63: LGTM: URL cleanup logic is correct.The parameter removal logic properly constructs the new URL and uses
router.replacewithscroll: falseto update the browser URL without page navigation or scroll jumps.
65-67: LGTM: Dependencies are complete and return is appropriate.The effect dependency array correctly includes all values referenced within the effect, and returning
nullis the right pattern for a handler component that manages side effects without rendering UI.
…billing feature, streamlining the UI and improving user experience by eliminating unnecessary actions.
… state handling from the code editor, simplifying the component and improving performance by eliminating unnecessary state and effects.
…. Introduce aggressive letter-spacing reset in animation components for improved visual consistency. Re-export proxy for future compatibility.
…codebase and ensuring compatibility with Next.js 16.
… billing management. Refactor AuthProvider to manage authentication state more effectively and streamline the CheckoutSuccessClient component by removing unnecessary execution guards. Update Home component to utilize new auth context for loading states in CodeEditor.
There was a problem hiding this comment.
Actionable comments posted: 11
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/home/index.tsx (2)
47-52: UsesearchParamshook consistently instead ofwindow.location.search.The component imports and uses
useSearchParams()at line 20, but this effect directly accesseswindow.location.search. This creates inconsistency and potential hydration issues in Next.js 16, especially with React 19's stricter hydration checks.Apply this diff to use the hook consistently:
useEffect(() => { - const queryParams = new URLSearchParams(location.search); + // Convert searchParams to URLSearchParams for compatibility with existing logic + const queryParams = new URLSearchParams(searchParams.toString()); if (queryParams.size === 0) { return; } const codeParam = queryParams.get("code"); // ... rest of the logic - }, []); + }, [searchParams]);Note: Adding
searchParamsto the dependency array ensures the effect runs when URL parameters change via client-side navigation.
54-68: Consider clarifying the code parameter validation logic.The validation logic with multiple early returns and regex patterns could be more readable. The intent appears to be filtering out authentication tokens (UUIDs) and invalid base64 strings, but this isn't immediately clear.
Consider adding comments or extracting validation to named functions:
const codeParam = queryParams.get("code"); const encodedPattern = new RegExp("^[0-9a-zA-Z+/=]*$", "i"); // pattern for base64 encoded string const loggedInPattern = new RegExp( "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", // pattern for signing in "i" ); + // Skip processing if code param is missing, invalid base64, or is an auth token (UUID) + const isAuthToken = codeParam && loggedInPattern.test(codeParam); + const isInvalidBase64 = codeParam && !encodedPattern.test(codeParam); + + if (!codeParam || isAuthToken || isInvalidBase64) { + return; + } - if (codeParam && !encodedPattern.test(codeParam)) { - return; - } - - if (codeParam && loggedInPattern.test(codeParam)) { - return; - }
♻️ Duplicate comments (7)
src/app/checkout/success/checkout-success-client.tsx (3)
18-21: Remove unusedsessionIdprop.The
sessionIdprop is destructured but never used in the component body. This was flagged in a previous review and remains unaddressed.Apply this diff to remove it:
type CheckoutSuccessClientProps = { plan: string; - sessionId: string; }; export function CheckoutSuccessClient({ plan, - sessionId, }: CheckoutSuccessClientProps) {
32-40: Toast duration exceeds redirect delay.The toast displays for 5000ms but the redirect executes after 1500ms, meaning users are redirected before seeing the full toast message. This was flagged in a previous review and remains unaddressed.
Apply this diff to align the durations:
toast.success(`Subscription activated!`, { description: `Your ${plan} subscription is now active.`, - duration: 5000, + duration: 1500, });
43-43: Add eslint-disable comment for intentional empty dependency array.The empty dependency array is correct for ensuring the effect runs only once on mount (avoiding the React StrictMode double-execution issue), but React's
exhaustive-depsrule will warn about missing dependencies (plan,router,queryClient). Add a disable comment to document the intentional choice.Apply this diff:
return () => clearTimeout(redirectTimer); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []);src/components/auth-provider/index.tsx (1)
69-75: Optional: Remove redundant['user']invalidation.Line 71 invalidates
['user']within theSIGNED_OUTbranch, but line 60 already invalidates['user']for all auth events (includingSIGNED_OUT). While React Query handles this gracefully, removing the duplicate improves code clarity.Apply this diff to remove the redundancy:
} else if (event === 'SIGNED_OUT') { // Invalidate user-specific queries on sign out, preserving public data - await queryClient.invalidateQueries({ queryKey: ['user'] }) await queryClient.invalidateQueries({ queryKey: ['billing-info'] }) await queryClient.invalidateQueries({ queryKey: ['user-plan'] }) await queryClient.invalidateQueries({ queryKey: ['collections'] }) router.refresh()src/features/animation/embed-view.tsx (1)
144-169: Missing dependencies:handleResetandhandlePlayPausenot in dependency array.The effect references
handleResetandhandlePlayPausebut they're not included in the dependency array at line 169. This can cause stale closure issues if these callbacks change. The auto-play effect (lines 126-142) correctly includes them.- }, [progress, isPlaying, isReady]); + }, [progress, isPlaying, isReady, handleReset, handlePlayPause]);src/features/billing/queries.ts (1)
74-91: Consider logging errors infetchInvoicesfor observability.Unlike
fetchPaymentMethod(line 58),fetchInvoicessilently swallows errors without logging. While the graceful fallback to an empty array is appropriate, adding a log would help with debugging and observability.} catch (error) { + console.error("Error fetching invoices:", error); return []; }src/features/code-editor/editor.tsx (1)
284-288: Missing dependencies in useEffect.The
useEffectreferencesrefetchUsageandisUsageLoadingbut only includesuser_idin the dependency array. While the comment indicates this is intentional, omitting stable function references violates the exhaustive-deps rule and could cause subtle bugs if React Query's implementation changes.useEffect(() => { if (user_id && !isUsageLoading) { refetchUsage(); } - }, [user_id]); // Only depend on user_id to trigger on user change + }, [user_id, refetchUsage, isUsageLoading]);
📜 Review details
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (10)
src/actions/snippets/create-snippet.ts(2 hunks)src/app/checkout/success/checkout-success-client.tsx(1 hunks)src/components/auth-provider/index.tsx(1 hunks)src/features/animation/embed-view.tsx(2 hunks)src/features/animation/layout-components.tsx(1 hunks)src/features/animation/shared-view.tsx(2 hunks)src/features/billing/index.tsx(1 hunks)src/features/billing/queries.ts(1 hunks)src/features/code-editor/editor.tsx(8 hunks)src/features/home/index.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/features/**
📄 CodeRabbit inference engine (AGENTS.md)
Feature flows are grouped in
src/features/*(e.g.,code-editor,snippets,share-code); keep new domain logic in its own feature folder.
Files:
src/features/animation/shared-view.tsxsrc/features/home/index.tsxsrc/features/animation/embed-view.tsxsrc/features/billing/index.tsxsrc/features/code-editor/editor.tsxsrc/features/billing/queries.tssrc/features/animation/layout-components.tsx
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use TypeScript-first with React function components; prefer server components unless interactivity is required ("use client").
Component folders use kebab-case; exported components use PascalCase, hooks/utilities use camelCase.
When developing analytics/Sentry changes, guard them behind environment checks to keep local runs noise-free.
Files:
src/features/animation/shared-view.tsxsrc/features/home/index.tsxsrc/app/checkout/success/checkout-success-client.tsxsrc/features/animation/embed-view.tsxsrc/features/billing/index.tsxsrc/features/code-editor/editor.tsxsrc/actions/snippets/create-snippet.tssrc/features/billing/queries.tssrc/features/animation/layout-components.tsxsrc/components/auth-provider/index.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Let Prettier (via ESLint) handle formatting; avoid manual style deviations.
Files:
src/features/animation/shared-view.tsxsrc/features/home/index.tsxsrc/app/checkout/success/checkout-success-client.tsxsrc/features/animation/embed-view.tsxsrc/features/billing/index.tsxsrc/features/code-editor/editor.tsxsrc/actions/snippets/create-snippet.tssrc/features/billing/queries.tssrc/features/animation/layout-components.tsxsrc/components/auth-provider/index.tsx
src/app/**
📄 CodeRabbit inference engine (AGENTS.md)
Next.js App Router lives in
src/app(routes, layouts, metadata, providers). Shared stores sit insrc/app/store.
Files:
src/app/checkout/success/checkout-success-client.tsx
src/components/**
📄 CodeRabbit inference engine (AGENTS.md)
Reusable building blocks are in
src/components(uidesign system, analytics, SEO helpers, session sync). Assets and marketing visuals sit insrc/assets.
Files:
src/components/auth-provider/index.tsx
src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/components/**/*.{ts,tsx}: Use thesrc/components/uiprimitives andcnhelper for styling; align with existing Tailwind tokens and CSS variables insrc/app/globals.css.
Surface upgrade prompts viaUpgradeDialogand show current usage withUsageStatsWidget.
Files:
src/components/auth-provider/index.tsx
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Use the `src/components/ui` primitives and `cn` helper for styling; align with existing Tailwind tokens and CSS variables in `src/app/globals.css`.
Applied to files:
src/features/animation/shared-view.tsxsrc/features/animation/layout-components.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: PRs should describe intent, note any env vars touched (e.g., `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, Sentry/PostHog keys), and link related issues. Include screenshots or short clips for UI changes.
Applied to files:
src/features/home/index.tsxsrc/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript-first with React function components; prefer server components unless interactivity is required (`"use client"`).
Applied to files:
src/app/checkout/success/checkout-success-client.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/lib/services/usage-limits.ts : Use `src/lib/services/usage-limits.ts` + `src/features/user/queries.ts` for limit checks and usage fetch/invalidation.
Applied to files:
src/app/checkout/success/checkout-success-client.tsxsrc/features/code-editor/editor.tsxsrc/actions/snippets/create-snippet.tssrc/features/billing/queries.tssrc/components/auth-provider/index.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Applies to src/components/**/*.{ts,tsx} : Surface upgrade prompts via `UpgradeDialog` and show current usage with `UsageStatsWidget`.
Applied to files:
src/features/billing/index.tsxsrc/features/code-editor/editor.tsx
📚 Learning: 2025-12-06T21:29:49.370Z
Learnt from: CR
Repo: danarocha-br/jolly-code PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-06T21:29:49.370Z
Learning: Free plan caps: 10 snippets, 10 animations, 5 slides per animation. Pro plan removes limits. Usage counts live in `profiles` and `usage_limits` with helper RPCs (`check_*_limit`, `increment_*`, `decrement_*`).
Applied to files:
src/features/code-editor/editor.tsx
🧬 Code graph analysis (7)
src/features/home/index.tsx (2)
src/components/auth-provider/index.tsx (1)
useAuth(18-24)src/features/code-editor/index.tsx (1)
CodeEditor(17-165)
src/app/checkout/success/checkout-success-client.tsx (1)
src/features/user/queries.ts (3)
USAGE_QUERY_KEY(11-11)USER_PLAN_QUERY_KEY(12-12)BILLING_INFO_QUERY_KEY(13-13)
src/features/animation/embed-view.tsx (6)
src/features/animation/share-utils.ts (1)
AnimationSharePayload(5-16)src/features/animation/hooks/use-watermark-visibility.ts (1)
useWatermarkVisibility(19-46)src/features/animation/analytics.ts (1)
trackAnimationEvent(7-16)src/lib/themes-options.ts (1)
themes(1-74)src/lib/fonts-options.ts (1)
fonts(1-58)src/lib/utils/index.ts (1)
cn(10-12)
src/features/code-editor/editor.tsx (5)
src/actions/utils/action-result.ts (1)
error(18-20)src/app/store/index.ts (2)
EditorState(14-23)useEditorStore(68-162)src/features/user/queries.ts (2)
useUserUsage(45-67)USAGE_QUERY_KEY(11-11)src/features/snippets/queries.ts (2)
CreateSnippetProps(44-52)createSnippet(412-447)src/lib/services/usage-limits-cache.ts (1)
getUsageLimitsCacheProvider(155-160)
src/actions/snippets/create-snippet.ts (6)
src/actions/utils/action-result.ts (3)
ActionResult(4-6)error(18-20)success(11-13)src/features/snippets/dtos.ts (1)
Snippet(1-9)src/actions/utils/validation.ts (2)
createSnippetInputSchema(75-81)formatZodError(107-112)src/actions/utils/with-auth.ts (1)
withAuthAction(5-11)src/lib/services/usage-limits.ts (1)
UsageLimitCheck(21-28)src/lib/services/database/snippets.ts (1)
insertSnippet(78-175)
src/features/billing/queries.ts (2)
src/lib/services/billing.ts (4)
BillingInfo(129-138)getBillingInfo(175-280)PaymentMethodInfo(152-160)InvoiceInfo(162-170)src/features/user/queries.ts (1)
BILLING_INFO_QUERY_KEY(13-13)
src/components/auth-provider/index.tsx (2)
src/app/store/index.ts (1)
useUserStore(46-53)src/actions/utils/action-result.ts (1)
error(18-20)
🪛 ast-grep (0.40.0)
src/features/animation/shared-view.tsx
[warning] 159-159: 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)
src/features/animation/embed-view.tsx
[warning] 201-201: 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)
src/features/animation/shared-view.tsx
[error] 160-160: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
src/features/animation/embed-view.tsx
[error] 202-202: 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 (24)
src/features/animation/layout-components.tsx (1)
114-114: LGTM: Typography normalization consistent with broader PR changes.The change from
letterSpacing: "normal"toletterSpacing: 0is syntactically correct and aligns with the typography normalization across other animation components (morphing-code-renderer.tsx, unified-canvas.tsx) and global styles mentioned in the AI summary. The numeric zero forces explicit zero spacing rather than relying on browser defaults.Note: This is a visual change that tightens letter spacing. The spread of
contentStyleallows consumer overrides if needed.src/features/animation/shared-view.tsx (1)
184-184: LGTM!The addition of the
idattribute appropriately targets the container for the style reset.src/app/checkout/success/checkout-success-client.tsx (1)
46-50: LGTM!The minimal loading state is appropriate for this brief transition, and the styling is consistent with the application's design system.
src/components/auth-provider/index.tsx (3)
1-24: LGTM: Clean context and hook setup.The imports, type definitions, and
useAuthhook follow standard React patterns. The error message for using the hook outside the provider is helpful.
26-37: LGTM: Proper component initialization.The memoized Supabase client (line 34) correctly addresses the previous critical issue about client recreation. The dual tracking of initialization (ref for immediate check, state for rendering) is a sound pattern.
38-109: Excellent implementation with robust error handling.The auth state listener correctly addresses all previous critical and major concerns:
- Stable client: Memoized Supabase client (line 34) prevents recreation on every render.
- Error resilience: Try-catch blocks (lines 58-90) ensure query failures don't break the auth flow, with the user store updated synchronously before any async operations.
- Selective invalidation:
SIGNED_OUTinvalidates only user-specific queries (lines 71-74), preserving public data like changelogs.- Proper cleanup: Subscription unsubscribes on unmount (line 95).
The initialization tracking with both ref and state is well-designed, and the nested error handling for the fallback
router.refresh()demonstrates defensive coding.src/features/animation/embed-view.tsx (4)
65-81: LGTM!Good defensive handling with fallback defaults for slide properties and proper memoization of derived values.
83-113: LGTM!Store initialization is well-structured with proper cleanup to reset the presentational mode. The spread pattern preserves existing state while applying payload overrides.
126-142: LGTM!Auto-play logic is well-guarded with ref to prevent re-triggering, includes proper cleanup, and correctly lists all dependencies including the handler callbacks.
247-264: LGTM!Watermark rendering correctly uses the
shouldShowWatermarkflag and applies appropriate text color based on background theme. TheisDarkBackgroundarray properly handles both naming conventions for backward compatibility.src/features/home/index.tsx (2)
99-101: LGTM: Two-stage loading properly handles mount and auth initialization.The component correctly implements a two-stage loading pattern:
- Wait for client-side mount (lines 99-101) to prevent hydration issues
- Wait for auth initialization (line 131) to prevent unauthorized content exposure
This approach properly addresses the previous race condition concerns while maintaining Next.js compatibility.
Also applies to: 131-131
19-19:isInitializedis properly exported fromAuthContextValue.Verification confirms that
isInitializedis correctly defined as a boolean property in theAuthContextValuetype and properly exported via theuseAuth()hook. The previous critical issues regarding hardcoded loading state and race conditions have been successfully addressed by implementing auth initialization state management.src/features/billing/index.tsx (4)
1-29: LGTM! Imports are well-organized.The imports follow a clear structure: external libraries first, then UI components, features, services, and local modules. The
"use client"directive is correctly placed for this interactive component.
31-75: LGTM! Well-structured data fetching with proper conditional loading.The component correctly:
- Conditionally fetches payment method and invoices only when
shouldShowBillingDetailsis true- Handles both active subscriptions and canceled subscriptions that still have access
- Uses the billing hooks with appropriate parameter gating
198-234: LGTM! Restore subscription flow is properly implemented.The dynamic import for
syncSubscriptionavoids loading Stripe code until needed. Thetoast.promisepattern provides good user feedback during the async operation. The previous duplication concern appears to have been addressed.
103-159: LGTM! Consistent error handling with user-friendly messages.The error alerts follow a consistent pattern and provide clear, helpful feedback to users. Each section handles its own error state independently.
src/features/billing/queries.ts (3)
1-14: LGTM! Query keys and constants are well-defined.Good practice:
- Re-using
BILLING_INFO_QUERY_KEYfrom@/features/user/queriesfor consistency- Exporting local query key constants for payment method and invoices
- Using a descriptive constant for stale time
43-72: LGTM! Robust payment method fetching with proper error handling.The implementation correctly:
- URL-encodes the
customerIdparameter (addressing previous feedback)- Returns
nullfor missing parameters or errors (graceful degradation)- Guards the hook with
enabled: Boolean(customerId)- Limits retries appropriately
93-102: LGTM! Consistent hook implementation.The
useInvoiceshook follows the established pattern with properenabledgating, retry configuration, and query key usage.src/actions/snippets/create-snippet.ts (1)
92-110: LGTM!The snippet creation flow is well-structured: proper null check on the result, path revalidation for cache invalidation, and returning the created snippet. The integration with
insertSnippetand error handling is correct.src/features/code-editor/editor.tsx (4)
68-115: Well-designed upgrade error detection utility.The
isUpgradeErrorfunction comprehensively handles multiple error shapes (objects with codes/types, string messages) and checks for relevant keywords. The implementation is defensive and correctly handles edge cases.
117-138: Good extraction of rollback logic.This helper consolidates the previously duplicated rollback logic between
onErrorandonSettledhandlers. The implementation correctly handles the edge cases and uses the store'sgetState()/setState()pattern appropriately.
314-395: Well-structured mutation with proper typing and rollback.The mutation now has proper generic types, eliminating the previous
anycast. The flow correctly:
- Stores previous state for rollback in
onMutate- Uses the extracted
revertEditorStatehelper in error paths- Clears the usage limits cache before query invalidation (lines 384-389) to prevent race conditions
- Conditionally opens upgrade dialog only for upgrade-related errors
514-550: Robust save flow with proper validation.The
handleSaveSnippetfunction includes all necessary guards:
- Session validation (lines 516-519)
- Usage data loading check (lines 521-525) to prevent race conditions
- Limit check with analytics tracking before showing upgrade dialog
This ensures a good user experience with clear messaging when limits are reached.
… and improve overLimit calculation. Refactor animation component to track views based on slug changes instead of a boolean state. Clean up unused state variables in embed view and code editor components for better performance and readability.
…matting and consistency.
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/code-editor/editor.tsx (2)
397-426: 🧹 Nitpick | 🔵 Trivial
removeSnippetmutation doesn't revert editor state on failure.
handleCreateSnippetstorespreviousEditorStatein its context and reverts viarevertEditorStateon error, buthandleRemoveSnippetonly reverts the query cache. If the server call fails,isSnippetSavedstaysfalsein the store while the snippet still exists on the server — the bookmark icon would incorrectly show "unsaved."Consider extending the context to include the previous editor state and reverting it in
onError, mirroring the create mutation pattern.♻️ Suggested fix
const { mutate: handleRemoveSnippet } = useMutation< void, Error, RemoveSnippetProps, - { previousSnippets: unknown } + { previousSnippets: unknown; previousEditorState: EditorState | null; editorId: string | undefined } >({ mutationFn: removeSnippet, onMutate: async () => { await queryClient.cancelQueries({ queryKey: queryKey }); const previousSnippets = queryClient.getQueryData(queryKey); + const editorId = currentEditor?.id; + const previousEditorState = currentEditor ? { ...currentEditor } : null; + useEditorStore.setState({ editors: editors.map((editor) => { if (editor.id === currentEditor?.id) { return { ...editor, isSnippetSaved: false, }; } return editor; }), }); - return { previousSnippets }; + return { previousSnippets, previousEditorState, editorId }; }, onError: (err, variables, context) => { if (context) { queryClient.setQueryData(queryKey, context.previousSnippets); + revertEditorState(context.previousEditorState, context.editorId); } },
513-548:⚠️ Potential issue | 🟡 MinorAdd an explicit null guard for
currentEditorbefore invoking the mutation.
currentEditoris typedEditorState | null. The?.+!pattern on lines 541 and 547 only silences TypeScript — at runtime,idwould beundefinedandstatewould benullifcurrentEditoris ever null. Currently protected by the rendering guard (button hidden whencode === ""), but this is fragile ifhandleSaveSnippetis later called from a hotkey or another code path.🛡️ Proposed fix
const handleSaveSnippet = () => { // Validate user session if (!user || !user_id) { toast.error("Please log in to save snippets."); return; } + if (!currentEditor) { + return; + } + // Wait for usage data to load before allowing save if (isUsageLoading || !usage) { toast.error("Loading your account limits. Please try again in a moment."); return; } // ... (snippetLimitReached check unchanged) handleCreateSnippet({ - id: currentEditor?.id!, + id: currentEditor.id, user_id, currentUrl: currentUrlOrigin, - title: currentEditor?.title || "Untitled", + title: currentEditor.title || "Untitled", code: code, language: language, - state: currentEditor!, + state: currentEditor, }); };
🤖 Fix all issues with AI agents
In `@src/actions/snippets/create-snippet.ts`:
- Around line 53-74: Extract the inline mapping in create-snippet.ts into a
shared function parseUsageLimitCheck(raw: unknown): UsageLimitCheck in a new
module `@/lib/services/usage-limits`, implementing the same logic: accept
camelCase or snake_case keys (canSave/can_save, overLimit/over_limit), default
handling for current/max/plan, and the "started" -> "starter" plan
normalization; replace the mapping in create-snippet.ts (and duplicate code in
create-animation.ts) to call parseUsageLimitCheck(limitCheckRaw) and import the
helper, and ensure the helper is exported and typed to return UsageLimitCheck so
both files use the same normalized result.
- Around line 84-85: The message interpolation uses max which is typed number |
null and can render "null" to users; update the free-plan branch in
create-snippet.ts (the if (plan === 'free') block where current, max, overLimit
are used) to handle max === null safely—choose a human-friendly fallback (e.g.,
"no limit" or a numeric fallback) or conditionally format the message so it
never inserts null, and use that sanitized string in the error(...) call.
- Around line 92-110: After verifying insertSnippet returned data, call the
Postgres RPC increment_snippet_count via the supabase client (await
supabase.rpc('increment_snippet_count', { p_user_id: user.id })) before
returning success; place this call after the existing data check (the block that
returns error on empty data) and before the final return success(data[0]), await
the RPC and handle or log a non-fatal failure so snippet creation still returns
the new snippet; reference insertSnippet, supabase, and increment_snippet_count
to locate the correct spot.
- Around line 38-40: The generated DB type for the RPC is wrong: update the
generated types declaration for check_snippet_limit so its Args uses
target_user_id: string (replace the incorrect p_user_id key) and ensure Returns
is Json; locate the check_snippet_limit entry in the generated types file (the
one that currently lists p_user_id) and change it to: Args: { target_user_id:
string } Returns: Json.
In `@src/features/animation/embed-view.tsx`:
- Around line 247-264: The watermark is currently controlled by
shouldShowWatermark and uses a hardcoded URL; update the logic so
shouldShowWatermark reflects the embed owner's subscription by reading the embed
metadata/owner plan passed into the component (e.g., add a prop like
embedOwnerPlan or fetch embed info inside EmbedView) and use that to decide
rendering in the component where shouldShowWatermark is evaluated, and replace
the hardcoded "https://jollycode.dev" in the anchor (the JSX around the anchor
tag and span in embed-view.tsx) with a configurable site URL or passed-in origin
(e.g., SITE_URL or a prop) so the link is not hardcoded.
- Around line 46-49: The embed view currently calls useWatermarkVisibility()
without context so it always defaults to showing watermarks; update the embed
flow to provide the embed creator's user ID to the hook (e.g.,
useWatermarkVisibility(ownerUserId)) or fetch the owner's watermark preference
server-side and include it in the share payload. Concretely, extend the
AnimationSharePayload to include ownerUserId (or ownerWatermarkPreference) and
ensure the code in embed-view.tsx passes that owner field into
useWatermarkVisibility (or uses the pref directly) so the watermark respects the
embed creator's subscription settings.
In `@src/features/billing/dtos.ts`:
- Around line 1-2: Remove the unused PlanId import: locate the import statement
that brings in PlanId (import type { PlanId } from "@/lib/config/plans";) at the
top of the billing DTOs and delete PlanId from that line so only the used
imports remain (e.g., keep the BillingInfo, PaymentMethodInfo, InvoiceInfo
import); ensure there are no remaining references to PlanId in any types in this
file.
In `@src/features/billing/queries.ts`:
- Around line 16-42: The hook useBillingInfo currently disables the query when
userId is falsy (enabled: Boolean(userId)), which makes the auth-fallback inside
fetchBillingInfo unreachable; fix by choosing one of two clear contracts: 1) If
the hook must always receive an explicit id, make userId required for
useBillingInfo (remove optional typing) and remove the auth-resolution fallback
from fetchBillingInfo (or throw early) so fetchBillingInfo only accepts a
concrete userId; or 2) If the hook should support the "current user" path,
change useBillingInfo to resolve the current user before enabling the query
(e.g., read current user from your auth context or call supabase.auth.getUser()
in the hook) and set enabled accordingly (or remove enabled) so
fetchBillingInfo’s fallback path is reachable; update references to
useBillingInfo, fetchBillingInfo, and the enabled flag to reflect the chosen
contract.
In `@src/features/code-editor/editor.tsx`:
- Around line 88-114: The code currently reassigns the function parameter
`error` when extracting a string from `errorObj`; instead create a new local
variable (e.g., `message`) to hold the extracted string and use that for
downstream checks. Specifically, when checking `if (typeof errorObj.message ===
"string")` or `if (typeof errorObj.error === "string")`, assign to `message`
(not `error`), then ensure `message` is the value used to build `errorLower`
(rename to `messageLower` if you prefer) and to feed
`upgradeKeywords.some(...)`; leave the original `error` parameter untouched and
keep `upgradeKeywords`, `errorObj`, and the keyword-check logic unchanged
otherwise.
| const { data: limitCheckRaw, error: limitError } = await supabase.rpc('check_snippet_limit', { | ||
| target_user_id: user.id | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for check_snippet_limit function definition in SQL migrations
find . -type f \( -name "*.sql" -o -name "*migration*" \) 2>/dev/null | head -20Repository: danarocha-br/jolly-code
Length of output: 1321
🏁 Script executed:
#!/bin/bash
# Search for check_snippet_limit in the entire codebase
rg "check_snippet_limit" -A 5 -B 2Repository: danarocha-br/jolly-code
Length of output: 12932
🏁 Script executed:
#!/bin/bash
# Check the most recent definition in migrations by sorting timestamps
rg "CREATE OR REPLACE FUNCTION public.check_snippet_limit" supabase/migrations/ -A 2 | head -50Repository: danarocha-br/jolly-code
Length of output: 964
🏁 Script executed:
#!/bin/bash
# Get the exact content of the database types file to confirm parameter name
cat src/types/database.ts | grep -A 10 "check_snippet_limit"Repository: danarocha-br/jolly-code
Length of output: 295
Update the TypeScript type definition to match the deployed SQL function signature.
The RPC parameter is correctly named target_user_id in the code and in the deployed SQL function (check_snippet_limit from migration 20251210120000), but src/types/database.ts incorrectly declares it as p_user_id. Update the generated types file to reflect:
check_snippet_limit: {
Args: {
target_user_id: string
}
Returns: Json
}🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 38 - 40, The generated
DB type for the RPC is wrong: update the generated types declaration for
check_snippet_limit so its Args uses target_user_id: string (replace the
incorrect p_user_id key) and ensure Returns is Json; locate the
check_snippet_limit entry in the generated types file (the one that currently
lists p_user_id) and change it to: Args: { target_user_id: string } Returns:
Json.
| // Map RPC response (can be camelCase or snake_case) to camelCase UsageLimitCheck type | ||
| const rpcResponse = limitCheckRaw as { | ||
| can_save?: boolean | ||
| canSave?: boolean | ||
| current?: number | null | ||
| max?: number | null | ||
| plan?: string | null | ||
| over_limit?: number | null | ||
| overLimit?: number | null | ||
| } | ||
|
|
||
| return success({ snippet: data[0], usage: updatedUsage }) | ||
| } catch (err) { | ||
| console.error('Error creating snippet:', err) | ||
| // Normalize plan value from database ("started" -> "starter") | ||
| const rawPlan = rpcResponse.plan ?? 'free'; | ||
| const normalizedPlan = rawPlan === 'started' ? 'starter' : rawPlan; | ||
|
|
||
| if (err instanceof Error && err.message.includes('authenticated')) { | ||
| return error('User must be authenticated') | ||
| const limitCheck: UsageLimitCheck = { | ||
| canSave: Boolean(rpcResponse.canSave ?? rpcResponse.can_save ?? false), | ||
| current: rpcResponse.current ?? 0, | ||
| max: rpcResponse.max ?? null, | ||
| plan: (normalizedPlan as UsageLimitCheck['plan']) ?? 'free', | ||
| overLimit: rpcResponse.overLimit ?? rpcResponse.over_limit ?? undefined, | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider extracting the RPC-response-to-UsageLimitCheck mapping into a shared helper.
This camelCase/snake_case normalization block (including plan renaming) is likely duplicated in create-animation.ts and possibly other actions. A shared parseUsageLimitCheck(raw: unknown): UsageLimitCheck helper in @/lib/services/usage-limits would reduce duplication and ensure consistent mapping.
🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 53 - 74, Extract the
inline mapping in create-snippet.ts into a shared function
parseUsageLimitCheck(raw: unknown): UsageLimitCheck in a new module
`@/lib/services/usage-limits`, implementing the same logic: accept camelCase or
snake_case keys (canSave/can_save, overLimit/over_limit), default handling for
current/max/plan, and the "started" -> "starter" plan normalization; replace the
mapping in create-snippet.ts (and duplicate code in create-animation.ts) to call
parseUsageLimitCheck(limitCheckRaw) and import the helper, and ensure the helper
is exported and typed to return UsageLimitCheck so both files use the same
normalized result.
| if (plan === 'free') { | ||
| return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`) |
There was a problem hiding this comment.
max can be null, producing a user-facing message like "allows null".
max is typed number | null (line 78). If the RPC ever returns null for a free-plan user, the interpolated message becomes "the Free plan allows null".
Suggested fix
- return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)
+ return error(`You have ${current} snippets but the Free plan allows ${max ?? 0}. Delete items or upgrade to save again. Over limit: ${overLimit}.`)📝 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.
| if (plan === 'free') { | |
| return error(`You have ${current} snippets but the Free plan allows ${max}. Delete items or upgrade to save again. Over limit: ${overLimit}.`) | |
| if (plan === 'free') { | |
| return error(`You have ${current} snippets but the Free plan allows ${max ?? 0}. Delete items or upgrade to save again. Over limit: ${overLimit}.`) | |
| } |
🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 84 - 85, The message
interpolation uses max which is typed number | null and can render "null" to
users; update the free-plan branch in create-snippet.ts (the if (plan ===
'free') block where current, max, overLimit are used) to handle max === null
safely—choose a human-friendly fallback (e.g., "no limit" or a numeric fallback)
or conditionally format the message so it never inserts null, and use that
sanitized string in the error(...) call.
| const data = await insertSnippet({ | ||
| id, | ||
| user_id: user.id, | ||
| title: title || 'Untitled', | ||
| code, | ||
| language, | ||
| url: url || null, | ||
| supabase | ||
| }) | ||
|
|
||
| if (!data || data.length === 0) { | ||
| return error('Failed to create snippet') | ||
| } | ||
|
|
||
| // Revalidate the snippets list | ||
| revalidatePath('/snippets') | ||
| revalidatePath('/') | ||
|
|
||
| return success(data[0]) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if there's a DB trigger or an explicit increment call for snippet count
echo "=== Searching for increment functions in SQL ==="
rg -n "increment" --type sql | head -30
echo ""
echo "=== Searching for triggers on snippets table ==="
rg -n "CREATE TRIGGER" --type sql | grep -i snippet
echo ""
echo "=== Searching for increment calls in TypeScript ==="
rg -n "increment_snippet\|increment_usage\|decrement_snippet" --type ts | head -20Repository: danarocha-br/jolly-code
Length of output: 4101
🏁 Script executed:
#!/bin/bash
echo "=== All CREATE TRIGGER statements in SQL files ==="
rg -n "CREATE TRIGGER" --type sql -A 3 | head -50
echo ""
echo "=== Looking for triggers related to snippets (broader search) ==="
rg -n "snippets" --type sql | grep -i trigger
echo ""
echo "=== Finding insertSnippet function ==="
fd -e ts -e tsx -e js -e jsx | xargs grep -l "insertSnippet" | head -10Repository: danarocha-br/jolly-code
Length of output: 1110
🏁 Script executed:
#!/bin/bash
echo "=== Examining insertSnippet function in snippets.ts ==="
cat -n src/lib/services/database/snippets.tsRepository: danarocha-br/jolly-code
Length of output: 10878
Add call to increment_snippet_count after successful snippet creation.
The insertSnippet function does not increment the usage count—there are no database triggers on the snippets table, and no explicit call to increment_snippet_count exists. The increment_snippet_count function is defined in the database but is never invoked during snippet creation, causing usage limits to fall out of sync. Add await supabase.rpc('increment_snippet_count', { p_user_id: user_id }) after the snippet is successfully inserted and added to the collection.
🤖 Prompt for AI Agents
In `@src/actions/snippets/create-snippet.ts` around lines 92 - 110, After
verifying insertSnippet returned data, call the Postgres RPC
increment_snippet_count via the supabase client (await
supabase.rpc('increment_snippet_count', { p_user_id: user.id })) before
returning success; place this call after the existing data check (the block that
returns error on empty data) and before the final return success(data[0]), await
the RPC and handle or log a non-fatal failure so snippet creation still returns
the new snippet; reference insertSnippet, supabase, and increment_snippet_count
to locate the correct spot.
| // Check if watermark should be shown | ||
| // Note: For embed view, we need to get the owner's user ID from the payload or slug | ||
| // For now, we'll show watermark by default for embeds (can be enhanced later to fetch owner preference) | ||
| const { shouldShowWatermark } = useWatermarkVisibility(); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd "useWatermarkVisibility\|use-watermark" --type f -e ts -e tsxRepository: danarocha-br/jolly-code
Length of output: 49
🏁 Script executed:
# Search for the hook definition
rg "useWatermarkVisibility" -t ts -t tsx -A 10 -B 2Repository: danarocha-br/jolly-code
Length of output: 94
🏁 Script executed:
# Check the AnimationSharePayload structure to understand what owner data is available
rg "AnimationSharePayload" -t ts -t tsx -A 10Repository: danarocha-br/jolly-code
Length of output: 94
🏁 Script executed:
# List relevant files first
git ls-files | grep -E "(watermark|embed-view)" | head -20Repository: danarocha-br/jolly-code
Length of output: 278
🏁 Script executed:
# Search for useWatermarkVisibility without type restrictions
rg "useWatermarkVisibility" -A 10 -B 2Repository: danarocha-br/jolly-code
Length of output: 5780
🏁 Script executed:
# Look at the embed-view file mentioned in the review
cat -n src/features/animation/embed-view.tsx | head -80Repository: danarocha-br/jolly-code
Length of output: 3351
🏁 Script executed:
# Check AnimationSharePayload structure
rg "AnimationSharePayload" -A 20 src/features/animation/share-utils.tsRepository: danarocha-br/jolly-code
Length of output: 1324
🏁 Script executed:
# Also check if there's owner info in the payload
cat -n src/features/animation/share-utils.ts | head -100Repository: danarocha-br/jolly-code
Length of output: 3497
🏁 Script executed:
# Look at the full use-watermark-visibility hook implementation
cat -n src/features/animation/hooks/use-watermark-visibility.tsRepository: danarocha-br/jolly-code
Length of output: 1767
useWatermarkVisibility() needs owner context to respect embed creator's preferences.
The hook accepts an optional userId parameter but is called without arguments, so it defaults to showing the watermark (shouldShowWatermark: true). For embeds, this should reflect the embed creator's subscription status, not the viewer's. A Pro user who paid to remove watermarks would still see them on their embedded animations.
The AnimationSharePayload structure (slides, settings, editor configuration) does not include the owner's user ID or watermark preference. Either:
- Extend
AnimationSharePayloadto includeownerUserIdand pass it to the hook, or - Fetch and include the owner's watermark preference server-side before encoding the payload
🤖 Prompt for AI Agents
In `@src/features/animation/embed-view.tsx` around lines 46 - 49, The embed view
currently calls useWatermarkVisibility() without context so it always defaults
to showing watermarks; update the embed flow to provide the embed creator's user
ID to the hook (e.g., useWatermarkVisibility(ownerUserId)) or fetch the owner's
watermark preference server-side and include it in the share payload.
Concretely, extend the AnimationSharePayload to include ownerUserId (or
ownerWatermarkPreference) and ensure the code in embed-view.tsx passes that
owner field into useWatermarkVisibility (or uses the pref directly) so the
watermark respects the embed creator's subscription settings.
| {/* Watermark */} | ||
| {shouldShowWatermark && ( | ||
| <a | ||
| href="https://jollycode.dev" | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="absolute bottom-6 right-6 flex items-center gap-0 hover:opacity-80 transition-opacity cursor-pointer" | ||
| > | ||
| <span | ||
| className={cn( | ||
| "text-xs font-medium tracking-wide", | ||
| isDarkBackground ? "text-white/90" : "text-foreground/90" | ||
| )} | ||
| > | ||
| jollycode.dev | ||
| </span> | ||
| </a> | ||
| )} |
There was a problem hiding this comment.
Watermark visibility depends on shouldShowWatermark — ensure it correctly reflects the embed owner's plan.
The watermark rendering is gated by shouldShowWatermark, which is good. However, as noted above, the hook may not have the context to determine the embed owner's subscription plan. If a Pro user embeds their animation, viewers (who are unauthenticated) would still see the watermark since the hook likely defaults to showing it.
Also, the watermark link at line 250 uses a hardcoded URL (https://jollycode.dev) — same concern as handleOpenOriginal.
🤖 Prompt for AI Agents
In `@src/features/animation/embed-view.tsx` around lines 247 - 264, The watermark
is currently controlled by shouldShowWatermark and uses a hardcoded URL; update
the logic so shouldShowWatermark reflects the embed owner's subscription by
reading the embed metadata/owner plan passed into the component (e.g., add a
prop like embedOwnerPlan or fetch embed info inside EmbedView) and use that to
decide rendering in the component where shouldShowWatermark is evaluated, and
replace the hardcoded "https://jollycode.dev" in the anchor (the JSX around the
anchor tag and span in embed-view.tsx) with a configurable site URL or passed-in
origin (e.g., SITE_URL or a prop) so the link is not hardcoded.
| import type { PlanId } from "@/lib/config/plans"; | ||
| import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing"; |
There was a problem hiding this comment.
Unused import: PlanId is imported but never referenced.
PlanId from @/lib/config/plans is not used in any type definition in this file.
Proposed fix
-import type { PlanId } from "@/lib/config/plans";
-import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";
+import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing";📝 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.
| import type { PlanId } from "@/lib/config/plans"; | |
| import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing"; | |
| import type { BillingInfo, PaymentMethodInfo, InvoiceInfo } from "@/lib/services/billing"; |
🤖 Prompt for AI Agents
In `@src/features/billing/dtos.ts` around lines 1 - 2, Remove the unused PlanId
import: locate the import statement that brings in PlanId (import type { PlanId
} from "@/lib/config/plans";) at the top of the billing DTOs and delete PlanId
from that line so only the used imports remain (e.g., keep the BillingInfo,
PaymentMethodInfo, InvoiceInfo import); ensure there are no remaining references
to PlanId in any types in this file.
| export const fetchBillingInfo = async ( | ||
| userId?: string | ||
| ): Promise<BillingInfo | null> => { | ||
| const supabase = createClient(); | ||
| const resolvedUserId = | ||
| userId ?? | ||
| ( | ||
| await supabase.auth.getUser().then(({ data }) => { | ||
| return data.user?.id; | ||
| }) | ||
| ); | ||
|
|
||
| if (!resolvedUserId) { | ||
| throw new Error("User not authenticated"); | ||
| } | ||
|
|
||
| return getBillingInfo(supabase, resolvedUserId); | ||
| }; | ||
|
|
||
| export const useBillingInfo = (userId?: string) => { | ||
| return useQuery<BillingInfo | null>({ | ||
| queryKey: [BILLING_INFO_QUERY_KEY, userId ?? "current"], | ||
| queryFn: () => fetchBillingInfo(userId), | ||
| staleTime: BILLING_QUERY_STALE_TIME_MS, | ||
| enabled: Boolean(userId), | ||
| }); | ||
| }; |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
useBillingInfo with enabled: Boolean(userId) makes the auth-fallback path in fetchBillingInfo unreachable from the hook.
fetchBillingInfo resolves the current user from Supabase auth when userId is undefined (lines 20–26), and the query key uses "current" for that case (line 37). However, enabled: Boolean(userId) (line 40) disables the query when userId is falsy, so the fallback path is dead code when invoked through the hook.
This isn't a bug (the enabled guard correctly prevents the "User not authenticated" throw flagged in a previous review), but the two intents are contradictory. Consider aligning them:
- If the hook should always require an explicit
userId, remove the auth-resolution fallback fromfetchBillingInfo(or makeuserIdrequired) so the contract is clear. - If the hook should support the "current user" path, replace the
enabledguard with a pre-resolved userId (e.g., passed from an auth context) so the query can fire.
🤖 Prompt for AI Agents
In `@src/features/billing/queries.ts` around lines 16 - 42, The hook
useBillingInfo currently disables the query when userId is falsy (enabled:
Boolean(userId)), which makes the auth-fallback inside fetchBillingInfo
unreachable; fix by choosing one of two clear contracts: 1) If the hook must
always receive an explicit id, make userId required for useBillingInfo (remove
optional typing) and remove the auth-resolution fallback from fetchBillingInfo
(or throw early) so fetchBillingInfo only accepts a concrete userId; or 2) If
the hook should support the "current user" path, change useBillingInfo to
resolve the current user before enabling the query (e.g., read current user from
your auth context or call supabase.auth.getUser() in the hook) and set enabled
accordingly (or remove enabled) so fetchBillingInfo’s fallback path is
reachable; update references to useBillingInfo, fetchBillingInfo, and the
enabled flag to reflect the chosen contract.
|
|
||
| // Extract message from error object if available | ||
| if (typeof errorObj.message === "string") { | ||
| error = errorObj.message; | ||
| } else if (typeof errorObj.error === "string") { | ||
| error = errorObj.error; | ||
| } | ||
| } | ||
|
|
||
| // Handle string errors | ||
| if (typeof error !== "string") { | ||
| return false; | ||
| } | ||
|
|
||
| const errorLower = error.toLowerCase(); | ||
|
|
||
| // Check for explicit upgrade-related keywords | ||
| const upgradeKeywords = [ | ||
| "upgrade", | ||
| "limit reached", | ||
| "plan limit", | ||
| "reached your", | ||
| "exceeded", | ||
| "over limit", | ||
| ]; | ||
|
|
||
| return upgradeKeywords.some((keyword) => errorLower.includes(keyword)); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Avoid reassigning the error parameter; use a local variable instead.
Reassigning the function parameter error on lines 91/93 to change its type from object to string makes the control flow harder to follow. Extract the string message into a separate variable.
♻️ Suggested refactor
- // Extract message from error object if available
- if (typeof errorObj.message === "string") {
- error = errorObj.message;
- } else if (typeof errorObj.error === "string") {
- error = errorObj.error;
- }
- }
-
- // Handle string errors
- if (typeof error !== "string") {
- return false;
- }
-
- const errorLower = error.toLowerCase();
+ // Extract message from error object if available
+ const msg = typeof errorObj.message === "string"
+ ? errorObj.message
+ : typeof errorObj.error === "string"
+ ? errorObj.error
+ : null;
+ if (typeof msg === "string") {
+ return upgradeKeywords.some((kw) => msg.toLowerCase().includes(kw));
+ }
+ return false;
+ }
+
+ // Handle string errors
+ if (typeof error !== "string") {
+ return false;
+ }
+
+ const errorLower = error.toLowerCase();🤖 Prompt for AI Agents
In `@src/features/code-editor/editor.tsx` around lines 88 - 114, The code
currently reassigns the function parameter `error` when extracting a string from
`errorObj`; instead create a new local variable (e.g., `message`) to hold the
extracted string and use that for downstream checks. Specifically, when checking
`if (typeof errorObj.message === "string")` or `if (typeof errorObj.error ===
"string")`, assign to `message` (not `error`), then ensure `message` is the
value used to build `errorLower` (rename to `messageLower` if you prefer) and to
feed `upgradeKeywords.some(...)`; leave the original `error` parameter untouched
and keep `upgradeKeywords`, `errorObj`, and the keyword-check logic unchanged
otherwise.
Summary by CodeRabbit
New Features
Improvements
Bug Fixes