Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughAdds dark-mode and full-logo variants to Project data and surfaces them through DB migrations, Prisma schema, API shapes, admin UI, editor types, email rendering, and tests. Email rendering now accepts a Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant Route as API Route
participant DB as Project Store
participant Renderer as Email Renderer
participant Theme as EmailTheme / ProjectLogo
Route->>DB: fetch project (logoUrl, logoFullUrl, logoDarkModeUrl, logoFullDarkModeUrl)
DB-->>Route: return project logo fields
Route->>Renderer: renderEmailWithTemplate(templateId, data, { themeProps: { unsubscribeLink, projectLogos } })
Renderer->>Theme: mount EmailTheme with {...themeProps}
Theme->>Theme: ProjectLogo selects variant by mode (light/dark) and chooses Logo/FullLogo
Theme-->>Renderer: rendered email HTML with chosen logo
Renderer-->>Route: return rendered HTML / payload
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (2)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
🔇 Additional comments (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Claude finished @fomalhautb's task —— View job Pull Request ReviewReviewing the "Email templates with logo" changes for code quality, security, performance, and potential issues. Todo List
Review SummaryThis PR adds logo support to email templates by extending the theme props to include ✅ Positive Aspects
|
|
Claude finished @fomalhautb's task —— View job Code Review Complete ✅I've thoroughly reviewed PR #852 that adds logo support to email templates. Here's my comprehensive feedback: Todo Checklist
🔍 Review SummaryThe implementation correctly adds logo support to email templates with proper type safety and consistent patterns. The changes are well-structured and follow existing codebase conventions. ✅ Strengths
|
Review by RecurseML🔍 Review performed on 933c60d..7bce171 ✨ No bugs found, your code is sparkling clean ✅ Files analyzed, no issues (5)• |
| `; | ||
|
|
||
| export const LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components'; | ||
| import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; |
There was a problem hiding this comment.
Duplicate import in LightEmailTheme string literal: remove the redundant import and combine required modules (include Img in a single import).
There was a problem hiding this comment.
Greptile Summary
This review covers only the changes made since the last review (commit 7bce171), not the entire PR.
This PR adds logo support to Stack Auth's email template system, enabling projects to display their branding in email communications. The implementation introduces a new themeProps object structure that replaces the simple unsubscribeLink parameter across the email rendering pipeline.
Key changes include:
-
Email Rendering Pipeline: Updated
renderEmailWithTemplate()inemail-rendering.tsxto accept a comprehensivethemePropsobject containingunsubscribeLink,projectDisplayName,logoUrl, andfullLogoUrl. This props object is properly spread to the EmailTheme component during rendering. -
API Route Updates: Both
render-email/route.tsxandsend-email/route.tsxnow constructthemePropsfrom the tenancy project configuration, extracting logo URLs and display names fromtenancy.project.logo_url,tenancy.project.full_logo_url, andtenancy.project.display_name. -
Email Theme Templates: The
LightEmailThemeandDarkEmailThemeinemails.tsnow include conditional logo rendering logic. They prioritize displaying a full logo (fullLogoUrl) if available, otherwise show a smaller logo alongside the project name (logoUrl+projectDisplayName). -
Type Safety: Extended the
ThemePropstype definition incode-editor.tsxto include the new optional logo properties, ensuring proper TypeScript intellisense for developers writing custom email templates.
The implementation maintains backward compatibility by making all logo properties optional and using null coalescing operators to handle missing values. The changes integrate well with the existing email rendering architecture, following established patterns for prop passing and theme customization.
Confidence score: 3/5
- This PR has implementation issues that should be addressed before merging, particularly duplicate imports in email themes
- Score reflects code quality concerns and potential runtime issues with malformed template strings
- Pay close attention to
packages/stack-shared/src/helpers/emails.tswhich contains duplicate import statements that will cause compilation errors
5 files reviewed, 1 comment
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
|
Claude finished @fomalhautb's task —— View job Pull Request ReviewReviewing the "Email templates with logo" changes for code quality, security, performance, and potential issues. Todo List
Review SummaryThis PR adds logo support to email templates by extending the theme props to include ✅ Positive Aspects
|
Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
|
Claude finished @fomalhautb's task —— View job Pull Request ReviewReviewing the "Email templates with logo" changes for code quality, security, performance, and potential issues. Todo List
Review SummaryThis PR adds logo support to email templates by extending the theme props to include ✅ Positive Aspects
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
packages/stack-shared/src/helpers/emails.ts (1)
45-46: Duplicate imports inside LightEmailTheme string cause syntax errorThere are two import lines for @react-email/components inside the template string. This will break bundling.
Apply this dedupe:
-export const LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link } from '@react-email/components'; -import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; +export const LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components';apps/backend/src/lib/email-rendering.tsx (1)
95-101: Fix: spreading possibly undefined themeProps throws at runtime; also set correct merge precedenceIf options.themeProps is undefined, JSON.stringify(options.themeProps) becomes undefined and {...undefined} throws. Also, PreviewProps should provide defaults that call-site props can override, not the other way around.
Apply:
- const themeProps = { - ...${JSON.stringify(options.themeProps)}, - ...${previewMode ? "EmailTheme.PreviewProps" : "{}"}, - } + const themeProps = { + ...${previewMode ? "EmailTheme.PreviewProps" : "{}"}, + ...${JSON.stringify(options.themeProps || {})}, + }This unblocks first-pass renders (which don’t pass themeProps) in send-email and prevents PreviewProps from clobbering explicit props.
🧹 Nitpick comments (5)
packages/stack-shared/src/helpers/emails.ts (5)
47-47: Prefer type-only import for ThemeProps to avoid runtime importUsing a value import for a type may keep an unnecessary import after transpilation depending on settings.
-import { ThemeProps } from "@stackframe/emails" +import type { ThemeProps } from "@stackframe/emails"
56-63: Add text fallback and better alt text for accessibilityIf neither logo is set, nothing renders; also alt text should be descriptive.
- {fullLogoUrl ? - <Img src={fullLogoUrl} alt="Full Logo" className="h-16" /> : - logoUrl ? - <div className="flex gap-2 items-center"> - <Img src={logoUrl} alt="Logo" className="h-8" /> - <h2>{projectDisplayName}</h2> - </div> - : null} + {fullLogoUrl ? ( + <Img src={fullLogoUrl} alt={(projectDisplayName ? projectDisplayName + " " : "") + "full logo"} className="h-16" /> + ) : logoUrl ? ( + <div className="flex gap-2 items-center"> + <Img src={logoUrl} alt={(projectDisplayName ? projectDisplayName + " " : "") + "logo"} className="h-8" /> + <h2>{projectDisplayName}</h2> + </div> + ) : ( + projectDisplayName ? <h2>{projectDisplayName}</h2> : null + )}
79-80: Consider enriching PreviewProps to showcase branding in previewOptional: add sample projectDisplayName/logoUrl to improve preview experience in the editor.
EmailTheme.PreviewProps = { - unsubscribeLink: "https://example.com", + unsubscribeLink: "https://example.com", + projectDisplayName: "Acme Inc.", + // logoUrl: "https://example.com/logo.png", + // fullLogoUrl: "https://example.com/full-logo.png", } satisfies Partial<ThemeProps>
84-85: Prefer type-only import for ThemeProps in Dark themeSame rationale as Light theme.
-const DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; -import { ThemeProps } from "@stackframe/emails" +const DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; +import type { ThemeProps } from "@stackframe/emails"
94-101: Dark theme: add text fallback and descriptive alt textMirror Light theme’s accessibility improvements.
- {fullLogoUrl ? - <Img src={fullLogoUrl} alt="Full Logo" className="h-16" /> : - logoUrl ? - <div className="flex gap-2 items-center"> - <Img src={logoUrl} alt="Logo" className="h-8" /> - <h2 className="text-white">{projectDisplayName}</h2> - </div> - : null} + {fullLogoUrl ? ( + <Img src={fullLogoUrl} alt={(projectDisplayName ? projectDisplayName + " " : "") + "full logo"} className="h-16" /> + ) : logoUrl ? ( + <div className="flex gap-2 items-center"> + <Img src={logoUrl} alt={(projectDisplayName ? projectDisplayName + " " : "") + "logo"} className="h-8" /> + <h2 className="text-white">{projectDisplayName}</h2> + </div> + ) : ( + projectDisplayName ? <h2 className="text-white">{projectDisplayName}</h2> : null + )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
apps/backend/src/app/api/latest/emails/render-email/route.tsx(1 hunks)apps/backend/src/app/api/latest/emails/send-email/route.tsx(2 hunks)apps/backend/src/lib/email-rendering.tsx(3 hunks)apps/dashboard/src/components/vibe-coding/code-editor.tsx(1 hunks)packages/stack-shared/src/helpers/emails.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
apps/backend/src/app/api/latest/**/*
📄 CodeRabbit Inference Engine (CLAUDE.md)
apps/backend/src/app/api/latest/**/*: Main API routes are located in /apps/backend/src/app/api/latest
The project uses a custom route handler system in the backend for consistent API responses
Files:
apps/backend/src/app/api/latest/emails/render-email/route.tsxapps/backend/src/app/api/latest/emails/send-email/route.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
- GitHub Check: docker
- GitHub Check: lint_and_build (latest)
- GitHub Check: setup-tests
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
- GitHub Check: all-good
- GitHub Check: Security Check
🔇 Additional comments (7)
apps/backend/src/app/api/latest/emails/render-email/route.tsx (2)
58-62: Branding props wiring looks goodPassing projectDisplayName, logoUrl, and fullLogoUrl via themeProps aligns with the new renderer signature and EmailTheme expectations. No issues here.
58-62: Sanity-check logo URLs are absolute and reachableIf tenancy.project.logo_url/full_logo_url can be relative or point to private hosts, many email clients will fail to display them. Ensure these values are absolute, public URLs, or normalize/validate before passing through.
Would you like a guard that validates these URLs and drops invalid ones to avoid broken images?
apps/dashboard/src/components/vibe-coding/code-editor.tsx (1)
96-99: Type augmentation matches backend themePropsAdding projectDisplayName, logoUrl, and fullLogoUrl to ThemeProps keeps Monaco typings in sync with the renderer and themes. Looks good.
apps/backend/src/app/api/latest/emails/send-email/route.tsx (3)
158-163: Good: themeProps passed with unsubscribe and brandingThis aligns with the updated renderEmailWithTemplate signature and new EmailTheme props.
107-115: First-pass render doesn’t pass themeProps; relies on renderer to handle undefinedThe first pass to compute notification category omits themeProps. With the current renderer implementation, object-spreading an undefined themeProps causes a runtime error. Ensure the renderer defaults themeProps to {} when absent (see email-rendering.tsx review).
Do you want me to open a follow-up to harden renderEmailWithTemplate and/or pass branding themeProps in the first pass too?
6-6:KnownErrorscorrectly re-exported at package rootThe
packages/stack-shared/src/index.tsfile includes:export { KnownError, KnownErrors } from "./known-errors";so importing
KnownErrorsfrom"@stackframe/stack-shared"will resolve successfully at runtime.apps/backend/src/lib/email-rendering.tsx (1)
52-57: Public API extension is coherentAdding themeProps with unsubscribeLink, projectDisplayName, logoUrl, and fullLogoUrl matches consuming routes and theme templates.
There was a problem hiding this comment.
Actionable comments posted: 1
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/backend/src/lib/email-rendering.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/backend/src/lib/email-rendering.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: docker
- GitHub Check: restart-dev-and-test
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: lint_and_build (latest)
- GitHub Check: Security Check
🔇 Additional comments (2)
apps/backend/src/lib/email-rendering.tsx (2)
98-101: Children fallback logic looks good.
EmailTheme.PreviewProps?.children ?? EmailTemplateWithPropscorrectly prioritizes preview children without breaking default rendering.
51-56: Verify or create a sharedEmailThemePropstype instack-shared.No matching exported
EmailThemeProps(or similar) was found inpackages/stack-shared. Confirm whether a shared props type exists (e.g. underhelpers/emails), or define and export one, before replacing the inline definition withPartial<EmailThemeProps>.
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)
apps/backend/src/lib/email-rendering.tsx (1)
135-135: Fix type assertion: subject and notificationCategory are optional.Current cast contradicts the function’s return type and can hide missing fields.
- return Result.ok(output.data.result as { html: string, text: string, subject: string, notificationCategory: string }); + return Result.ok(output.data.result as { html: string, text: string, subject?: string, notificationCategory?: string });apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts (1)
258-262: Update remaining legacyfull_logo_urlreferences in projects.tsx
- In apps/backend/src/lib/projects.tsx, replace in
logoFieldsarray:
•'full_logo_url'→'logo_full_url'- In the same file (around lines 115 and 149), update property access:
•logoUrls.full_logo_url→logoUrls.logo_full_url
♻️ Duplicate comments (1)
apps/backend/src/lib/email-rendering.tsx (1)
95-98: Guard spread of PreviewProps to prevent runtime TypeError in preview mode.Spreading
EmailTheme.PreviewPropsthrows if it'sundefined. Use nullish coalescing.- const themeProps = { - ...${JSON.stringify(options.themeProps || {})}, - ...${previewMode ? "EmailTheme.PreviewProps" : "{}"}, - } + const themeProps = { + ...${JSON.stringify(options.themeProps || {})}, + ...${previewMode ? "(EmailTheme.PreviewProps ?? {})" : "{}"}, + }
🧹 Nitpick comments (11)
apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts (1)
80-86: Consistent field rename in provisioning snapshot.Changed to logo_full_url as expected. Consider adding coverage for dark-mode logo fields when those are exposed by this endpoint.
packages/stack-shared/src/helpers/emails.ts (4)
45-50: Remove redundant ThemeProps import outside the template string.There’s an extra import { ThemeProps } from "@stackframe/emails" outside the TSX string that isn’t used.
Apply:
export const LightEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; import { ThemeProps } from "@stackframe/emails" -import { ThemeProps } from "@stackframe/emails"
56-64: Add header fallback when no logos are provided (show project name).Currently nothing renders if both logoFullUrl and logoUrl are absent. Show projectDisplayName alone to keep consistent branding.
Apply:
- {logoFullUrl ? - <Img src={logoFullUrl} alt="Full Logo" className="h-16" /> : - logoUrl ? - <div className="flex gap-2 items-center"> - <Img src={logoUrl} alt="Logo" className="h-8" /> - <h2>{projectDisplayName}</h2> - </div> - : null} + {logoFullUrl ? ( + <Img + src={logoFullUrl} + alt={projectDisplayName ? `${projectDisplayName} full logo` : "Project full logo"} + className="h-16" + height={64} + /> + ) : logoUrl ? ( + <div className="flex gap-2 items-center"> + <Img + src={logoUrl} + alt={projectDisplayName ? `${projectDisplayName} logo` : "Project logo"} + className="h-8" + height={32} + /> + {projectDisplayName ? <h2>{projectDisplayName}</h2> : null} + </div> + ) : ( + projectDisplayName ? <h2>{projectDisplayName}</h2> : null + )}
94-101: Mirror the no-logo fallback in Dark theme.Same rationale as Light theme; also keep contrast for heading.
Apply:
- {logoFullDarkModeUrl ? - <Img src={logoFullDarkModeUrl} alt="Full Logo" className="h-16" /> : - logoDarkModeUrl ? - <div className="flex gap-2 items-center"> - <Img src={logoDarkModeUrl} alt="Logo" className="h-8" /> - <h2 className="text-white">{projectDisplayName}</h2> - </div> - : null} + {logoFullDarkModeUrl ? ( + <Img + src={logoFullDarkModeUrl} + alt={projectDisplayName ? `${projectDisplayName} full logo` : "Project full logo"} + className="h-16" + height={64} + /> + ) : logoDarkModeUrl ? ( + <div className="flex gap-2 items-center"> + <Img + src={logoDarkModeUrl} + alt={projectDisplayName ? `${projectDisplayName} logo` : "Project logo"} + className="h-8" + height={32} + /> + {projectDisplayName ? <h2 className="text-white">{projectDisplayName}</h2> : null} + </div> + ) : ( + projectDisplayName ? <h2 className="text-white">{projectDisplayName}</h2> : null + )}
106-108: Minor a11y: clarify unsubscribe link text.“Click here to unsubscribe” is fine; consider a clearer label for screen readers.
Apply:
- <Link href={unsubscribeLink} className="text-gray-300">Click here{" "}</Link> - to unsubscribe from these emails + <Link href={unsubscribeLink} className="text-gray-300" aria-label="Unsubscribe from these emails"> + Unsubscribe + </Link>apps/backend/src/app/api/latest/emails/send-email/route.tsx (1)
159-166: ThemeProps wiring LGTMPassing branding + unsubscribe via
themePropsis consistent with the renderer change.For consistency, consider passing the same
themeProps(minusunsubscribeLink) in the first render pass used to detect the notification category to avoid divergence if templates start referencing branding in that branch.packages/template/src/lib/stack-app/projects/index.ts (1)
58-111: Avoid sending an all-undefined config payload
adminProjectUpdateOptionsToCrudalways includes aconfigobject, mostly withundefinedvalues whenoptions.configis not provided. Prefer omittingconfigentirely to reduce downstream branching and accidental overrides.export function adminProjectUpdateOptionsToCrud(options: AdminProjectUpdateOptions): ProjectsCrud["Admin"]["Update"] { - return { - display_name: options.displayName, - description: options.description, - is_production_mode: options.isProductionMode, - logo_url: options.logoUrl, - logo_full_url: options.logoFullUrl, - logo_dark_mode_url: options.logoDarkModeUrl, - logo_full_dark_mode_url: options.logoFullDarkModeUrl, - config: { + const config = options.config ? { domains: options.config?.domains?.map((d) => ({ domain: d.domain, handler_path: d.handlerPath })), oauth_providers: options.config?.oauthProviders?.map((p) => ({ id: p.id as any, type: p.type, ...(p.type === 'standard' && { client_id: p.clientId, client_secret: p.clientSecret, facebook_config_id: p.facebookConfigId, microsoft_tenant_id: p.microsoftTenantId, }), })), email_config: options.config?.emailConfig && ( options.config.emailConfig.type === 'shared' ? { type: 'shared', } : { type: 'standard', host: options.config.emailConfig.host, port: options.config.emailConfig.port, username: options.config.emailConfig.username, password: options.config.emailConfig.password, sender_name: options.config.emailConfig.senderName, sender_email: options.config.emailConfig.senderEmail, } ), email_theme: options.config?.emailTheme, sign_up_enabled: options.config?.signUpEnabled, credential_enabled: options.config?.credentialEnabled, magic_link_enabled: options.config?.magicLinkEnabled, passkey_enabled: options.config?.passkeyEnabled, allow_localhost: options.config?.allowLocalhost, create_team_on_sign_up: options.config?.createTeamOnSignUp, client_team_creation_enabled: options.config?.clientTeamCreationEnabled, client_user_deletion_enabled: options.config?.clientUserDeletionEnabled, team_creator_default_permissions: options.config?.teamCreatorDefaultPermissions, team_member_default_permissions: options.config?.teamMemberDefaultPermissions, user_default_permissions: options.config?.userDefaultPermissions, oauth_account_merge_strategy: options.config?.oauthAccountMergeStrategy, allow_user_api_keys: options.config?.allowUserApiKeys, allow_team_api_keys: options.config?.allowTeamApiKeys, - }, - }; + } + } : undefined; + + return { + display_name: options.displayName, + description: options.description, + is_production_mode: options.isProductionMode, + logo_url: options.logoUrl, + logo_full_url: options.logoFullUrl, + logo_dark_mode_url: options.logoDarkModeUrl, + logo_full_dark_mode_url: options.logoFullDarkModeUrl, + ...(config ? { config } : {}), + }; }apps/backend/src/lib/projects.tsx (2)
93-100: Upload logo variants in parallel and improve typingParallelize uploads and use a precise type for the four keys (or a Map, per guidelines) instead of
Record<string,...>.- const logoFields = ['logo_url', 'full_logo_url', 'logo_dark_mode_url', 'logo_full_dark_mode_url'] as const; - const logoUrls: Record<string, string | null | undefined> = {}; - - for (const field of logoFields) { - if (options.data[field] !== undefined) { - logoUrls[field] = await uploadAndGetUrl(options.data[field], "project-logos"); - } - } + const logoFields = ['logo_url', 'full_logo_url', 'logo_dark_mode_url', 'logo_full_dark_mode_url'] as const; + type LogoField = typeof logoFields[number]; + const entries = await Promise.all( + logoFields.map<Promise<[LogoField, string | null | undefined]>>(async (field) => { + return [field, options.data[field] !== undefined ? await uploadAndGetUrl(options.data[field], "project-logos") : undefined]; + }) + ); + const logoUrls = Object.fromEntries(entries) as Partial<Record<LogoField, string | null | undefined>>;
114-118: Map access to typed keysSwitching to the typed object (or a
Map) avoids stringly-typed key access.- logoUrl: logoUrls.logo_url, - logoFullUrl: logoUrls.full_logo_url, - logoDarkModeUrl: logoUrls.logo_dark_mode_url, - logoFullDarkModeUrl: logoUrls.logo_full_dark_mode_url, + logoUrl: logoUrls.logo_url, + logoFullUrl: logoUrls.full_logo_url, + logoDarkModeUrl: logoUrls.logo_dark_mode_url, + logoFullDarkModeUrl: logoUrls.logo_full_dark_mode_url,(kept identical keys; relies on the typed change above)
Also applies to: 148-152
apps/backend/src/lib/email-rendering.tsx (1)
100-102: Avoid passing 'children' via spread; rely on explicit children composition.If
PreviewProps.childrenexists, it’s redundantly passed in props and as explicit children. Strip it in the merge to reduce ambiguity.- const themeProps = { - ...${JSON.stringify(options.themeProps || {})}, - ...${previewMode ? "(EmailTheme.PreviewProps ?? {})" : "{}"}, - } + const __previewProps = ${previewMode ? "(EmailTheme.PreviewProps ?? {})" : "{}"}; + const { children: __ignoredChildren, ...__previewPropsSansChildren } = __previewProps; + const themeProps = { + ...${JSON.stringify(options.themeProps || {})}, + ...__previewPropsSansChildren, + }apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts (1)
230-267: Optionally assert the legacy field is absent to guard against regressions.Keeps the contract strict while retaining the inline snapshot.
expect(response2).toMatchInlineSnapshot(` NiceResponse { "status": 200, "body": { "config": { @@ "owner_team_id": "<stripped UUID>", }, "headers": Headers { <some fields may have been hidden> }, } `); + // Ensure deprecated field isn't accidentally returned + expect(response2.body).not.toHaveProperty("full_logo_url");
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
apps/backend/prisma/migrations/20250801204029_logo_url/migration.sql(1 hunks)apps/backend/prisma/schema.prisma(1 hunks)apps/backend/src/app/api/latest/emails/render-email/route.tsx(1 hunks)apps/backend/src/app/api/latest/emails/send-email/route.tsx(1 hunks)apps/backend/src/lib/email-rendering.tsx(2 hunks)apps/backend/src/lib/projects.tsx(5 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx(1 hunks)apps/dashboard/src/components/vibe-coding/code-editor.tsx(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts(2 hunks)apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts(3 hunks)apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts(2 hunks)apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts(11 hunks)apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts(1 hunks)apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts(16 hunks)apps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.ts(1 hunks)packages/stack-shared/src/helpers/emails.ts(3 hunks)packages/stack-shared/src/interface/crud/projects.ts(2 hunks)packages/stack-shared/src/schema-fields.ts(1 hunks)packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts(1 hunks)packages/template/src/lib/stack-app/projects/index.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
packages/stack-shared/src/interface/crud/projects.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.tsapps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.tspackages/stack-shared/src/schema-fields.tsapps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.tsapps/e2e/tests/backend/endpoints/api/v1/projects.test.tsapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/vibe-coding/code-editor.tsxapps/backend/src/app/api/latest/emails/send-email/route.tsxpackages/template/src/lib/stack-app/apps/implementations/admin-app-impl.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.tsapps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.tsapps/backend/src/app/api/latest/emails/render-email/route.tsxpackages/template/src/lib/stack-app/projects/index.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.tspackages/stack-shared/src/helpers/emails.tsapps/backend/src/lib/projects.tsxapps/backend/src/lib/email-rendering.tsxapps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts
**/*.test.{ts,tsx,js}
📄 CodeRabbit inference engine (AGENTS.md)
In tests, prefer .toMatchInlineSnapshot where possible; refer to snapshot-serializer.ts for snapshot formatting and handling of non-deterministic values
Files:
apps/e2e/tests/backend/endpoints/api/v1/integrations/custom/projects/provision.test.tsapps/e2e/tests/backend/endpoints/api/v1/team-permissions.test.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.tsapps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.tsapps/e2e/tests/backend/endpoints/api/v1/projects.test.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.tsapps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.tsapps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.tsapps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
For blocking alerts and errors in UI, do not use toast notifications; use alerts instead
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/vibe-coding/code-editor.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}
📄 CodeRabbit inference engine (AGENTS.md)
Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)
Files:
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsxapps/dashboard/src/components/vibe-coding/code-editor.tsx
apps/backend/src/app/api/latest/**
📄 CodeRabbit inference engine (AGENTS.md)
apps/backend/src/app/api/latest/**: Organize backend API routes by resource under /api/latest (e.g., auth at /api/latest/auth/, users at /api/latest/users/, teams at /api/latest/teams/, oauth providers at /api/latest/oauth-providers/)
Use the custom route handler system in the backend to ensure consistent API responses
Files:
apps/backend/src/app/api/latest/emails/send-email/route.tsxapps/backend/src/app/api/latest/emails/render-email/route.tsx
packages/template/**
📄 CodeRabbit inference engine (AGENTS.md)
When modifying the SDK copies, make changes in packages/template (source of truth)
Files:
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.tspackages/template/src/lib/stack-app/projects/index.ts
🧬 Code graph analysis (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)
apps/dashboard/src/components/logo-upload.tsx (1)
LogoUpload(20-135)
apps/backend/src/lib/projects.tsx (1)
apps/backend/src/s3.tsx (1)
uploadAndGetUrl(86-105)
🪛 GitHub Actions: Run setup tests
packages/template/src/lib/stack-app/projects/index.ts
[error] 65-65: TypeScript error TS2353: Object literal may only specify known properties; 'logo_dark_mode_url' does not exist on the Project type.
🪛 GitHub Actions: Runs E2E API Tests with external source of truth
packages/template/src/lib/stack-app/projects/index.ts
[error] 65-65: TS2353: Object literal may only specify known properties; 'logo_dark_mode_url' does not exist in the project type. (src/lib/stack-app/projects/index.ts:65,5).
🪛 GitHub Actions: Lint & build
packages/template/src/lib/stack-app/projects/index.ts
[error] 65-65: TS2353: Object literal may only specify known properties; 'logo_dark_mode_url' does not exist in the type for the project config.
🪛 GitHub Actions: Runs E2E API Tests
packages/template/src/lib/stack-app/projects/index.ts
[error] 65-65: TS2353: Object literal may only specify known properties, and 'logo_dark_mode_url' does not exist in the type for a stack-app project's configuration.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: all-good
- GitHub Check: restart-dev-and-test
- GitHub Check: docker
- GitHub Check: docker
- GitHub Check: Security Check
🔇 Additional comments (12)
apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/oauth-providers.test.ts (1)
124-133: Verify removal of allfull_logo_url/fullLogoUrlreferences
Rungrep -RIn --exclude-dir={node_modules,dist,build} -E '(full_logo_url|fullLogoUrl)' .to confirm no stale uses remain in code, docs or OpenAPI.
apps/dashboard/src/components/vibe-coding/code-editor.tsx (1)
96-101: Type surface matches new branding props.Extension of ThemeProps for editor typings looks good and aligned with renderer changes.
apps/backend/prisma/migrations/20250801204029_logo_url/migration.sql (1)
5-11: Migration properly adds the new logo fields.The migration correctly adds the four new logo-related columns to the Project table:
logoFullUrl(renamed from the originalfullLogoUrl)logoDarkModeUrl(new dark mode logo)logoFullDarkModeUrl(new dark mode full logo)All columns are properly defined as nullable TEXT fields, which is appropriate for optional logo URLs.
apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts (1)
95-95: Test snapshots consistently updated for API field rename.All inline snapshots have been properly updated to reflect the API field rename from
full_logo_urltologo_full_url. The changes are consistent across all test cases and maintain the same null values, indicating this is purely a field name update without any functional changes.Also applies to: 148-148, 204-204, 251-251, 312-312, 375-375, 421-421, 473-473, 514-514, 565-565, 619-619
apps/e2e/tests/backend/endpoints/api/v1/project-permissions.test.ts (1)
240-240: Test snapshot properly reflects API field rename.The snapshot correctly updates the field name from
full_logo_urltologo_full_url, maintaining consistency with the broader API changes across the codebase.packages/stack-shared/src/schema-fields.ts (1)
490-490: Schema constant renamed to match API consistency.The export name change from
projectFullLogoUrlSchematoprojectLogoFullUrlSchemaproperly aligns with the API field naming convention (logo_full_url). The schema validation logic and metadata remain unchanged, ensuring no functional impact while improving naming consistency.apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts (1)
42-42: Neon integration test snapshots consistently updated.All three test snapshots have been properly updated to reflect the API field rename from
full_logo_urltologo_full_url. The changes maintain consistency across the Neon integration endpoints.Also applies to: 97-97, 168-168
apps/backend/src/app/api/latest/emails/render-email/route.tsx (1)
58-64: Email theming properly extended with project branding data.The addition of the
themePropsparameter torenderEmailWithTemplatecorrectly provides email themes with access to:
- Project display name
- Light and dark mode logo variants (both regular and full logos)
The implementation properly handles nullable logo URLs by using the nullish coalescing operator (
??) to provideundefinedas fallback values. This ensures email themes can conditionally render logos based on availability.apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)
115-117: Project settings UI properly updated for new logo fields.The changes correctly:
- Update the Full Logo field to use
project.logoFullUrlinstead of the previousproject.fullLogoUrl- Add dark mode logo variants with proper field mappings:
- "Logo (Dark Mode)" →
project.logoDarkModeUrl- "Full Logo (Dark Mode)" →
project.logoFullDarkModeUrlThe implementation maintains consistency with the existing logo upload patterns and provides appropriate descriptions for each logo type. The
LogoUploadcomponent properly handles thetypeprop for different logo formats.Also applies to: 123-141
apps/e2e/tests/backend/endpoints/api/v1/projects.test.ts (1)
100-101: Snapshots updated to logo_full_url — OKThe rename in snapshots matches the public field change.
Also applies to: 150-151, 205-206, 266-267, 321-322, 419-420, 479-480, 525-526, 571-572, 631-632, 804-805, 1100-1101
apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/provision.test.ts (1)
3-3: Import cleanup + snapshot rename are correctRemoving unused
Projectimport and updating tologo_full_urllook good.Also applies to: 84-85
apps/backend/src/lib/email-rendering.tsx (1)
51-58: Remove the ThemeProps import suggestion—no such export exists in the shared package. The filepackages/stack-shared/src/helpers/emails.ts(and other email-related modules) do not export aThemePropsorEmailThemePropstype, so importingPartial<EmailThemeProps>isn’t valid.Likely an incorrect or invalid review comment.
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/backend/src/lib/projects.tsx (1)
65-66: Fix created_at_millis: avoid coercing Date to stringUsing row.createdAt + "Z" can yield Invalid Date. Use the Date directly or parse only when string.
Apply this diff:
- created_at_millis: new Date(row.createdAt + "Z").getTime(), + created_at_millis: row.createdAt instanceof Date + ? row.createdAt.getTime() + : new Date(row.createdAt).getTime(),
🧹 Nitpick comments (3)
apps/backend/src/lib/projects.tsx (3)
93-101: Use Map over Record and parallelize uploadsFollows repo guideline (prefer ES6 Map). Also removes sequential awaits.
Apply this diff:
const logoFields = ['logo_url', 'logo_full_url', 'logo_dark_mode_url', 'logo_full_dark_mode_url'] as const; - const logoUrls: Record<string, string | null | undefined> = {}; - - for (const field of logoFields) { - if (options.data[field] !== undefined) { - logoUrls[field] = await uploadAndGetUrl(options.data[field], "project-logos"); - } - } + const logoUrls = new Map<typeof logoFields[number], string | null>(); + await Promise.all( + logoFields.map(async (field) => { + const input = options.data[field]; + if (input !== undefined) { + const url = await uploadAndGetUrl(input, "project-logos"); + logoUrls.set(field, url ?? null); + } + }) + );(Optional) To avoid passing undefineds to Prisma, build a filtered object:
const logosData = filterUndefined({ logoUrl: logoUrls.get('logo_url') ?? undefined, logoFullUrl: logoUrls.get('logo_full_url') ?? undefined, logoDarkModeUrl: logoUrls.get('logo_dark_mode_url') ?? undefined, logoFullDarkModeUrl: logoUrls.get('logo_full_dark_mode_url') ?? undefined, });
114-118: Read from Map and avoid undefined writesUse Map#get; consider wrapping data with filterUndefined to skip untouched fields.
Apply this diff:
- logoUrl: logoUrls['logo_url'], - logoFullUrl: logoUrls['logo_full_url'], - logoDarkModeUrl: logoUrls['logo_dark_mode_url'], - logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'], + logoUrl: logoUrls.get('logo_url'), + logoFullUrl: logoUrls.get('logo_full_url'), + logoDarkModeUrl: logoUrls.get('logo_dark_mode_url'), + logoFullDarkModeUrl: logoUrls.get('logo_full_dark_mode_url'),(Optional) Wrap the whole data object:
data: filterUndefined({ // existing fields... logoUrl: logoUrls.get('logo_url'), logoFullUrl: logoUrls.get('logo_full_url'), logoDarkModeUrl: logoUrls.get('logo_dark_mode_url'), logoFullDarkModeUrl: logoUrls.get('logo_full_dark_mode_url'), })
148-152: Same Map usage for update pathMirror the create-path change; also consider filterUndefined to prevent accidental field nulling.
Apply this diff:
- logoUrl: logoUrls['logo_url'], - logoFullUrl: logoUrls['logo_full_url'], - logoDarkModeUrl: logoUrls['logo_dark_mode_url'], - logoFullDarkModeUrl: logoUrls['logo_full_dark_mode_url'], + logoUrl: logoUrls.get('logo_url'), + logoFullUrl: logoUrls.get('logo_full_url'), + logoDarkModeUrl: logoUrls.get('logo_dark_mode_url'), + logoFullDarkModeUrl: logoUrls.get('logo_full_dark_mode_url'),
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/backend/src/lib/projects.tsx(5 hunks)packages/stack-shared/src/interface/crud/projects.ts(2 hunks)packages/stack-shared/src/schema-fields.ts(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/stack-shared/src/schema-fields.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
apps/backend/src/lib/projects.tsxpackages/stack-shared/src/interface/crud/projects.ts
🧬 Code graph analysis (1)
apps/backend/src/lib/projects.tsx (1)
apps/backend/src/s3.tsx (1)
uploadAndGetUrl(86-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
- GitHub Check: all-good
- GitHub Check: docker
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: docker
- GitHub Check: restart-dev-and-test
- GitHub Check: Security Check
🔇 Additional comments (4)
apps/backend/src/lib/projects.tsx (2)
11-11: Import addition looks goodImporting RawQuery aligns with getProjectQuery’s return type.
62-64: Dark-mode logo fields exposed — LGTMField names match Prisma columns and shared types.
packages/stack-shared/src/interface/crud/projects.ts (2)
69-71: Admin Read: dark-mode logo fields — LGTMTypes align with new schemaFields; matches backend response shape.
121-123: Admin Update: dark-mode logo fields — LGTMSymmetric with read schema; nullable+optional choice is appropriate.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/stack-shared/src/helpers/emails.ts (1)
45-46: Duplicate import warning from earlier bots is resolved — keep as-is.Only one
@react-email/componentsimport remains; the second import is forThemePropsand is required.
🧹 Nitpick comments (4)
packages/stack-shared/src/helpers/emails.ts (4)
67-69: Use descriptive unsubscribe link text (accessibility + clarity).“Click here” is non-descriptive. Prefer “Unsubscribe”.
- <Link href={unsubscribeLink}>Click here{" "}</Link> - to unsubscribe from these emails + <Link href={unsubscribeLink}>Unsubscribe</Link>{" "} + from these emails- <Link href={unsubscribeLink} className="text-gray-300">Click here{" "}</Link> - to unsubscribe from these emails + <Link href={unsubscribeLink} className="text-gray-300">Unsubscribe</Link>{" "} + from these emailsAlso applies to: 119-121
83-83: Export DarkEmailTheme for parity and reuse.Keeps API symmetrical with
LightEmailTheme.-const DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components'; +export const DarkEmailTheme = `import { Html, Head, Tailwind, Body, Container, Link, Img } from '@react-email/components';
95-97: Flatten nested ternaries and make FullLogo’s alt descriptive.Improves readability and brand-specific alt text.
-function FullLogo(props: { logoFullUrl: string }) { - return <Img src={props.logoFullUrl} alt="Full Logo" className="h-16" />; +function FullLogo(props: { logoFullUrl: string; projectDisplayName?: string }) { + return ( + <Img + src={props.logoFullUrl} + alt={props.projectDisplayName ? `${props.projectDisplayName} full logo` : "Full logo"} + className="h-16" + /> + ); }- {logoFullDarkModeUrl ? - <FullLogo logoFullUrl={logoFullDarkModeUrl} /> : - logoDarkModeUrl ? - <Logo logoUrl={logoDarkModeUrl} projectDisplayName={projectDisplayName} /> : - logoFullUrl ? - <FullLogo logoFullUrl={logoFullUrl} /> : - logoUrl ? - <Logo logoUrl={logoUrl} projectDisplayName={projectDisplayName} /> : - null} + {(() => { + if (logoFullDarkModeUrl) return <FullLogo logoFullUrl={logoFullDarkModeUrl} projectDisplayName={projectDisplayName} />; + if (logoDarkModeUrl) return <Logo logoUrl={logoDarkModeUrl} projectDisplayName={projectDisplayName} />; + if (logoFullUrl) return <FullLogo logoFullUrl={logoFullUrl} projectDisplayName={projectDisplayName} />; + if (logoUrl) return <Logo logoUrl={logoUrl} projectDisplayName={projectDisplayName} />; + return null; + })()}Also applies to: 106-114
78-79: Enhance PreviewProps to showcase branding in the preview.Helps designers see header logos without wiring backend.
EmailTheme.PreviewProps = { - unsubscribeLink: "https://example.com", + unsubscribeLink: "https://example.com", + projectDisplayName: "Acme Inc.", + logoUrl: "https://example.com/logo.png", + logoFullUrl: "https://example.com/logo-full.png", } satisfies Partial<ThemeProps>EmailTheme.PreviewProps = { - unsubscribeLink: "https://example.com", + unsubscribeLink: "https://example.com", + projectDisplayName: "Acme Inc.", + logoDarkModeUrl: "https://example.com/logo-dark.png", + logoFullDarkModeUrl: "https://example.com/logo-full-dark.png", } satisfies Partial<ThemeProps>Also applies to: 130-131
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
packages/stack-shared/src/helpers/emails.ts(3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Prefer ES6 Map over Record when representing key–value collections
Files:
packages/stack-shared/src/helpers/emails.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Security Check
🔇 Additional comments (2)
packages/stack-shared/src/helpers/emails.ts (2)
48-48: Light theme props signature looks correct and aligned with new branding fields.No issues spotted with added props.
99-99: Dark theme props signature matches new fields; LGTM.
| {logoFullUrl ? | ||
| <Img src={logoFullUrl} alt="Full Logo" className="h-16" /> : | ||
| logoUrl ? | ||
| <div className="flex gap-2 items-center"> | ||
| <Img src={logoUrl} alt="Logo" className="h-8" /> | ||
| <h2>{projectDisplayName}</h2> | ||
| </div> | ||
| : null} |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Prevent “undefined” in header and improve alt text for accessibility.
If projectDisplayName is missing, <h2> will render “undefined”. Also, alt text should reflect the brand when available.
Apply:
- {logoFullUrl ?
- <Img src={logoFullUrl} alt="Full Logo" className="h-16" /> :
- logoUrl ?
- <div className="flex gap-2 items-center">
- <Img src={logoUrl} alt="Logo" className="h-8" />
- <h2>{projectDisplayName}</h2>
- </div>
- : null}
+ {logoFullUrl ? (
+ <Img
+ src={logoFullUrl}
+ alt={projectDisplayName ? `${projectDisplayName} full logo` : "Full logo"}
+ className="h-16"
+ />
+ ) : logoUrl ? (
+ <div className="flex gap-2 items-center">
+ <Img
+ src={logoUrl}
+ alt={projectDisplayName ? `${projectDisplayName} logo` : "Logo"}
+ className="h-8"
+ />
+ {projectDisplayName && <h2>{projectDisplayName}</h2>}
+ </div>
+ ) : null}📝 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.
| {logoFullUrl ? | |
| <Img src={logoFullUrl} alt="Full Logo" className="h-16" /> : | |
| logoUrl ? | |
| <div className="flex gap-2 items-center"> | |
| <Img src={logoUrl} alt="Logo" className="h-8" /> | |
| <h2>{projectDisplayName}</h2> | |
| </div> | |
| : null} | |
| {logoFullUrl ? ( | |
| <Img | |
| src={logoFullUrl} | |
| alt={projectDisplayName ? `${projectDisplayName} full logo` : "Full logo"} | |
| className="h-16" | |
| /> | |
| ) : logoUrl ? ( | |
| <div className="flex gap-2 items-center"> | |
| <Img | |
| src={logoUrl} | |
| alt={projectDisplayName ? `${projectDisplayName} logo` : "Logo"} | |
| className="h-8" | |
| /> | |
| {projectDisplayName && <h2>{projectDisplayName}</h2>} | |
| </div> | |
| ) : null} |
🤖 Prompt for AI Agents
In packages/stack-shared/src/helpers/emails.ts around lines 55 to 62, the JSX
can render the literal "undefined" when projectDisplayName is missing and the
image alt attributes aren't using the brand name; change the conditional
rendering so the <h2> is only rendered when projectDisplayName is truthy
(otherwise omit it or render a safe fallback like an empty string), and update
the Img alt props to use projectDisplayName when available (e.g.
`${projectDisplayName} logo`) or a generic alt like "Project logo" to improve
accessibility.
| function Logo(props: { logoUrl: string; projectDisplayName: string | undefined }) { | ||
| return ( | ||
| <div className="flex gap-2 items-center"> | ||
| <Img src={props.logoUrl} alt="Logo" className="h-8" /> | ||
| <h2 className="text-white">{props.projectDisplayName}</h2> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Avoid “undefined” in Dark theme header + better alt text.
Same issue as Light theme.
function Logo(props: { logoUrl: string; projectDisplayName: string | undefined }) {
return (
<div className="flex gap-2 items-center">
- <Img src={props.logoUrl} alt="Logo" className="h-8" />
- <h2 className="text-white">{props.projectDisplayName}</h2>
+ <Img
+ src={props.logoUrl}
+ alt={props.projectDisplayName ? `${props.projectDisplayName} logo` : "Logo"}
+ className="h-8"
+ />
+ {props.projectDisplayName && <h2 className="text-white">{props.projectDisplayName}</h2>}
</div>
);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function Logo(props: { logoUrl: string; projectDisplayName: string | undefined }) { | |
| return ( | |
| <div className="flex gap-2 items-center"> | |
| <Img src={props.logoUrl} alt="Logo" className="h-8" /> | |
| <h2 className="text-white">{props.projectDisplayName}</h2> | |
| </div> | |
| ); | |
| } | |
| function Logo(props: { logoUrl: string; projectDisplayName: string | undefined }) { | |
| return ( | |
| <div className="flex gap-2 items-center"> | |
| <Img | |
| src={props.logoUrl} | |
| alt={props.projectDisplayName ? `${props.projectDisplayName} logo` : "Logo"} | |
| className="h-8" | |
| /> | |
| {props.projectDisplayName && <h2 className="text-white">{props.projectDisplayName}</h2>} | |
| </div> | |
| ); | |
| } |
🤖 Prompt for AI Agents
In packages/stack-shared/src/helpers/emails.ts around lines 86 to 93, the dark
theme Logo component currently renders the literal "undefined" when
projectDisplayName is missing and uses a generic alt; change the render to avoid
showing "undefined" by using a safe fallback or conditional (e.g.,
projectDisplayName ?? '' or only render the h2 when truthy) and update the Img
alt to a descriptive value (e.g., `${projectDisplayName ?? 'Project'} logo` or
'Project logo') so the header never shows "undefined" and screen readers get
meaningful alt text.
apps/backend/prisma/migrations/20250801204029_logo_url/migration.sql
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (2)
apps/backend/src/lib/projects.tsx (1)
65-65: Address the Date conversion issue flagged in previous review.The
created_at_millisconversion issue remains unresolved. As noted in the previous review, appending"Z"to a Date object coerces it to a string and can yieldInvalid Date.apps/backend/src/lib/email-rendering.tsx (1)
97-100: Address the PreviewProps guard issue flagged in previous review.The unguarded spread of
EmailTheme.PreviewPropscan cause a runtime TypeError if PreviewProps is undefined. The previous review recommended using(EmailTheme.PreviewProps ?? {})to prevent this.
🧹 Nitpick comments (3)
apps/dashboard/src/components/vibe-coding/code-editor.tsx (3)
33-51: Reconsider disablingstrictFunctionTypesin the editor TS configTurning off
strictFunctionTypesglobally weakens function type checking (especially for callbacks) in the code editor, which can hide real mistakes users make in templates.If this was added only to work around specific library type incompatibilities, consider a narrower fix instead (e.g., tweaking the affected declaration(s) only) so the rest of the editor can keep full
strictbehavior. At minimum, a short comment here explaining the rationale would help future maintainers understand why this strictness flag is relaxed.
55-67: Transform hook for remote d.ts is useful; minor robustness/clarity tweaksThe
transformhook onfetchAndAddTypeDefinitionplustransformTailwindTypeFileis a nice, targeted way to patch the@react-email/tailwindtypes without forking them; the control flow and usage look sound.A couple of small suggestions:
- Consider adding a brief comment above
transformTailwindTypeFileexplaining which upstream signature you’re patching and why (e.g., mismatch withJSX.Element | null), so future updates to@react-email/tailwindknow what to check.- If
@react-email/tailwindever adds multiple overloaded signatures matching this pattern, the singlereplacemay miss some. If you expect more than one, making the regex global (/.../g) would keep the patch scalable.These are non-blocking, but would make this “stringly typed” patch easier to maintain.
Also applies to: 124-130, 134-139
83-107: ConfirmThemeProps['projectLogos']shape and optionality matches the real APIThe
projectLogosobject andProjectLogocomponent typing generally look aligned with the PR’s logo fields (four optional URLs on a logos object).Two details to double‑check against
@stackframe/emails’ actualThemeProps:
projectLogosis required onThemeProps(the properties inside are optional). If the real theme props allowprojectLogosto be absent entirely, this stub should likely beprojectLogos?: { ... }to avoid mismatch and confusing editor errors.ProjectLogo’sdataprop is typed asThemeProps['projectLogos'], which is therefore non-nullable. IfProjectLogois intended to handle the “no logos configured” case (e.g.,undefinedornull), it might be better typed asThemeProps['projectLogos'] | undefined(or similar).Adjust only if they diverge from the actual runtime typings; otherwise this looks good.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
apps/backend/prisma/schema.prisma(1 hunks)apps/backend/src/app/api/latest/emails/render-email/route.tsx(1 hunks)apps/backend/src/app/api/latest/emails/send-email/route.tsx(1 hunks)apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx(1 hunks)apps/backend/src/lib/email-rendering.tsx(3 hunks)apps/backend/src/lib/emails.tsx(1 hunks)apps/backend/src/lib/projects.tsx(4 hunks)apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx(1 hunks)apps/dashboard/src/components/vibe-coding/code-editor.tsx(4 hunks)apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts(3 hunks)apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/e2e/tests/backend/endpoints/api/v1/integrations/neon/projects/current.test.ts
- apps/backend/src/app/api/latest/emails/render-email/route.tsx
- apps/backend/src/app/api/latest/emails/send-email/route.tsx
- apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
- apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts
- apps/backend/prisma/schema.prisma
🧰 Additional context used
🧬 Code graph analysis (1)
apps/backend/src/lib/projects.tsx (1)
apps/backend/src/s3.tsx (1)
uploadAndGetUrl(86-105)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: Vercel Agent Review
- GitHub Check: all-good
- GitHub Check: build (22.x)
- GitHub Check: lint_and_build (latest)
- GitHub Check: docker
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: setup-tests
- GitHub Check: restart-dev-and-test
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
🔇 Additional comments (6)
apps/dashboard/src/components/vibe-coding/code-editor.tsx (1)
114-118: react-email package list change looks correctSwapping in
tailwindand droppingrenderin thereactEmailPackagesarray matches the new usage pattern described in the PR (theme composition vs. direct rendering). No issues here.apps/backend/src/lib/projects.tsx (1)
62-64: LGTM! Logo field additions are well-integrated.The expansion from two to four logo fields (adding
logo_dark_mode_urlandlogo_full_dark_mode_url) is correctly implemented across the read and write paths:
- Read path correctly maps from Prisma camelCase to API snake_case
- Write path properly gathers URLs via
uploadAndGetUrlfor all four fields- Prisma create/update operations correctly write all four fields using bracket notation
Also applies to: 93-117, 148-151
apps/backend/src/lib/email-rendering.tsx (2)
52-60: LGTM! Theme props restructuring is well-designed.The update to accept a nested
themePropsobject withprojectLogosprovides a cleaner API surface for passing branding data to email themes. The spread operator usage on line 102 correctly propagates all theme props to the EmailTheme component.Also applies to: 102-103
290-354: LGTM! Logo component implementations are correct.The three-component hierarchy works well:
Logorenders compact logo with project nameFullLogorenders full-width logo imageProjectLogocorrectly selects the appropriate variant based on mode (light/dark) and available logo URLs, with sensible fallback logicapps/backend/src/lib/emails.tsx (1)
401-408: LGTM! Project logos correctly wired into email rendering.The
themePropsaddition properly sources all four logo variants fromtenancy.projectand converts null to undefined for optional fields, matching the updatedrenderEmailWithTemplatesignature.apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx (1)
45-52: LGTM! Theme props consistently integrated in preview mode.The
themePropsaddition follows the same pattern as other call sites, correctly providing all four logo variants for email template preview rendering.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/backend/src/lib/email-rendering.tsx (1)
97-100: Guard the spread ofEmailTheme.PreviewPropsto prevent runtime TypeError.When
previewModeis true andEmailTheme.PreviewPropsis undefined, the generated code will attempt to spread undefined, causing a runtime error.Apply this diff to add nullish coalescing:
const themeProps = { ...${JSON.stringify(options.themeProps || {})}, - ...${previewMode ? "EmailTheme.PreviewProps" : "{}"}, + ...${previewMode ? "(EmailTheme.PreviewProps ?? {})" : "{}"}, }
🧹 Nitpick comments (1)
apps/backend/src/lib/email-rendering.tsx (1)
335-365: Consider adding validation for required logo URLs.The ProjectLogo component returns components that depend on specific URL props (logoUrl, logoFullUrl, etc.) but doesn't validate these URLs exist before passing them. While the Img component may handle undefined src gracefully, explicit checks would make the logic clearer.
Example validation before returning Logo:
if (mode === "dark" && logoDarkModeUrl) { return React.createElement(Logo, { logoUrl: logoDarkModeUrl, projectDisplayName, }); }Could be:
if (mode === "dark" && logoDarkModeUrl && projectDisplayName) { return React.createElement(Logo, { logoUrl: logoDarkModeUrl, projectDisplayName, }); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/backend/src/app/api/latest/emails/send-email/route.tsx(1 hunks)apps/backend/src/lib/email-rendering.tsx(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/backend/src/app/api/latest/emails/send-email/route.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: Vercel Agent Review
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: all-good
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
- GitHub Check: setup-tests
- GitHub Check: lint_and_build (latest)
- GitHub Check: build (22.x)
- GitHub Check: restart-dev-and-test
🔇 Additional comments (5)
apps/backend/src/lib/email-rendering.tsx (5)
52-60: LGTM! ThemeProps structure is well-defined.The themeProps parameter properly encapsulates unsubscribeLink and projectLogos with appropriate optional fields for light/dark mode logo variants.
102-104: LGTM! ThemeProps correctly passed to EmailTheme.The spread operator properly passes themeProps to EmailTheme, and the children fallback logic is sound.
159-166: LGTM! Batch themeProps structure is appropriate.The themeProps structure for batch rendering appropriately includes projectLogos, while unsubscribeLink is handled per-input at the sibling level.
197-200: LGTM! Safe handling of optional themeProps in batch mode.The code properly uses optional chaining and nullish coalescing to safely extract projectLogos, preventing runtime errors when themeProps is undefined.
202-204: LGTM! ThemeProps correctly applied in batch rendering.The spread operator properly passes themeProps to EmailTheme in batch mode, consistent with the single rendering approach.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
apps/backend/src/lib/email-rendering.tsx (1)
97-100: Guard against undefined EmailTheme.PreviewProps to prevent runtime error.The spread
...EmailTheme.PreviewPropson line 99 will throw a TypeError ifPreviewPropsis undefined. Although this was marked as addressed in a previous review, the code still lacks the guard.Apply this diff to safely handle undefined PreviewProps:
const themeProps = { ...${JSON.stringify(options.themeProps || {})}, - ...${previewMode ? "EmailTheme.PreviewProps" : "{}"}, + ...${previewMode ? "(EmailTheme.PreviewProps ?? {})" : "{}"}, }
🧹 Nitpick comments (2)
apps/backend/src/lib/email-rendering.tsx (2)
197-200: Simplify themeProps construction by removing unnecessary spread.Line 198 spreads an object literal containing a single key, which can be written more directly.
Apply this diff for cleaner code:
const themeProps = { - ...{ projectLogos: input.themeProps?.projectLogos ?? {} }, + projectLogos: input.themeProps?.projectLogos ?? {}, unsubscribeLink: input.unsubscribeLink, }
311-321: Remove unused gap-2 className from Logo component.The Logo component applies
gap-2to a flex container, but it only has a single child image. This className has no effect and may be leftover from a previous version that rendered text alongside the logo.Apply this diff to clean up the unused style:
export function Logo(props) { return React.createElement( "div", - { className: "flex gap-2 items-center" }, + { className: "flex items-center" }, React.createElement(Img, { logoUrl: props.logoUrl, alt: "Logo", className: "h-8", }), ); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
apps/backend/src/lib/email-rendering.tsx(5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (1)
packages/stack-shared/src/utils/strings.tsx (1)
deindent(235-238)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: build (22.x)
- GitHub Check: setup-tests
- GitHub Check: restart-dev-and-test
- GitHub Check: restart-dev-and-test-with-custom-base-port
- GitHub Check: lint_and_build (latest)
- GitHub Check: all-good
- GitHub Check: Vercel Agent Review
- GitHub Check: check_prisma_migrations (22.x)
- GitHub Check: docker
🔇 Additional comments (1)
apps/backend/src/lib/email-rendering.tsx (1)
331-358: Verify the intended fallback behavior for dark mode logos.The
ProjectLogocomponent returnsnullwhen in dark mode but no dark-mode-specific logo URLs are provided, even if light mode logos exist. This could be intentional to avoid contrast issues (e.g., a light logo designed for dark backgrounds might not work on a light background), but it could also result in no logo being displayed when one is available.Confirm whether the intended behavior is:
- Show nothing in dark mode if no dark logos exist (current behavior), or
- Fall back to light mode logos in dark mode
If fallback is desired, you can add this after line 347:
// Add fallback to light mode logos in dark mode if (mode === "dark" && logoFullUrl) { return React.createElement(FullLogo, { logoFullUrl }); } if (mode === "dark" && logoUrl) { return React.createElement(Logo, { logoUrl }); }
https://www.loom.com/share/7628a0b5f14e4367bcde93e4817a50e8
Important
Enhance email templates with project branding and dark-mode support, update schemas and API responses for new logo fields.
email-rendering.tsxandemails.ts.email-rendering.tsx.full_logotologo_fulland add dark-mode logo fields inprojects.tsandschema-fields.ts.projects.tsxandpage-client.tsxto handle new logo fields.emails.ts.logoFullUrl,logoDarkModeUrl, andlogoFullDarkModeUrlcolumns toProjecttable inmigration.sql.This description was created by
for 0fbb79d. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Refactor
Tests
Chores
Note
Adds light/dark logo variants, renames full-logo fields, and passes project logos into email themes; updates DB, schemas, API, UI, and tests.
Project.fullLogoUrltologoFullUrl; addlogoDarkModeUrlandlogoFullDarkModeUrl.themePropswithprojectLogosand spreads intoEmailTheme(single/batched).@stackframe/emailscomponents (Logo,FullLogo,ProjectLogo) with light/dark fallbacks.logo_full_urland add dark-mode fields.<ProjectLogo>; improve dark-theme unsubscribe link contrast.logoFullUrl,logoDarkModeUrl,logoFullDarkModeUrluploads.ThemeProps.projectLogos, relax TS option, and Tailwind DTS fix.schema-fieldsand CRUD read/update models to new/extra logo fields; propagate through template app types.Written by Cursor Bugbot for commit 4d97561. This will update automatically on new commits. Configure here.