Skip to content

Conversation

@lukevella
Copy link
Owner

@lukevella lukevella commented Dec 27, 2025

Summary by CodeRabbit

  • New Features

    • Theme controls (system/light/dark) added across UI; environment-driven branding (primary color, app name, logo) now applied.
  • Bug Fixes

    • Improved hover colors, icon contrast, and dark-mode variants for better readability and consistency.
  • Removals

    • Legacy UI elements removed (cookie consent popover, upgrade promo, some avatar/logo helpers).
  • Style

    • Wide theming refresh across components and emails; email buttons/links adopt configured primary color.
  • Localization

    • Feedback-related translation keys removed; new theme-related keys added.

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

@vercel
Copy link

vercel bot commented Dec 27, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
app Ready Ready Preview, Comment Dec 27, 2025 0:35am
1 Skipped Deployment
Project Deployment Review Updated (UTC)
landing Skipped Skipped Dec 27, 2025 0:35am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 27, 2025

Walkthrough

Adds a ThemeProvider and env-driven primary color, extends email/context styling, introduces a Hono private-polls API with API-key auth and time-slot generation (plus DB migration and scripts), removes several UI/avatar components, and performs a large design-token and locale-key migration.

Changes

Cohort / File(s) Summary
Theming & env
packages/ui/src/theme-provider.tsx, apps/web/src/env.ts, apps/web/src/app/[locale]/layout.tsx
Add ThemeProvider export; expose APP_NAME/PRIMARY_COLOR/LOGO_* env vars; apply PRIMARY_COLOR CSS variables and computed foreground on html.
Email system & templates
packages/emails/src/*, apps/web/src/utils/emails.ts, packages/emails/src/types.ts, packages/emails/src/send-email.tsx
Add appName/primaryColor to email context; extend Button/Link to accept color; pass ctx.primaryColor to templates; use env.LOGO_ICON_URL and appName in email layout.
Private API: polls
apps/web/src/app/api/private/[...route]/route.ts, apps/web/src/app/api/private/schemas.ts, apps/web/src/app/api/private/utils/*
New Hono app with GET/POST exports, OpenAPI/docs endpoints, Zod/OpenAPI schemas, bearer API-key auth, time-slot parsing/generation/dedupe, createPoll util persisting via Prisma, and tests.
Database & tooling
packages/database/prisma/migrations/.../migration.sql, packages/database/prisma/models/user.prisma, packages/database/prisma/scripts/create-api-key.ts, packages/database/package.json
Add ApiKey Prisma model and migration; add create-api-key script and npm script.
Theme preferences & UI integration
apps/web/src/app/[locale]/(space)/settings/preferences/components/theme-preference.tsx, apps/web/src/app/[locale]/(space)/settings/preferences/preferences-page.tsx, apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
Reintroduce ThemePreference component; add Theme section to preferences and Theme submenu in user nav wired to useTheme.
Avatar & gradient cleanup
apps/web/src/components/user.tsx (removed), apps/web/src/utils/color-hash.ts (removed), packages/ui/src/avatar.tsx, apps/web/src/components/random-gradient-bar.tsx
Remove UserAvatar and color-hash helpers; simplify AvatarFallback and RandomGradientBar (drop seed/gradient generation).
UI removals / public API removals
apps/web/src/components/cookie-consent.tsx, apps/web/src/app/[locale]/(space)/(dashboard)/components/upgrade-to-pro-alert.tsx, apps/web/src/app/[locale]/(space)/settings/components/settings-layout.tsx, apps/web/src/app/components/logo-link.tsx
Remove CookieConsentPopover, UpgradeToProAlert, settings layout helpers, and LogoLink; exported helpers removed.
Design system & token migration
packages/tailwind-config/shared-styles.css, packages/ui/src/{button,alert,card,badge,dialog,dropdown-menu,icon,...}
Large migration to semantic tokens (muted, card, primary-foreground), dark-mode scaffolding, and many component variant class updates; few small prop/ref typing adjustments.
Widespread styling tweaks
apps/web/src/components/**, apps/landing/src/app/[locale]/(main)/footer.tsx, apps/landing/src/app/[locale]/globals.css
Replace many gray/primary-600 tokens with semantic tokens (muted, foreground, primary), adjust hover states (e.g., hover:text-primary-600 → hover:text-primary), and update backgrounds across many components.
Locales
apps/web/public/locales/*/app.json, apps/web/public/locales/en/app.json
Remove feedback-related keys across many locales; add theme-related keys to en/app.json and small localization edits.
Misc / layout & assets
apps/web/src/components/logo.tsx, apps/web/src/app/[locale]/(auth)/layout.tsx, apps/web/src/components/poll/**, apps/web/package.json
Replace Next.js Image with img for logo using env.LOGO_URL; remove DotPattern from auth layout; poll/mobile/layout styling tweaks; add Hono-related dependencies to package.json.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HonoApp as Hono App (route.ts)
    participant Auth as apiKeyAuth
    participant TimeSlots as time-slots util
    participant PollUtil as createPoll util
    participant DB as Prisma
    participant URL as shortUrl/absoluteUrl

    Client->>HonoApp: POST /api/private/polls (Bearer token + payload)
    HonoApp->>Auth: verify bearer token
    Auth->>DB: SELECT api_keys WHERE prefix = ?
    DB-->>Auth: api_key row
    Auth->>Auth: validate secret vs hashed_key, check revoked/expired
    Auth-->>HonoApp: authenticated { userId, apiKeyId }
    HonoApp->>TimeSlots: parse/generate slots (dates or slots + timezone)
    TimeSlots-->>HonoApp: deduped TimeSlot[] (<=100)
    HonoApp->>PollUtil: createPoll({ userId, title, options })
    PollUtil->>DB: INSERT poll, createMany options, watchers
    DB-->>PollUtil: created poll id
    PollUtil->>URL: generate adminUrl and inviteUrl
    URL-->>PollUtil: urls
    PollUtil-->>HonoApp: { id, adminUrl, inviteUrl }
    HonoApp-->>Client: 201 Created { data: { id, adminUrl, inviteUrl } }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped through tokens, swapped gray for new light,
I carried primary color into the night.
I shelved old gradients, trimmed avatars small,
I opened a private poll door — keys for one and all.
Hop — the theme now hums; the UI stands tall. 🎨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title '✨ Dark mode' is vague and generic, using a non-descriptive emoji and a broad term that doesn't convey the substantial implementation details of this changeset. Replace the title with a more specific, descriptive summary of the main changes, such as 'Add dark mode theme support with CSS variables and theme provider integration' or 'Implement dark theme with environment-driven color customization'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dark-theme

📜 Recent review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dad73c9 and ed6e204.

📒 Files selected for processing (2)
  • apps/web/src/components/stacked-list.tsx
  • apps/web/src/features/billing/components/pay-wall-dialog.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/src/features/billing/components/pay-wall-dialog.tsx
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory

**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, run pnpm i18n:scan instead of manually adding keys

Files:

  • apps/web/src/components/stacked-list.tsx
**/*.{tsx,css,config.ts}

📄 CodeRabbit inference engine (.cursorrules)

Use tailwindcss for styling

Files:

  • apps/web/src/components/stacked-list.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use react-hook-form for form handling
All text in the UI should be translated using either the Trans component or the useTranslation hook
Prefer composable components in the style of shadcn UI over large monolithic components
DropdownMenuItem is a flex container with a preset gap so there is no need to add margins to the children
The size and colour of an icon should be set by wrapping it with the component from @rallly/ui/icon which will give it the correct colour and size
Keep the props of a component as minimal as possible. Pass only the bare minimum amount of information needed to it
All text in the UI should be translatable
Use the component in client components from @/components/trans with the defaults prop to provide the default text
Always use a composable patterns when building components
Use cn() from @rallly/ui to compose classes
Add the "use client" directive to the top of any .tsx file that requires client-side javascript

Files:

  • apps/web/src/components/stacked-list.tsx
**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase

Files:

  • apps/web/src/components/stacked-list.tsx
**/*

📄 CodeRabbit inference engine (.cursorrules)

Always use kebab-case for file names

Files:

  • apps/web/src/components/stacked-list.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for code formatting with indent of 2 spaces and double quotes

Files:

  • apps/web/src/components/stacked-list.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g., function Component({ prop }: { prop: string }) instead of defining a separate interface)
Always use the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling

Files:

  • apps/web/src/components/stacked-list.tsx
apps/web/src/components/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place shared components in apps/web/src/components/

Files:

  • apps/web/src/components/stacked-list.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)

apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept a username (used for login, normalized) and an optional displayUsername (raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.

Files:

  • apps/web/src/components/stacked-list.tsx
🧠 Learnings (1)
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.tsx : Use `cn()` from rallly/ui to compose classes

Applied to files:

  • apps/web/src/components/stacked-list.tsx
🔇 Additional comments (2)
apps/web/src/components/stacked-list.tsx (2)

11-16: LGTM! Design token migration applied correctly.

The addition of border border-card-border bg-card classes properly implements the card styling with design tokens for dark mode support. The cn() composition correctly preserves the className prop for extensibility.


32-32: LGTM! Hover state updated to use design token.

The change from hover:bg-gray-50 to hover:bg-card-accent correctly applies the design system token for hover states, ensuring proper dark mode support.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Caution

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

⚠️ Outside diff range comments (5)
apps/web/src/components/poll/manage-poll/finalize-poll-dialog.tsx (1)

1-1: Add the "use client" directive.

This file uses client-side hooks (useForm, React.useMemo, usePoll, useParticipants, useDayjs) and trpc mutations, which require client-side JavaScript execution. Without the "use client" directive, Next.js will treat this as a Server Component and fail at runtime.

🔎 Proposed fix
+"use client";
+
 import { zodResolver } from "@hookform/resolvers/zod";
 import { cn } from "@rallly/ui";
 import { Button } from "@rallly/ui/button";

Based on coding guidelines:

Add the "use client" directive to the top of any .tsx file that requires client-side javascript

apps/web/src/components/poll/manage-poll.tsx (1)

251-259: Good refactor to use variant prop, but fix icon styling.

The change from className="text-destructive" to variant="destructive" is a good improvement that uses the component's proper API. However, the TrashIcon on Line 257 applies styling directly via className, which violates the coding guidelines.

As per coding guidelines: "The size and colour of an icon should be set by wrapping it with the component from @rallly/ui/icon which will give it the correct colour and size."

🔎 Proposed fix for icon styling
          <DropdownMenuItem
            variant="destructive"
            onClick={() => {
              setShowDeletePollDialog(true);
            }}
          >
-            <TrashIcon className="size-4 opacity-75" />
+            <Icon>
+              <TrashIcon />
+            </Icon>
            <Trans i18nKey="delete" />
          </DropdownMenuItem>
apps/web/src/components/participant.tsx (1)

1-2: Missing "use client" directive.

This component uses client-side hooks (React.useState and React.useRef) but lacks the required "use client" directive at the top of the file. Without this directive, Next.js will attempt to render this as a server component, causing runtime errors.

As per coding guidelines: "Add the 'use client' directive to the top of any .tsx file that requires client-side javascript."

🔎 Proposed fix
+"use client";
+
 import { cn } from "@rallly/ui";
 import React from "react";
apps/web/src/app/[locale]/(space)/settings/security/page.tsx (1)

57-67: Fix invalid Alert variant: "tip" is not a defined variant.

The change from variant="tip" to variant="success" is critical—variant="tip" does not exist in the Alert component definition, which only supports: default, primary, info, warning, success, and note.

Note that apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx also uses the invalid variant="tip" and requires the same fix. Additionally, apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx uses the non-existent variant="destructive" and also needs correction.

packages/ui/src/avatar.tsx (1)

1-6: Add the "use client" directive and remove the unused seed prop.

The avatar.tsx component is missing the "use client" directive at the top, which is required by the coding guidelines and present in all other shadcn-ui components in this package. Radix UI's Avatar component requires client-side JavaScript for proper fallback behavior.

Additionally, the seed prop is defined in the AvatarFallback type but never destructured or used in the component implementation. It spreads via ...props to Radix UI's Fallback element, which doesn't recognize it as a standard prop. Remove the seed?: string; from the type definition and update consumers (optimized-avatar-image.tsx, space-icon.tsx) to stop passing this unused prop.

🧹 Nitpick comments (9)
apps/web/src/app/[locale]/(space)/mobile-navigation.tsx (1)

16-22: Consider using the standard <Icon> component wrapper.

Based on the coding guidelines, icon sizing and coloring should be handled by wrapping with the <Icon> component from @rallly/ui/icon rather than a custom Slot wrapper. This would improve consistency across the codebase.

💡 Example refactor using the Icon component
+import { Icon } from "@rallly/ui/icon";

-function MobileNavigationIcon({ children }: { children: React.ReactNode }) {
-  return (
-    <Slot className="size-5 text-muted-foreground group-hover:text-foreground group-focus:text-primary group-[.is-active]:text-primary">
-      {children}
-    </Slot>
-  );
-}
+function MobileNavigationIcon({ children }: { children: React.ReactNode }) {
+  return (
+    <Icon className="group-hover:text-foreground group-focus:text-primary group-[.is-active]:text-primary">
+      {children}
+    </Icon>
+  );
+}

Note: Verify that the <Icon> component supports the group-based state classes, or adjust the implementation accordingly.

Based on learnings: Icons should be wrapped with the <Icon> component from @rallly/ui/icon.

apps/web/src/components/vote-summary-progress-bar.tsx (1)

60-60: Consider removing ineffective opacity modifiers.

The opacity-75 hover:opacity-100 classes have no visual effect on a bg-transparent background, since the element is already fully transparent. These modifiers only affect the visibility of opaque content.

If the intent is to keep visual consistency with the other segments, consider using a semi-transparent color instead, such as bg-gray-500/20, which would respond to the opacity modifiers. Otherwise, the opacity classes can be safely removed.

🔎 Option 1: Remove ineffective opacity classes
          <div
-            className="h-full bg-transparent opacity-75 hover:opacity-100"
+            className="h-full bg-transparent"
            style={{
              width: `${(props.no.length / props.total) * 100}%`,
            }}
          />
🔎 Option 2: Use a semi-transparent color that responds to opacity changes
          <div
-            className="h-full bg-transparent opacity-75 hover:opacity-100"
+            className="h-full bg-gray-500/20 opacity-75 hover:opacity-100"
            style={{
              width: `${(props.no.length / props.total) * 100}%`,
            }}
          />
apps/web/src/components/forms/poll-options-form/month-calendar/time-picker.tsx (1)

31-49: Optional refactor: Remove variable shadowing in cursor declaration.

The cursor variable is declared on line 31 and immediately redeclared on line 38 within the if (after) block, which shadows the outer declaration. The initial declaration is only used in the else block, making the code harder to follow.

🔎 Suggested refactor to eliminate shadowing
   const getOptions = React.useCallback(() => {
     if (!open) {
       return [dayjs(value).toISOString()];
     }
-    let cursor = after
-      ? dayjs(after).add(15, "minutes")
-      : dayjs(value).startOf("day");
-
     const res: string[] = [];
 
     if (after) {
       let cursor = dayjs(after).add(15, "minutes");
       while (cursor.diff(after, "hours") < 24) {
         res.push(cursor.toISOString());
         cursor = cursor.add(15, "minutes");
       }
     } else {
-      cursor = dayjs(value).startOf("day");
+      let cursor = dayjs(value).startOf("day");
       while (cursor.isSame(value, "day")) {
         res.push(cursor.toISOString());
         cursor = cursor.add(15, "minutes");
       }
     }
     return res;
   }, [after, open, value]);
apps/web/src/features/password/components/password-strength-meter.tsx (1)

57-63: Consider adding dark mode variants for label colors.

The label colors use -500 and -600 variants without explicit dark mode styles. While these may work reasonably in both modes, adding explicit dark mode variants would ensure optimal contrast and consistency with the rest of the dark mode implementation.

🔎 Suggested dark mode variants
           className={cn("font-medium", {
-            "text-gray-500": quality === "veryWeak",
+            "text-gray-500 dark:text-gray-400": quality === "veryWeak",
-            "text-red-600": quality === "weak",
+            "text-red-600 dark:text-red-400": quality === "weak",
-            "text-amber-600": quality === "fair",
+            "text-amber-600 dark:text-amber-400": quality === "fair",
-            "text-green-600": quality === "good",
+            "text-green-600 dark:text-green-400": quality === "good",
-            "text-teal-600": quality === "strong",
+            "text-teal-600 dark:text-teal-400": quality === "strong",
           })}
apps/web/src/components/participant.tsx (1)

16-32: Consider addressing the accessibility issue.

The biome-ignore comment indicates there's a pending accessibility issue. The component uses mouse events on a non-interactive element without keyboard support, which prevents keyboard-only users from accessing the full truncated text.

Consider wrapping this in a button element or adding keyboard event handlers (e.g., onFocus/onBlur) to make the tooltip accessible via keyboard navigation.

Would you like me to help generate a keyboard-accessible implementation?

packages/ui/src/switch.tsx (1)

16-16: LGTM! Dark mode support added.

The Switch component now properly supports dark mode with:

  • Lighter green shade (green-400) for checked state in dark mode for better contrast
  • Semantic bg-muted token for unchecked state
  • Dark thumb styling (dark:bg-gray-700)

The implementation correctly adapts the component's appearance for dark mode.

💡 Optional: Consider using semantic tokens for thumb background

The thumb currently uses bg-white (light mode) and dark:bg-gray-700 (dark mode). For greater consistency with the theme system, consider using semantic tokens like bg-background or bg-card that would automatically adapt to the theme.

-        "pointer-events-none flex h-full w-4 items-center justify-center rounded-full bg-white shadow-lg ring-0 transition-transform duration-100 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:bg-gray-700",
+        "pointer-events-none flex h-full w-4 items-center justify-center rounded-full bg-background shadow-lg ring-0 transition-transform duration-100 data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",

This would make the component fully theme-aware, though the current implementation is perfectly functional.

Also applies to: 24-24

apps/web/src/utils/color.ts (1)

1-10: Consider supporting 3-digit hex codes.

The regex only matches 6-digit hex colors (#RRGGBB). If PRIMARY_COLOR might be specified as a 3-digit shorthand (e.g., #fff), parsing will fail and default to white foreground.

🔎 Proposed fix to support 3-digit hex
 function hexToRgb(hex: string): { r: number; g: number; b: number } | null {
-  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
-  return result
-    ? {
-        r: Number.parseInt(result[1], 16),
-        g: Number.parseInt(result[2], 16),
-        b: Number.parseInt(result[3], 16),
-      }
-    : null;
+  // Expand 3-digit hex to 6-digit
+  const expandedHex = hex.replace(
+    /^#?([a-f\d])([a-f\d])([a-f\d])$/i,
+    (_, r, g, b) => `${r}${r}${g}${g}${b}${b}`,
+  );
+  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(expandedHex);
+  return result
+    ? {
+        r: Number.parseInt(result[1], 16),
+        g: Number.parseInt(result[2], 16),
+        b: Number.parseInt(result[3], 16),
+      }
+    : null;
 }
apps/web/src/features/billing/components/pay-wall-dialog.tsx (1)

189-189: Consider using semantic tokens instead of hard-coded gray values.

The styling uses hard-coded bg-gray-100 dark:bg-gray-700 and bg-gray-100 dark:bg-gray-900 rather than semantic tokens like bg-muted or bg-card. This is inconsistent with the token-based theming approach used elsewhere in this PR.

🔎 Proposed fix using semantic tokens
-                  <div className="flex items-start justify-between gap-4 rounded-lg bg-gray-100 p-4 dark:bg-gray-700">
+                  <div className="flex items-start justify-between gap-4 rounded-lg bg-muted p-4">
-            <div className="hidden overflow-y-auto bg-gray-100 px-6 py-6 md:block dark:bg-gray-900">
+            <div className="hidden overflow-y-auto bg-muted px-6 py-6 md:block">

Also applies to: 244-244

apps/web/src/app/[locale]/layout.tsx (1)

80-92: Consider making suppressHydrationWarning conditional.

The suppressHydrationWarning attribute is always set to true, but the style prop is only set when primaryColor is defined. This unnecessarily suppresses hydration warnings even when no dynamic styles are applied.

🔎 Proposed optimization
    <html
      lang={locale}
      className={inter.className}
-     suppressHydrationWarning={true}
+     suppressHydrationWarning={!!primaryColor}
      style={
        primaryColor
          ? ({
              "--primary": primaryColor,
              "--primary-foreground": getForegroundColor(primaryColor),
            } as React.CSSProperties)
          : undefined
      }
    >

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

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

⚠️ Outside diff range comments (2)
packages/ui/src/page-tabs.tsx (1)

42-42: Update TabsContent ref type for consistency.

TabsContent still uses the deprecated React.ElementRef, while TabsTrigger was correctly updated to React.ComponentRef on Line 26. Apply the same update here for consistency.

🔎 Proposed fix
 const TabsContent = React.forwardRef<
-  React.ElementRef<typeof TabsPrimitive.Content>,
+  React.ComponentRef<typeof TabsPrimitive.Content>,
   React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
 >(({ className, ...props }, ref) => (
apps/web/src/components/forms/poll-details-form.tsx (1)

1-1: Add missing "use client" directive.

This file uses client-side hooks (useFormContext, useTranslation, useFormValidation) and requires the "use client" directive at the top.

As per coding guidelines, "Add the 'use client' directive to the top of any .tsx file that requires client-side javascript".

🔎 Proposed fix
+"use client";
+
 import { FormField, FormItem, FormLabel, FormMessage } from "@rallly/ui/form";
♻️ Duplicate comments (6)
apps/web/src/components/participant.tsx (1)

30-31: Critical: Hardcoded white background breaks dark mode.

The hover:bg-white class uses a hardcoded white background that will not adapt to dark mode. This is a blocker for the dark mode feature in this PR.

Replace with a theme-aware semantic color token such as hover:bg-gray-50 or another appropriate background utility that automatically adapts between light and dark themes.

🔎 Proposed fix
-        "hover:-translate-x-2 hover:cursor-pointer hover:overflow-visible hover:whitespace-nowrap hover:rounded-md hover:bg-white hover:p-2":
+        "hover:-translate-x-2 hover:cursor-pointer hover:overflow-visible hover:whitespace-nowrap hover:rounded-md hover:bg-gray-50 hover:p-2":
           isTruncated,
apps/landing/src/components/error-page.tsx (1)

26-29: Address previously flagged hardcoded color tokens.

The icon (line 26) and description (line 29) still use hardcoded color tokens (text-gray-400 and text-gray-600) instead of semantic tokens. This was flagged in a previous review and remains unaddressed. For dark mode to work properly, these should be updated to semantic tokens consistent with the web app's error component:

  • Line 26: text-gray-400text-muted-foreground
  • Line 29: text-gray-600text-foreground or text-muted-foreground

Additionally, per coding guidelines and learnings, the FileSearchIcon should be wrapped with the <Icon> component from @rallly/ui/icon.

🔎 Proposed fix
+import { Icon } from "@rallly/ui/icon";
+
 const ErrorPage: React.FunctionComponent<ComponentProps> = ({
   icon,
   title,
   description,
 }) => {
   const { t } = useTranslation();
   return (
     <div className="inset-0 flex h-full w-full items-center justify-center lg:absolute">
       <div className="space-y-8">
         <div className="space-y-4 text-center">
           {icon || (
-            <FileSearchIcon className="mb-4 inline-block size-24 text-gray-400" />
+            <Icon size="lg">
+              <FileSearchIcon className="mb-4 size-24" />
+            </Icon>
           )}
           <div className="mb-2 font-bold text-3xl text-primary">{title}</div>
-          <p className="text-gray-600">{description}</p>
+          <p className="text-muted-foreground">{description}</p>
         </div>
apps/web/src/features/space/components/space-tier.tsx (1)

20-28: Semantic token migration is good, but structural issues remain unresolved.

The migration to semantic color tokens (border-muted-border, bg-muted, text-muted-foreground, border-primary-border, bg-primary, text-primary-foreground) properly supports dark mode. However, the underlying structural issues flagged in the previous review still need to be addressed:

  1. Inconsistent color handling: The HandHeartIcon (line 21) inherits color from the parent's text-muted-foreground, while the SparklesIcon (line 27) explicitly sets text-primary-foreground.

  2. Missing <Icon> wrapper: Per coding guidelines and learnings, both icons should be wrapped with the <Icon> component from @rallly/ui/icon for consistent sizing and color handling.

Based on learnings, the <Icon> component provides the correct color and size automatically.

🔎 Proposed fix with consistent icon usage
+import { Icon } from "@rallly/ui/icon";
+
 export const SpaceTierIcon = ({ tier }: { tier: SpaceTier }) => {
   switch (tier) {
     case "hobby":
       return (
-        <div className="inline-flex size-10 items-center justify-center rounded-md border border-muted-border bg-muted text-muted-foreground text-xs">
-          <HandHeartIcon className="size-5" />
+        <div className="inline-flex size-10 items-center justify-center rounded-md border border-muted-border bg-muted text-muted-foreground">
+          <Icon>
+            <HandHeartIcon />
+          </Icon>
         </div>
       );
     case "pro":
       return (
-        <div className="inline-flex size-10 items-center justify-center rounded-md border border-primary-border bg-primary text-xs">
-          <SparklesIcon className="size-5 text-primary-foreground" />
+        <div className="inline-flex size-10 items-center justify-center rounded-md border border-primary-border bg-primary text-primary-foreground">
+          <Icon>
+            <SparklesIcon />
+          </Icon>
         </div>
       );
   }
 };
apps/web/src/features/billing/components/pay-wall-dialog.tsx (1)

189-189: Typo: Stray b class in className.

There's an errant b class that appears to be a leftover from an incomplete edit.

🔎 Proposed fix
-                  <div className="b flex items-start justify-between gap-4 rounded-lg bg-gray-100 p-4 dark:bg-gray-700">
+                  <div className="flex items-start justify-between gap-4 rounded-lg bg-gray-100 p-4 dark:bg-gray-700">
packages/ui/src/alert.tsx (1)

13-14: Missing text-primary-foreground on the primary variant.

The primary variant sets bg-primary but lacks text-primary-foreground for the root text color, unlike the other variants (info, warning, success, note) which all include both background and foreground colors. This inconsistency could cause readability issues.

🔎 Proposed fix
         primary:
-          "bg-primary *:data-[slot=alert-description]:text-primary-foreground/90 [&>svg]:text-primary-foreground",
+          "bg-primary text-primary-foreground *:data-[slot=alert-description]:text-primary-foreground/90 [&>svg]:text-primary-foreground",
apps/web/src/env.ts (1)

109-113: Add format validation for PRIMARY_COLOR.

The PRIMARY_COLOR environment variable should validate that the value is a valid hex color format to prevent runtime issues with invalid color values.

🔎 Proposed fix
    /**
     * Primary color for theming (hex format, e.g., "#4f46e5")
     */
-    PRIMARY_COLOR: z.string().optional(),
+    PRIMARY_COLOR: z.string().regex(/^#[0-9A-Fa-f]{6}$/, "PRIMARY_COLOR must be a valid hex color (e.g., #4f46e5)").optional(),
🧹 Nitpick comments (10)
packages/ui/src/switch.tsx (1)

24-24: Dark mode thumb styling looks good.

The dark:bg-gray-700 addition provides appropriate contrast for the switch thumb in dark mode. The use of specific color values (bg-white and bg-gray-700) for the thumb—rather than semantic tokens—may be intentional to ensure consistent visibility across all switch states.

Optional: Consider whether bg-white and dark:bg-gray-700 could use semantic tokens (e.g., bg-background or similar) for greater consistency with the token migration mentioned in the PR. However, specific colors for the thumb may be preferable for guaranteed visibility.

apps/web/src/components/random-gradient-bar.tsx (1)

1-1: Consider renaming component to reflect static behavior.

The component name "RandomGradientBar" implies non-deterministic rendering, but the gradient is now fixed. Consider renaming to GradientBar or PrimaryGradientBar to better communicate its current behavior.

packages/tailwind-config/shared-styles.css (1)

11-131: Consider tokenizing hard-coded hex values.

The theme structure is comprehensive, but several hard-coded hex values could be replaced with token references for better maintainability:

  • Lines 34-35, 102-103: --action-bar colors (#1f2937, #f9fafb) appear identical in both light and dark modes
  • Line 84: #f9fafb for sidebar-accent-foreground
  • Line 114: #27272a50 for card background (with alpha)

These could reference existing color tokens (e.g., var(--color-gray-800), var(--color-gray-50)) for consistency and easier theme updates.

Example tokenization
-  --action-bar: #1f2937;
-  --action-bar-foreground: #f9fafb;
+  --action-bar: var(--color-gray-800);
+  --action-bar-foreground: var(--color-gray-50);
-  --card: #27272a50;
+  --card: var(--color-zinc-800);
apps/web/src/app/api/private/utils/api-key.ts (2)

21-38: Consider early validation of hash format.

The verifyApiKey function handles malformed stored hashes gracefully by returning false, which is secure. However, a malformed stored hash (e.g., corrupted database entry) could silently fail authentication without logging.

🔎 Optional: Add logging for malformed hashes
 const verifyApiKey = (rawKey: string, stored: string) => {
   const trimmed = stored.trim();
   if (trimmed.startsWith("sha256$")) {
     const [, salt, hash] = trimmed.split("$");
     if (!salt || !hash || !isHexSha256(hash)) {
+      console.warn("Malformed salted hash format detected");
       return false;
     }
     const computed = hashApiKey(rawKey, salt);
     return crypto.timingSafeEqual(Buffer.from(computed), Buffer.from(hash));
   }

   if (!isHexSha256(trimmed)) {
+    console.warn("Invalid hash format detected");
     return false;
   }

76-81: Unhandled promise rejection in waitUntil.

The waitUntil call does not handle potential database errors. While waitUntil runs asynchronously and won't block the response, unhandled rejections could cause issues in some environments.

🔎 Proposed fix
     waitUntil(
       prisma.apiKey.update({
         where: { id: apiKey.id },
         data: { lastUsedAt: new Date() },
-      }),
+      }).catch(() => {
+        // Silently ignore lastUsedAt update failures
+      }),
     );
apps/web/src/app/[locale]/(space)/settings/preferences/components/theme-preference.tsx (1)

23-32: Remove redundant key prop.

The key prop on line 24 is unnecessary since ThemeOption is not being rendered in an array iteration context—the parent RadioGroup handles the mapping. The key should be on the outer component when iterating, not inside ThemeOption.

🔎 Proposed fix
   return (
     <label
-      key={value}
       className="flex aspect-video flex-1 flex-col items-center justify-center gap-4 rounded-lg border bg-card p-4 transition-colors hover:bg-card-accent has-data-[state=checked]:border-primary/50 has-data-[state=checked]:bg-linear-to-b has-data-[state=checked]:from-primary/10 has-data-[state=checked]:to-primary/5 has-data-[state=checked]:ring-primary [&>svg]:size-5"
       htmlFor={value}
     >
apps/web/src/app/api/private/utils/time-slots.ts (1)

53-66: Consider validating interval is positive.

If interval is zero or negative, the inner loop (lines 117-127) would either produce infinite iterations or no slots. While the schema likely validates this upstream, defensive validation here would prevent potential issues.

🔎 Proposed fix
 export const generateTimeSlots = (
   generator: SlotGeneratorInput,
   timeZone: string,
   durationMinutes: number,
 ): Array<TimeSlot> => {
+  const interval = generator.interval ?? durationMinutes;
+  if (interval <= 0 || durationMinutes <= 0) {
+    return [];
+  }
+
   const dayMap: Record<SlotGeneratorInput["daysOfWeek"][number], number> = {

Then update line 114:

-    const interval = generator.interval ?? durationMinutes;
+    // interval already validated above
apps/web/src/app/api/private/[...route]/route.test.ts (2)

77-150: Consider adding a test for hash mismatch scenario.

The authentication tests cover missing key, invalid prefix, revoked, and expired scenarios. However, there's no test for when the prefix is valid but the actual key doesn't match the hash (e.g., a tampered key with correct prefix).

🔎 Suggested test case
it("should return 401 when key hash does not match", async () => {
  const res = await app.request("/api/private/polls", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: "Bearer rallly_abc123_wrongsecret",
    },
    body: JSON.stringify({
      title: "Test Poll",
      dates: ["2025-01-15"],
    }),
  });

  expect(res.status).toBe(401);
});

286-415: Consider adding test for NO_OPTIONS_GENERATED error.

The slot tests cover successful creation and timezone errors, but don't test the scenario where a slot generator produces no valid options (e.g., when fromTime is after toTime).

🔎 Suggested test case
it("should return error when slot generator produces no options", async () => {
  const res = await app.request("/api/private/polls", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${testApiKey}`,
    },
    body: JSON.stringify({
      title: "Test Poll",
      slots: {
        duration: 30,
        timezone: "Europe/London",
        times: {
          startDate: "2025-01-20",
          endDate: "2025-01-20",
          days: ["mon"],
          startTime: "17:00",
          endTime: "09:00", // End before start
        },
      },
    }),
  });

  expect(res.status).toBe(400);
  const json = await res.json();
  expect(json.error.code).toBe("NO_OPTIONS_GENERATED");
});
apps/landing/src/app/[locale]/(main)/footer.tsx (1)

100-131: Social icon links updated consistently.

The migration from hover:text-primary-600 to hover:text-primary is applied consistently across all four social links, maintaining the hover behavior correctly.

Optional: Per coding guidelines, consider wrapping icons with the <Icon> component from @rallly/ui/icon for proper color and size management. Based on learnings, this would follow the project's icon handling pattern.

Example refactor for one icon
+import { Icon } from "@rallly/ui/icon";
+
 <a
   target="_blank"
   href="https://twitter.com/ralllyco"
   className="text-gray-500 text-sm hover:text-primary hover:no-underline"
   rel="noreferrer noopener"
 >
-  <TwitterIcon className="size-5" />
+  <Icon>
+    <TwitterIcon />
+  </Icon>
 </a>

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
packages/tailwind-config/shared-styles.css (1)

245-249: Hard-coded colors in bg-diagonal-lines won't adapt to dark mode.

The utility uses fixed hex colors (#f9fafb and #e5e7eb) that will display poorly in dark mode. Consider using theme-aware tokens or providing dark mode overrides.

🔎 Suggested approach using CSS custom properties
 @utility bg-diagonal-lines {
-  background-color: #f9fafb;
-  background-image: url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cline x1='0' y1='0' x2='100%' y2='100%' stroke='%23e5e7eb' stroke-width='1'/%3E%3C/svg%3E");
+  background-color: var(--color-gray-50);
   background-repeat: repeat-y;
 }
+
+.dark .bg-diagonal-lines {
+  background-color: var(--color-gray-900);
+}

Note: The SVG data URL would need to be dynamically generated or have separate light/dark variants since inline SVGs in data URLs cannot reference CSS variables.

♻️ Duplicate comments (1)
packages/ui/src/alert.tsx (1)

12-13: Primary variant still missing root text-primary-foreground.

The primary variant sets bg-primary but doesn't include text-primary-foreground for the root text color. Unlike other variants (info, warning, success, note, error) which all set both background and foreground colors, the primary variant only has child selectors for text color. This could cause readability issues when text is placed directly in the Alert without using AlertDescription.

🔎 Proposed fix
       primary:
-        "bg-primary *:data-[slot=alert-description]:text-primary-foreground/90 [&>svg]:text-primary-foreground",
+        "bg-primary text-primary-foreground *:data-[slot=alert-description]:text-primary-foreground/90 [&>svg]:text-primary-foreground",
🧹 Nitpick comments (2)
apps/web/src/utils/emails.ts (1)

20-22: Simplify ternary to nullish coalescing operator.

The pattern env.LOGO_ICON_URL ? env.LOGO_ICON_URL : fallback can be simplified to env.LOGO_ICON_URL ?? fallback since the value is either a valid URL or undefined (validated by z.url().optional()).

🔎 Proposed refactor
-      logoUrl: env.LOGO_ICON_URL
-        ? env.LOGO_ICON_URL
-        : "https://d39ixtfgglw55o.cloudfront.net/images/rallly-logo-mark.png",
+      logoUrl: env.LOGO_ICON_URL ?? "https://d39ixtfgglw55o.cloudfront.net/images/rallly-logo-mark.png",
packages/tailwind-config/shared-styles.css (1)

163-163: Remove trailing whitespace.

Line 163 contains only whitespace. Consider removing it for consistency.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8fb699 and aa5e195.

📒 Files selected for processing (24)
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/components/participant.tsx
  • apps/web/src/components/poll/desktop-poll.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/components/poll/mobile-poll.tsx
  • apps/web/src/components/poll/mobile-poll/poll-option.tsx
  • apps/web/src/components/poll/vote-icon.tsx
  • apps/web/src/components/poll/vote-selector.tsx
  • apps/web/src/env.ts
  • apps/web/src/utils/emails.ts
  • packages/emails/src/components/email-context.tsx
  • packages/emails/src/send-email.tsx
  • packages/emails/src/types.ts
  • packages/tailwind-config/shared-styles.css
  • packages/ui/src/alert.tsx
  • packages/ui/src/badge.tsx
  • packages/ui/src/button.tsx
  • packages/ui/src/dropdown-menu.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/web/src/components/poll/desktop-poll.tsx
  • apps/web/src/components/poll/mobile-poll.tsx
  • apps/web/src/components/poll/mobile-poll/poll-option.tsx
  • packages/emails/src/send-email.tsx
  • packages/ui/src/dropdown-menu.tsx
  • apps/web/src/components/poll/vote-selector.tsx
  • apps/web/src/components/participant.tsx
  • packages/ui/src/button.tsx
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx}: Use dayjs for date handling
Use react-query for data fetching
Prefer implicit return values over explicit return values
Use zod for form validation
Create separate import statements for types
Prefer using the React module APIs (e.g. React.useState) instead of standalone hooks (e.g. useState)
Prefer double quotes for strings over single quotes
Only add comments when it is necessary to explain code that isn't self-explanatory

**/*.{ts,tsx}: Only create named interfaces when they're reused or complex
When TypeScript errors occur for missing i18n keys, run pnpm i18n:scan instead of manually adding keys

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*.{tsx,css,config.ts}

📄 CodeRabbit inference engine (.cursorrules)

Use tailwindcss for styling

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • packages/tailwind-config/shared-styles.css
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

**/*.tsx: Use react-hook-form for form handling
All text in the UI should be translated using either the Trans component or the useTranslation hook
Prefer composable components in the style of shadcn UI over large monolithic components
DropdownMenuItem is a flex container with a preset gap so there is no need to add margins to the children
The size and colour of an icon should be set by wrapping it with the component from @rallly/ui/icon which will give it the correct colour and size
Keep the props of a component as minimal as possible. Pass only the bare minimum amount of information needed to it
All text in the UI should be translatable
Use the component in client components from @/components/trans with the defaults prop to provide the default text
Always use a composable patterns when building components
Use cn() from @rallly/ui to compose classes
Add the "use client" directive to the top of any .tsx file that requires client-side javascript

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*.{ts,tsx,json}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{ts,tsx,json}: i18n keys are in camelCase
i18nKeys should describe the message in camelCase. Ex. "lastUpdated": "Last Updated"
If the i18nKey is not intended to be reused, prefix it with the component name in camelCase

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*

📄 CodeRabbit inference engine (.cursorrules)

Always use kebab-case for file names

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • packages/tailwind-config/shared-styles.css
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for code formatting with indent of 2 spaces and double quotes

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{tsx,ts}: Prefer inline prop types over named interfaces for simple component props (e.g., function Component({ prop }: { prop: string }) instead of defining a separate interface)
Always use the useDialog hook from @rallly/ui/dialog for managing dialog state instead of manual useState for open/close state
Use TanStack Query with tRPC for server state management
Use React Context for client state (auth, preferences, etc.)
Use react-hook-form with Zod validation for form state management
Use TailwindCSS with custom design system for styling

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • packages/emails/src/components/email-context.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • packages/ui/src/badge.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
  • packages/ui/src/alert.tsx
apps/web/src/components/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place shared components in apps/web/src/components/

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
apps/web/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/web/.cursor/rules/better-auth.md)

apps/web/**/*.{ts,tsx,js,jsx}: When using the username plugin, require all sign up endpoints to accept a username (used for login, normalized) and an optional displayUsername (raw, for display purposes).
When using the username plugin, all username values must be normalized according to the configuration function before storage or comparison (default: lowercase).
Whenever updating a user's username, always check for uniqueness and apply the normalization procedure.
Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.

Files:

  • apps/web/src/components/poll/vote-icon.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row-form.tsx
  • apps/web/src/components/poll/desktop-poll/participant-row.tsx
  • apps/web/src/components/poll/desktop-poll/poll-header.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/env.ts
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/utils/emails.ts
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
apps/web/src/app/**

📄 CodeRabbit inference engine (CLAUDE.md)

Use Next.js App Router conventions for route handlers

Files:

  • apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx
  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
**/*.ts

📄 CodeRabbit inference engine (.cursorrules)

On the server use the getTranslations function from @/i18n/server to get the translations

Files:

  • apps/web/src/env.ts
  • packages/emails/src/types.ts
  • apps/web/src/utils/emails.ts
packages/ui/**/*.tsx

📄 CodeRabbit inference engine (.cursorrules)

shadcn-ui components should be added to packages/ui

Files:

  • packages/ui/src/badge.tsx
  • packages/ui/src/alert.tsx
🧠 Learnings (10)
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.tsx : The size and colour of an icon should be set by wrapping it with the <Icon> component from rallly/ui/icon which will give it the correct colour and size

Applied to files:

  • apps/web/src/components/poll/vote-icon.tsx
  • apps/web/src/components/empty-state.tsx
  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Rallly is built with Next.js 15, React 19, tRPC, Prisma with PostgreSQL, NextAuth.js, TailwindCSS, and TypeScript throughout

Applied to files:

  • packages/emails/src/components/email-context.tsx
📚 Learning: 2025-06-14T16:32:39.967Z
Learnt from: lukevella
Repo: lukevella/rallly PR: 1776
File: apps/web/src/auth/adapters/prisma.ts:40-60
Timestamp: 2025-06-14T16:32:39.967Z
Learning: In the Rallly codebase, optional user fields like timeZone, weekStart, timeFormat, and locale correspond to nullable database columns, so passing undefined values won't cause insert failures.

Applied to files:

  • packages/emails/src/components/email-context.tsx
📚 Learning: 2025-11-25T11:04:24.541Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: apps/web/.cursor/rules/better-auth.md:0-0
Timestamp: 2025-11-25T11:04:24.541Z
Learning: Applies to apps/web/**/*.{ts,tsx,js,jsx} : Login endpoints or forms supporting username authentication must allow signing in with username and password, not just email.

Applied to files:

  • apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.tsx : DropdownMenuItem is a flex container with a preset gap so there is no need to add margins to the children

Applied to files:

  • apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{tsx,ts} : Use TailwindCSS with custom design system for styling

Applied to files:

  • packages/tailwind-config/shared-styles.css
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.{tsx,css,config.ts} : Use tailwindcss for styling

Applied to files:

  • packages/tailwind-config/shared-styles.css
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{tsx,ts} : Use React Context for client state (auth, preferences, etc.)

Applied to files:

  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
📚 Learning: 2025-11-25T11:04:05.725Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:04:05.725Z
Learning: Applies to **/*.{tsx,ts} : Always use the `useDialog` hook from `rallly/ui/dialog` for managing dialog state instead of manual `useState` for open/close state

Applied to files:

  • apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx
📚 Learning: 2025-11-25T11:03:55.173Z
Learnt from: CR
Repo: lukevella/rallly PR: 0
File: .cursorrules:0-0
Timestamp: 2025-11-25T11:03:55.173Z
Learning: Applies to **/*.tsx : Use `cn()` from rallly/ui to compose classes

Applied to files:

  • packages/ui/src/alert.tsx
🧬 Code graph analysis (3)
apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx (1)
packages/ui/src/alert.tsx (1)
  • Alert (71-71)
apps/web/src/components/poll/desktop-poll/participant-row.tsx (1)
packages/ui/src/lib/utils.ts (1)
  • cn (5-7)
apps/web/src/utils/emails.ts (2)
apps/web/src/env.ts (1)
  • env (7-196)
packages/utils/src/absolute-url.ts (1)
  • absoluteUrl (18-39)
🔇 Additional comments (26)
packages/emails/src/components/email-context.tsx (1)

16-16: LGTM!

The addition of appName to the preview context correctly matches the updated EmailContext type definition and provides the necessary branding metadata for email templates.

packages/emails/src/types.ts (1)

13-14: LGTM!

The type extensions are well-designed:

  • appName as required ensures consistent branding across all email contexts
  • primaryColor as optional provides flexibility for custom theming without breaking existing implementations
packages/ui/src/badge.tsx (1)

12-17: LGTM! Clean migration to semantic tokens for dark mode.

The variant updates properly migrate to semantic design tokens that support theming:

  • text-primary-foreground and bg-muted will adapt automatically to light/dark modes
  • bg-primary/10 opacity pattern works well across themes

Note: The default variant no longer includes a border—this appears intentional as part of the design system update.

apps/web/src/components/poll/vote-icon.tsx (1)

13-13: LGTM! Size adjustment aligns with design system update.

The reduction from size-4 to size-3 for the small variant is consistent with the broader styling token migration mentioned in this PR's objectives. This makes small vote icons slightly more compact (16px → 12px).

apps/web/src/app/[locale]/(auth)/reset-password/components/reset-password-form.tsx (1)

45-45: LGTM!

The variant change from "destructive" to "error" correctly aligns with the new error variant defined in alert.tsx. This resolves the previous review concern about the missing destructive variant.

packages/ui/src/alert.tsx (2)

19-25: LGTM!

The new error variant follows the same styling pattern as other semantic variants with proper light/dark mode support. The default variant change to note provides a sensible neutral default.


7-27: The tip variant migration is complete — no remaining variant="tip" usages found in the codebase. All Alert components have been updated to use appropriate existing variants.

apps/web/src/app/[locale]/(space)/settings/billing/page-client.tsx (1)

138-154: LGTM!

The variant change from "tip" to "success" is semantically appropriate for a seat update confirmation message. The CheckCircleIcon aligns well with the success variant, and this resolves the missing tip variant concern from the alert component update.

apps/web/src/components/poll/desktop-poll/poll-header.tsx (1)

51-51: LGTM! Consistent theme token migration.

All background changes correctly replace hardcoded colors (bg-white, bg-gray-50) with the semantic bg-background token, enabling dark mode support while preserving the sticky positioning and layering behavior.

Also applies to: 78-78, 117-117, 142-142

apps/web/src/components/poll/desktop-poll/participant-row.tsx (2)

51-51: LGTM! Consistent theme token migration.

Header cell correctly migrates to bg-background for dark mode support.


73-84: Nice simplification of vote cell backgrounds.

The refactored logic is cleaner and more maintainable:

  • State-based backgrounds using bg-background with opacity modifiers follow Tailwind v4 patterns correctly
  • Removed unnecessary DOM wrapper for conditional backgrounds
  • Trailing cell simplified from diagonal pattern to plain border

This maintains visual hierarchy through opacity while reducing complexity.

apps/web/src/components/poll/desktop-poll/participant-row-form.tsx (2)

60-60: LGTM! Past issue resolved.

The sticky cell now correctly includes bg-background, addressing the previous review concern about content bleed-through during horizontal scrolling.


108-139: Comprehensive dark mode support for interactive states.

The vote cells now include well-defined hover and active states for both light and dark modes with appropriate visual feedback (background changes, rings, and transitions). The removal of bg-gray-50 and bg-diagonal-lines aligns with the simplified theming approach.

apps/web/src/env.ts (2)

105-122: LGTM! Environment variables for theming are well-defined.

The new theming and branding variables are properly validated:

  • APP_NAME has a sensible default
  • PRIMARY_COLOR regex correctly validates 3 or 6-digit hex colors
  • URL validation applied to LOGO_URL and LOGO_ICON_URL

The addressed past review concern about PRIMARY_COLOR validation has been properly resolved.


190-193: LGTM! Runtime environment mappings are complete.

All new environment variables are correctly mapped in the runtimeEnv configuration.

apps/web/src/utils/emails.ts (1)

26-27: LGTM! Primary color and app name integration is correct.

The environment-driven configuration properly handles:

  • primaryColor with a sensible fallback matching the env.ts example
  • appName safely uses the environment value (has default "Rallly" in schema)
apps/web/src/app/[locale]/(space)/(dashboard)/components/nav-user.tsx (2)

10-17: LGTM! Imports are well-organized and complete.

All new imports for the theme submenu functionality are properly used in the component. The imports follow the existing code organization pattern.

Also applies to: 20-20, 24-28


93-124: Excellent implementation of the theme submenu!

The theme selector follows all coding guidelines correctly:

  • Uses composable shadcn-style pattern with Sub/SubTrigger/SubContent/Portal
  • RadioGroup is semantically appropriate for mutually exclusive theme selection
  • Icons are properly wrapped with the Icon component
  • All UI text uses Trans with camelCase i18nKeys and defaults
  • No unnecessary margins on children (DropdownMenuItem is a flex container)

Remember to run pnpm i18n:scan to register the new i18n keys (theme, themeSystem, themeLight, themeDark) for translation workflows.

packages/tailwind-config/shared-styles.css (4)

3-4: LGTM: Dark variant correctly configured for Tailwind v4.

The custom variant definition follows Tailwind v4 conventions and implements class-based dark mode appropriately.


11-71: LGTM: Comprehensive light mode token system.

The root theme tokens are well-structured with semantic naming and appropriate use of OKLCH color space for better color fidelity.


209-220: LGTM: Clean gray-to-zinc palette migration.

This mapping provides a smooth migration path from gray to zinc palette while maintaining backward compatibility with existing code that references gray tokens.


231-239: LGTM: Proper Tailwind v4 compatibility patch.

This correctly addresses the Tailwind v4 breaking change where default border colors changed to currentcolor. The patch ensures consistent behavior with v3 while allowing explicit overrides.

apps/web/src/app/[locale]/invite/[urlId]/invite-page.tsx (4)

3-12: LGTM!

The imports are well-organized and follow the coding guidelines. Icons are correctly imported for wrapping with the <Icon> component.


86-86: LGTM!

The responsive spacing adjustment (space-y-2 md:space-y-4) improves the layout by reducing spacing on mobile devices while increasing it on larger screens.


63-63: The bg-poll semantic token is properly defined and correctly implemented.

The token is defined in packages/tailwind-config/shared-styles.css with appropriate light mode (gray-100) and dark mode (gray-900) values, and is correctly exposed via the Tailwind theme configuration. No action needed.


45-45: The link styling is correct. The Link element inside Alert variant="primary" automatically inherits the text color (text-primary-foreground/90) through the Alert component's CSS cascade, making an explicit text-primary class unnecessary. The visual hierarchy and contrast are properly maintained by the design system.

Likely an incorrect or invalid review comment.

Comment on lines +73 to +127
.dark {
--background: var(--color-gray-900);
--foreground: var(--color-gray-300);

--primary: var(--color-indigo-500);
--primary-foreground: var(--color-indigo-50);

--sidebar: var(--color-gray-900);
--sidebar-foreground: var(--color-gray-100);
--sidebar-border: var(--color-gray-800);

--sidebar-accent: var(--color-gray-800);
--sidebar-accent-foreground: #f9fafb;

--border: oklch(.274 .006 286.033);
--border-muted: oklch(.274 .006 286.033 / .5);

--input: var(--color-gray-800);
--input-border: var(--color-gray-700);
--input-foreground: var(--color-gray-100);

--ring: #4b5563;

--destructive: var(--color-rose-500);
--destructive-foreground: var(--color-rose-50);

--accent: var(--color-gray-700);
--accent-border: var(--color-gray-600);
--accent-foreground: var(--color-gray-100);

--action-bar: #1f2937;
--action-bar-foreground: #f9fafb;

--muted: var(--color-gray-800);
--muted-foreground: var(--color-gray-400);
--muted-border: var(--color-gray-700);

--popover: var(--color-gray-800);
--popover-border: var(--color-gray-700);
--popover-foreground: var(--color-gray-300);
--popover-accent: var(--color-gray-700);

--card: #27272a50;
--card-border: var(--color-gray-800);
--card-foreground: var(--color-gray-300);
--card-accent: var(--color-gray-800);

--success: var(--color-green-950);
--success-foreground: var(--color-green-100);

--warning: var(--color-yellow-950);
--warning-foreground: var(--color-yellow-100);

--poll: var(--color-gray-900);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add dark mode overrides for --info and --tip tokens.

The dark mode block is missing overrides for --info, --info-foreground, --tip, and --tip-foreground. These tokens are defined in light mode (lines 61-62, 67-68) but not overridden for dark mode, which means they'll use light mode colors (light backgrounds with dark text) in dark mode—likely resulting in poor contrast.

🔎 Suggested dark mode overrides
   --warning: var(--color-yellow-950);
   --warning-foreground: var(--color-yellow-100);
 
+  --info: var(--color-blue-950);
+  --info-foreground: var(--color-blue-100);
+
+  --tip: var(--color-green-950);
+  --tip-foreground: var(--color-green-100);
+
   --poll: var(--color-gray-900);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.dark {
--background: var(--color-gray-900);
--foreground: var(--color-gray-300);
--primary: var(--color-indigo-500);
--primary-foreground: var(--color-indigo-50);
--sidebar: var(--color-gray-900);
--sidebar-foreground: var(--color-gray-100);
--sidebar-border: var(--color-gray-800);
--sidebar-accent: var(--color-gray-800);
--sidebar-accent-foreground: #f9fafb;
--border: oklch(.274 .006 286.033);
--border-muted: oklch(.274 .006 286.033 / .5);
--input: var(--color-gray-800);
--input-border: var(--color-gray-700);
--input-foreground: var(--color-gray-100);
--ring: #4b5563;
--destructive: var(--color-rose-500);
--destructive-foreground: var(--color-rose-50);
--accent: var(--color-gray-700);
--accent-border: var(--color-gray-600);
--accent-foreground: var(--color-gray-100);
--action-bar: #1f2937;
--action-bar-foreground: #f9fafb;
--muted: var(--color-gray-800);
--muted-foreground: var(--color-gray-400);
--muted-border: var(--color-gray-700);
--popover: var(--color-gray-800);
--popover-border: var(--color-gray-700);
--popover-foreground: var(--color-gray-300);
--popover-accent: var(--color-gray-700);
--card: #27272a50;
--card-border: var(--color-gray-800);
--card-foreground: var(--color-gray-300);
--card-accent: var(--color-gray-800);
--success: var(--color-green-950);
--success-foreground: var(--color-green-100);
--warning: var(--color-yellow-950);
--warning-foreground: var(--color-yellow-100);
--poll: var(--color-gray-900);
}
.dark {
--background: var(--color-gray-900);
--foreground: var(--color-gray-300);
--primary: var(--color-indigo-500);
--primary-foreground: var(--color-indigo-50);
--sidebar: var(--color-gray-900);
--sidebar-foreground: var(--color-gray-100);
--sidebar-border: var(--color-gray-800);
--sidebar-accent: var(--color-gray-800);
--sidebar-accent-foreground: #f9fafb;
--border: oklch(.274 .006 286.033);
--border-muted: oklch(.274 .006 286.033 / .5);
--input: var(--color-gray-800);
--input-border: var(--color-gray-700);
--input-foreground: var(--color-gray-100);
--ring: #4b5563;
--destructive: var(--color-rose-500);
--destructive-foreground: var(--color-rose-50);
--accent: var(--color-gray-700);
--accent-border: var(--color-gray-600);
--accent-foreground: var(--color-gray-100);
--action-bar: #1f2937;
--action-bar-foreground: #f9fafb;
--muted: var(--color-gray-800);
--muted-foreground: var(--color-gray-400);
--muted-border: var(--color-gray-700);
--popover: var(--color-gray-800);
--popover-border: var(--color-gray-700);
--popover-foreground: var(--color-gray-300);
--popover-accent: var(--color-gray-700);
--card: #27272a50;
--card-border: var(--color-gray-800);
--card-foreground: var(--color-gray-300);
--card-accent: var(--color-gray-800);
--success: var(--color-green-950);
--success-foreground: var(--color-green-100);
--warning: var(--color-yellow-950);
--warning-foreground: var(--color-yellow-100);
--info: var(--color-blue-950);
--info-foreground: var(--color-blue-100);
--tip: var(--color-green-950);
--tip-foreground: var(--color-green-100);
--poll: var(--color-gray-900);
}
🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around lines 73 to 127, the .dark
block lacks overrides for the --info, --info-foreground, --tip, and
--tip-foreground tokens defined in light mode, causing those components to
retain light-mode colors in dark mode; add dark-mode values for these four
variables (use darker backgrounds and light foregrounds consistent with other
dark tokens, e.g., darker blue/gray backgrounds and pale/white foregrounds) to
ensure proper contrast and visual consistency in dark theme.

--popover-foreground: var(--color-gray-300);
--popover-accent: var(--color-gray-700);

--card: #27272a50;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use a token reference instead of a raw hex color.

Line 115 uses #27272a50 (raw hex with alpha), which is inconsistent with the token-based approach used throughout the rest of the file. Consider using a zinc or gray token with opacity instead.

🔎 Proposed refactor
-  --card: #27272a50;
+  --card: oklch(.17 .004 286.32 / .5);

Or reference an existing token:

-  --card: #27272a50;
+  --card: color-mix(in oklch, var(--color-gray-900) 50%, transparent);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
--card: #27272a50;
--card: oklch(.17 .004 286.32 / .5);
🤖 Prompt for AI Agents
In packages/tailwind-config/shared-styles.css around line 115, replace the raw
hex-with-alpha value `#27272a50` with a reference to an existing color token
(e.g., a zinc/gray token) and apply the alpha via your project's token-opacity
approach (either via an rgba() with the token's RGB variables or the modern CSS
color/opacity slash syntax), so the file remains consistent with token-based
colors and not hard-coded hex values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants