Skip to content

Marketing Case Studies#37

Merged
FindMalek merged 6 commits intomainfrom
fin-113
Oct 25, 2025
Merged

Marketing Case Studies#37
FindMalek merged 6 commits intomainfrom
fin-113

Conversation

@FindMalek
Copy link
Owner

@FindMalek FindMalek commented Oct 25, 2025

Summary by CodeRabbit

  • New Features

    • Launched new articles section with dedicated article pages and a browsable articles list.
    • Expanded subscription system to notify users about both articles and roadmap updates.
    • Added navigation link to articles in the marketing header.
  • Chores

    • Added dependencies for content collections and enhanced typography styling.

@FindMalek FindMalek self-assigned this Oct 25, 2025
@linear
Copy link

linear bot commented Oct 25, 2025

@vercel
Copy link
Contributor

vercel bot commented Oct 25, 2025

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

Project Deployment Preview Comments Updated (UTC)
zero-locker Ready Ready Preview Comment Oct 25, 2025 7:39pm

@coderabbitai
Copy link

coderabbitai bot commented Oct 25, 2025

Walkthrough

This PR introduces a new articles feature to the application by generalizing the existing roadmap subscription system to support multiple update types ("roadmap" and "articles"). It adds article content sourcing via content collections, new marketing pages, refactored subscription components, and updates the backend routing and email infrastructure to handle both subscription types.

Changes

Cohort / File(s) Summary
Configuration & Build Setup
.gitignore, tsconfig.json, next.config.ts, package.json, prisma.config.ts, styles/globals.css
Adds content-collections ignore patterns and path alias; integrates content-collections Next.js plugin; adds dependencies for content-collections and Tailwind typography; enables typography plugin in globals CSS.
Article Feature — Pages
app/(marketing)/articles/page.tsx, app/(marketing)/articles/[slug]/page.tsx
New articles landing page with metadata and MarketingArticlesList; new dynamic article detail page with static path generation, metadata generation, and MDX content rendering.
Article Feature — Components
components/app/marketing-articles-list.tsx, components/ui/link.tsx
New scrollable articles list component with scroll indicators; new Link component routing between internal and external URLs.
Marketing Layout Updates
components/app/marketing-header-desktop.tsx
Adds "/articles" navigation link alongside existing roadmap link.
Subscription System Generalization
components/app/marketing-subscription.tsx, components/app/email-roadmap-subscription.tsx
Refactors MarketingRoadmapSubscription → MarketingSubscription with type-based behavior; renames EmailRoadmapSubscription → EmailSubscription with dynamic subject and content per subscription type.
Roadmap Page Refactoring
app/(marketing)/roadmap/page.tsx
Updates to use generalized MarketingSubscription component with type and description props.
Content Collections Configuration
lib/content-collections.ts
New config defining articles collection sourcing MDX files, applying schema, and compiling to HTML.
Email Service Generalization
lib/email/send-roadmap-subscription-email.ts
Replaces sendRoadmapSubscriptionEmail with sendSubscriptionEmail supporting multiple types; maintains backward-compatibility wrapper.
Schema Definitions
schemas/utils/article.ts, schemas/utils/index.ts, schemas/user/roadmap/input.ts, schemas/user/roadmap/output.ts
Adds articleSchema; introduces SubscriptionInput and SubscriptionOutput schemas generalizing roadmap-specific schemas; re-exports new schemas.
Backend API — Hooks & Router
orpc/hooks/use-users.ts, orpc/routers/user.rs
Adds useSubscribeToUpdates hook; introduces subscribeToUpdates endpoint supporting multiple update types; updates subscribeToRoadmap to use generalized email service.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as UI<br/>(MarketingSubscription)
    participant Hook as useSubscribeToUpdates
    participant API as subscribeToUpdates<br/>(Endpoint)
    participant Email as sendSubscriptionEmail
    participant DB as Prisma<br/>(roadmapSubscription)

    User->>UI: Submit subscription (email, type)
    UI->>Hook: Call mutation
    Hook->>API: Call subscribeToUpdates({email, type})
    API->>DB: Upsert subscription by (email, type)
    API->>Email: sendSubscriptionEmail({to, type})
    Email->>Email: Render EmailSubscription<br/>with type-specific content
    Email-->>API: Send confirmation email
    API-->>Hook: Return success payload
    Hook-->>UI: Show success message<br/>(type-specific)
    UI-->>User: Display confirmation
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~35 minutes

  • Key areas requiring attention:
    • Subscription generalization logic across multiple layers (MarketingSubscription, EmailSubscription, sendSubscriptionEmail, subscribeToUpdates endpoint) — ensure type handling is consistent and backward-compatibility maintained.
    • Content-collections configuration and MDX compilation flow (lib/content-collections.ts, next.config.ts) — verify collection setup and integration with Next.js build pipeline.
    • Schema changes and type inference (subscriptionInputSchema, SubscriptionOutput) — confirm schema validation and type safety across endpoints.
    • New routing and page generation (articles/[slug]/page.tsx with generateStaticParams and generateMetadata) — validate dynamic path generation and metadata handling.
    • Link component abstraction (components/ui/link.tsx) — confirm correct routing behavior for internal vs. external links.

Possibly related PRs

  • PR Roadmap Page #26: Generalizes roadmap-specific subscription/email components (EmailRoadmapSubscription → EmailSubscription, sendRoadmapSubscriptionEmail → sendSubscriptionEmail) and refactors app/(marketing)/roadmap/page.tsx — directly overlaps with core refactoring in this PR.
  • PR Style/auth marketing #8: Modifies components/app/marketing-header-desktop.tsx alongside this PR's navigation link additions — shares UI component changes.
  • PR Feat/orpc #6: Modifies ORPC client/router/hooks (user-related endpoints like subscribeToUpdates) — overlaps on backend API surface.

Poem

📚 Fresh articles bloom, no longer alone,
Roadmap's faithful subscriptions now widely sown.
MDX pages emerge with a hop and a bound,
Marketing links multiply all around!
🐰 Content collections grow, generalized and true,
With a wiggle of whiskers, we're ready anew!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The PR title "Marketing Case Studies" does not accurately describe the actual changes in the changeset. The implementation introduces a new "Marketing Articles" feature with article pages, article listing, articles collection with MDX support, and an articles subscription type. Throughout the codebase, components and paths consistently use "articles" terminology (e.g., MarketingArticlesList, app/(marketing)/articles/[slug]/page.tsx, "The Articles" heading). While both case studies and articles are marketing content, the title describes something not present in the changeset, making it misleading about the actual implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 fin-113

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.

@FindMalek FindMalek marked this pull request as ready for review October 25, 2025 19:32
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (3)
components/app/email-roadmap-subscription.tsx (1)

42-48: Make cover image conditional based on subscription type.

The email template uses a hardcoded roadmap image (/email/roadmap.png) for both roadmap and articles subscriptions. Articles subscribers will receive an email with an incorrect cover image.

Apply this diff to make the image conditional:

+const imageUrl = isRoadmap ? "/email/roadmap.png" : "/email/articles.png"
+const imageAlt = isRoadmap ? `${siteConfig.name} Roadmap` : `${siteConfig.name} Articles`
+
 <Section className="mb-0">
   <Img
-    src={`${siteConfig.url}/email/roadmap.png`}
-    alt={`${siteConfig.name} Roadmap`}
+    src={`${siteConfig.url}${imageUrl}`}
+    alt={imageAlt}
     className="w-full object-cover"
   />
 </Section>

Note: Ensure the /email/articles.png image asset exists before deploying.

orpc/hooks/use-users.ts (1)

104-111: Remove console.error for production code (existing issue).

The error handler uses console.error, which violates the coding guideline prohibiting console usage. This is an existing issue that should be addressed.

Apply this diff:

 export function useSubscribeToRoadmap() {
   return useMutation({
     mutationFn: orpc.users.subscribeToRoadmap.call,
-    onError: (error) => {
-      console.error("Failed to subscribe to roadmap:", error)
-    },
   })
 }
components/app/marketing-subscription.tsx (1)

13-18: Add accessible label for the email field (FormLabel).

Inputs need an associated label. Use a visually hidden label to keep UI clean while satisfying a11y.

 import {
   Form,
   FormControl,
   FormField,
   FormItem,
+  FormLabel,
   FormMessage,
 } from "@/components/ui/form"
 ...
 <FormField
   control={form.control}
   name="email"
   render={({ field }) => (
     <FormItem className="w-full sm:flex-1">
+      <FormLabel className="sr-only">Email</FormLabel>
       <FormControl>
         <Input
           type="email"
           placeholder="your@email.com"
           disabled={subscribeToUpdatesMutation.isPending}
           {...field}
         />
       </FormControl>
       <FormMessage />
     </FormItem>
   )}
 />

As per coding guidelines.

Also applies to: 116-127

🧹 Nitpick comments (10)
schemas/user/roadmap/output.ts (1)

7-16: Consider consolidating duplicate schemas.

Both subscriptionOutputSchema and roadmapSubscribeOutputSchema are identical. While keeping both maintains backward compatibility, consider whether the roadmap-specific schema can simply re-export or extend the subscription schema to reduce duplication.

Example consolidation:

export const subscriptionOutputSchema = z.object({
  success: z.boolean(),
  error: z.string().optional(),
})

// Backward compatibility - use the same schema
export const roadmapSubscribeOutputSchema = subscriptionOutputSchema
components/shared/counter.tsx (2)

19-34: Consider adding ARIA labels for better accessibility.

While the buttons have visible text, adding aria-label attributes would improve screen reader experience by providing more context about what's being incremented/decremented.

       <Button onClick={decrement} variant="outline" size="sm">
+        aria-label={`Decrement by ${step}`}
         -
       </Button>
       <Button onClick={increment} variant="outline" size="sm">
+        aria-label={`Increment by ${step}`}
         +
       </Button>
       <Button onClick={reset} variant="secondary" size="sm">
+        aria-label={`Reset to ${initialValue}`}
         Reset
       </Button>

12-17: Consider optimizing with useCallback for stable handler references.

The handler functions are recreated on every render. For better performance, especially if Counter is used frequently, consider wrapping them with useCallback.

+import { useState, useCallback } from "react"

-  const increment = () => setCount(count + step)
-  const decrement = () => setCount(count - step)
-  const reset = () => setCount(initialValue)
+  const increment = useCallback(() => setCount(c => c + step), [step])
+  const decrement = useCallback(() => setCount(c => c - step), [step])
+  const reset = useCallback(() => setCount(initialValue), [initialValue])
components/app/marketing-articles-list.tsx (2)

28-29: Make the scroll listener passive.

Marking the scroll handler as { passive: true } improves responsiveness and prevents main-thread blocking.

-    scrollContainer.addEventListener("scroll", handleScroll)
-    return () => scrollContainer.removeEventListener("scroll", handleScroll)
+    scrollContainer.addEventListener("scroll", handleScroll, { passive: true })
+    return () =>
+      scrollContainer.removeEventListener("scroll", handleScroll)

As per best practices.


39-43: Consider using the shared UI Link for consistency.

To centralize link behavior/styles, import and use components/ui/link.tsx instead of next/link directly. Optional, for consistency with MDX links.

components/app/marketing-subscription.tsx (5)

21-26: Reuse shared Zod schema/types; avoid duplication.

Leverage the shared schema from schemas/user/roadmap/input.ts to keep UI and server validation in sync. Pick only email for this form.

-import { z } from "zod"
+import { z } from "zod"
+import { subscriptionInputSchema } from "@/schemas/user/roadmap/input"
+import type { SubscriptionInput as BaseSubscriptionInput } from "@/schemas/user/roadmap/input"
 
-const subscriptionInputSchema = z.object({
-  email: z.string().email("Please enter a valid email address"),
-})
+const subscriptionEmailOnlySchema = subscriptionInputSchema.pick({ email: true })
 
-type SubscriptionInput = z.infer<typeof subscriptionInputSchema>
+type SubscriptionInput = Pick<BaseSubscriptionInput, "email">
 
 ...
-const form = useForm<SubscriptionInput>({
-  resolver: zodResolver(subscriptionInputSchema),
+const form = useForm<SubscriptionInput>({
+  resolver: zodResolver(subscriptionEmailOnlySchema),

Based on guidelines.

Also applies to: 43-45


50-63: Clear both timers to avoid state updates after unmount.

The inner setTimeout isn’t cleared, risking updates on unmounted components. Track and clear both timers.

 useEffect(() => {
-  if (showSuccess) {
-    const timer = setTimeout(() => {
-      setIsTransitioning(true)
-      setTimeout(() => {
-        setShowSuccess(false)
-        setIsTransitioning(false)
-      }, 300) // Wait for fade out animation
-    }, 3000) // Show success for 3 seconds
-
-    return () => clearTimeout(timer)
-  }
+  if (!showSuccess) return
+  let fadeTimer: ReturnType<typeof setTimeout> | undefined
+  const timer = setTimeout(() => {
+    setIsTransitioning(true)
+    fadeTimer = setTimeout(() => {
+      setShowSuccess(false)
+      setIsTransitioning(false)
+    }, 300)
+  }, 3000)
+
+  return () => {
+    clearTimeout(timer)
+    if (fadeTimer) clearTimeout(fadeTimer)
+  }
 }, [showSuccess])

129-138: Button disabled style per repo convention.

Add disabled:opacity-50 for consistency with button states.

 <Button
   type="submit"
-  className="w-full sm:w-auto"
+  className="w-full sm:w-auto disabled:opacity-50"
   disabled={subscribeToUpdatesMutation.isPending}
 >

107-111: Autocomplete: prefer field-level autoComplete="email"; avoid disabling at form level.

Improves UX and meets validity guidance.

-<form
-  onSubmit={form.handleSubmit(onSubmit)}
-  className="flex flex-col gap-2 sm:flex-row"
-  autoComplete="off"
->
+<form
+  onSubmit={form.handleSubmit(onSubmit)}
+  className="flex flex-col gap-2 sm:flex-row"
+>
...
   <Input
     type="email"
     placeholder="your@email.com"
+    autoComplete="email"
     disabled={subscribeToUpdatesMutation.isPending}
     {...field}
   />

Also applies to: 118-123


140-153: Announce success text to AT users.

Mark the status region as live so announcements are read by screen readers.

-<div className="flex items-center gap-2">
+<div className="flex items-center gap-2" role="status" aria-live="polite">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e008ef and 383f168.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (25)
  • .gitignore (1 hunks)
  • app/(marketing)/articles/[slug]/page.tsx (1 hunks)
  • app/(marketing)/articles/page.tsx (1 hunks)
  • app/(marketing)/roadmap/page.tsx (2 hunks)
  • components/app/email-roadmap-subscription.tsx (3 hunks)
  • components/app/marketing-articles-list.tsx (1 hunks)
  • components/app/marketing-header-desktop.tsx (1 hunks)
  • components/app/marketing-subscription.tsx (5 hunks)
  • components/shared/counter.tsx (1 hunks)
  • components/ui/link.tsx (1 hunks)
  • data/articles/advanced-security-features.mdx (1 hunks)
  • data/articles/getting-started-with-zero-locker.mdx (1 hunks)
  • lib/content-collections.ts (1 hunks)
  • lib/email/send-roadmap-subscription-email.ts (1 hunks)
  • next.config.ts (2 hunks)
  • orpc/hooks/use-users.ts (1 hunks)
  • orpc/routers/user.ts (5 hunks)
  • package.json (2 hunks)
  • prisma.config.ts (2 hunks)
  • schemas/user/roadmap/input.ts (1 hunks)
  • schemas/user/roadmap/output.ts (1 hunks)
  • schemas/utils/article.ts (1 hunks)
  • schemas/utils/index.ts (1 hunks)
  • styles/globals.css (1 hunks)
  • tsconfig.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (38)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,jsx,ts,tsx}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't write functions that exceed a given Cognitive Complexity score
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Use for...of statements instead of Array.forEach
Don't create classes that only have static members
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Don't use ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use arrow functions instead of function expressions
Use Date.now() to get milliseconds since the Unix Epoch
Use .flatMap() instead of map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work
Use concise optional chaining instead of chained logical expressions
Use regular expression literals instead of the RegExp constructor when possible
Don't use number literal object member names that aren't base 10 or use underscore separators
Remove redundant terms from logical expressions
Use while loops instead of for loops when you don't need initializer and update expressions
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min and Math.max to clamp value...

Files:

  • schemas/utils/index.ts
  • schemas/user/roadmap/output.ts
  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • schemas/utils/article.ts
  • lib/email/send-roadmap-subscription-email.ts
  • next.config.ts
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • schemas/user/roadmap/input.ts
  • lib/content-collections.ts
  • app/(marketing)/roadmap/page.tsx
  • prisma.config.ts
  • orpc/hooks/use-users.ts
  • orpc/routers/user.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Make sure switch-case statements are exhaustive
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibility modifiers on class properties and methods
Put default function parameters and optional function parameters last
Initialize each enum member value explicitly
Use export type for types
Use import type for types
Make sure all enum members are literal values
Use function types instead of object types with call signatures
Don't use void type outside of generic or return types
Don't use TypeScript const enum
Don't declare empty interfaces
Don't let variables evolve into any type through reassignments
Don't use the any type
Don't misuse the non-null assertion operator (!) in TypeScript files
Don't use implicit any type on variable declarations
Don't merge interfaces and classes unsafely
Don't use overload signatures that aren't next to each other
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces

Use type-only imports when possible

**/*.{ts,tsx}: Use camelCase for function and method names
Use UPPER_SNAKE_CASE for constants
Use camelCase for variables
Use PascalCase for classes and types
Use absolute imports for top-level modules (e.g., @/components, @/lib, @/entities) instead of long relative paths
Follow import order: React imports, third-party, internal abso...

Files:

  • schemas/utils/index.ts
  • schemas/user/roadmap/output.ts
  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • schemas/utils/article.ts
  • lib/email/send-roadmap-subscription-email.ts
  • next.config.ts
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • schemas/user/roadmap/input.ts
  • lib/content-collections.ts
  • app/(marketing)/roadmap/page.tsx
  • prisma.config.ts
  • orpc/hooks/use-users.ts
  • orpc/routers/user.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,ts}: Don't use __dirname and __filename in the global scope
Use with { type: "json" } for JSON module imports
Don't access namespace imports dynamically
Use node:assert/strict over node:assert
Use the node: protocol for Node.js builtin modules

Files:

  • schemas/utils/index.ts
  • schemas/user/roadmap/output.ts
  • schemas/utils/article.ts
  • lib/email/send-roadmap-subscription-email.ts
  • next.config.ts
  • schemas/user/roadmap/input.ts
  • lib/content-collections.ts
  • prisma.config.ts
  • orpc/hooks/use-users.ts
  • orpc/routers/user.ts
schemas/**/[a-z0-9-]*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Zod schemas should be placed in the schemas directory and named in kebab-case with .ts

Files:

  • schemas/utils/index.ts
  • schemas/user/roadmap/output.ts
  • schemas/utils/article.ts
  • schemas/user/roadmap/input.ts
{components,entities,lib,orpc,schemas,hooks,config,types}/**/index.ts

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Use barrel exports: each folder should include an index.ts for clean imports

Files:

  • schemas/utils/index.ts
schemas/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Define Zod validation schemas for API and forms under schemas/

schemas/**/*.ts: Define all input/output schemas in the /schemas folder
Export both the Zod schema and its inferred TypeScript type
Provide clear, field-specific, contextual validation error messages in Zod schemas
List endpoint input schemas must include pagination (page, limit), search, and filtering fields with consistent defaults and bounds

Schemas must be named in kebab-case.ts (e.g., credential-form-dto.ts)

schemas/**/*.ts: Within schemas, import from concrete files (e.g., .../input, .../output) and avoid barrel imports under /schemas to prevent circular dependencies
Do not import from '@/schemas/' or '@/schemas/utils' inside the schemas folder; use direct file imports like '@/schemas//input'
Use .optional() for optional fields, .nullable() for nullable fields, and .nullish() for values that can be null or undefined
Validate arrays with z.array(itemSchema); mark arrays optional with .optional() when not required
Use nested object validation for structured fields (e.g., z.object({...}) or referenced schemas)
Compose schemas via .extend(...) and use .partial() for update variants
Reuse common patterns by defining base schemas and extending them for specific cases

Files:

  • schemas/utils/index.ts
  • schemas/user/roadmap/output.ts
  • schemas/utils/article.ts
  • schemas/user/roadmap/input.ts
schemas/**/output.ts

📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)

schemas/**/output.ts: Name output schemas as {entity}{Type}OutputSchema (e.g., credentialSimpleOutputSchema, userSimpleOutputSchema)
Output schema variants: {entity}SimpleOutputSchema, {entity}IncludeOutputSchema, list{Entity}OutputSchema

Files:

  • schemas/user/roadmap/output.ts
schemas/**/{input,output,enums}.ts

📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)

schemas/**/{input,output,enums}.ts: Name TypeScript types inferred from schemas as {Entity}{Type}Input / {Entity}{Type}Output / {Entity}Infer
Generate types via z.infer<> from schemas and export both schemas and their corresponding types

Files:

  • schemas/user/roadmap/output.ts
  • schemas/user/roadmap/input.ts
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{html,jsx,tsx}: Don't use the accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for the tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use an explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a title attribute for iframe elements
Include caption tracks for audio and video elements
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Use correct ISO language/country codes for the lang attribute
Don't use variables that haven't been declared in the document
Make sure void (self-closing) elements don't have children
Don't use event handlers on non-interactiv...

Files:

  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • app/(marketing)/roadmap/page.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{jsx,tsx}: Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Use semantic elements instead of role attributes in JSX
Don't use unnecessary fragments
Don't pass children as props
Don't use the return value of React.render
Make sure all dependencies are correctly specified in React hooks
Make sure all React hooks are called from the top level of component functions
Don't forget key props in iterators and collection literals
Don't define React components inside other components
Don't assign to React component props
Don't use dangerous JSX props
Don't use both children and dangerouslySetInnerHTML props on the same element
Use <>...</> instead of ...
Don't add extra closing tags for components without children
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Watch out for possible "wrong" semicolons inside JSX elements
Don't put multiple components in one file; each file must have one component

Files:

  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • app/(marketing)/roadmap/page.tsx
**/*.{html,jsx,tsx,css}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Don't use TailwindCSS class names 'h-NUMBER w-NUMBER'; instead use 'size-NUMBR'

Files:

  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • styles/globals.css
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • app/(marketing)/roadmap/page.tsx
app/**/page.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

Page files must be named page.tsx and define the page component and metadata

Page components in the Next.js App Router must be named page.tsx

Pages must be named page.tsx

Files:

  • app/(marketing)/articles/page.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • app/(marketing)/roadmap/page.tsx
app/**/[(]*[)]/**

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

Use parentheses-named folders (group) to create route groups that don’t affect the URL

Files:

  • app/(marketing)/articles/page.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • app/(marketing)/roadmap/page.tsx
app/**/{page,layout}.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

app/**/{page,layout}.tsx: Export a Metadata object as export const metadata in pages and layouts
Perform server-side data fetching in pages and layouts where possible

Files:

  • app/(marketing)/articles/page.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • app/(marketing)/roadmap/page.tsx
app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

app/**/*.tsx: Prefer Server Components by default in the app directory
Add "use client" only when necessary for Client Components

Files:

  • app/(marketing)/articles/page.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • app/(marketing)/roadmap/page.tsx
app/(marketing)/**

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Place marketing and landing content within app/(marketing)/

Files:

  • app/(marketing)/articles/page.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • app/(marketing)/roadmap/page.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-patterns.mdc)

**/*.tsx: Use Component Composition Pattern: build complex UI by composing smaller, reusable components (e.g., Form, FormField, FormItem)
Handle form errors with React Hook Form and Zod resolver for client-side validation
Avoid direct database access in React components; use hooks/services instead

**/*.tsx: Use PascalCase for React component names
Use functional components only, typed with explicit props interfaces
Optimize components using React.memo for expensive components, useMemo for heavy computations, and useCallback for stable handlers

**/*.tsx: Never use useState for form state; use React Hook Form (useForm) instead
Always use Zod schemas with zodResolver for form validation
Always use shadcn Form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
Always use Icons.spinner with animate-spin for loading states
Never use text like "Deleting..." or "Loading..." for loading states
Use size-4 instead of h-4 w-4 for spinner sizing (Tailwind)
Disable buttons during loading: disabled={isLoading}
Show spinner + text inside buttons when loading
Use Button variant="destructive" for delete actions
Add disabled opacity class to buttons: className="disabled:opacity-50"
Avoid manual form validation; rely on Zod + resolver integration

Files:

  • app/(marketing)/articles/page.tsx
  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
  • app/(marketing)/roadmap/page.tsx
components/shared/**

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Place reusable shared components under components/shared

Files:

  • components/shared/counter.tsx
components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.{ts,tsx}: Always define a TypeScript interface for component props
Follow established naming patterns consistently across files
Use strict, explicit TypeScript types for safety

Files:

  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.tsx: Add 'use client' directive for components that use client-side hooks/effects
Use cn() for conditional Tailwind classes
Use semantic Tailwind colors (e.g., text-foreground, bg-background) and consistent spacing (space-y-4, gap-4)
Use responsive Tailwind prefixes (md:, lg:, xl:) for adaptive layouts
Define component variants with cva and defaultVariants when variants are needed
Prefer useState for local state; use useReducer for complex state
Use TanStack Query hooks from /orpc/hooks for server state; prefer server-side data fetching when possible
Implement proper ARIA attributes and keyboard navigation for accessibility
Use React.memo for expensive components
Use useMemo and useCallback for expensive computations and stable callbacks
Lazy-load heavy components via dynamic import
Import only needed icons from lucide-react (no wildcard imports)
Wrap components that might fail in Error Boundaries
Provide loading and error states for async UI
Add JSDoc comments for complex components

components/**/*.tsx: Components must be named in kebab-case.tsx (e.g., dashboard-credential-form.tsx)
Use React Hook Form for form management in UI forms
Use React Hook Form error handling, display validation errors, and handle submission errors gracefully in forms

Files:

  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
components/**/[a-z0-9-]*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Name React components in kebab-case with .tsx under the components directory (e.g., dashboard-credential-form.tsx)

Files:

  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
components/{app,layout,shared,ui}/**

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Organize React components under components/app, components/layout, components/shared, and components/ui

Files:

  • components/shared/counter.tsx
  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/ui/link.tsx
  • components/app/marketing-subscription.tsx
components/{layout,shared,ui}/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)

Organize layout, shared, and base UI components under components/layout, components/shared, and components/ui

Files:

  • components/shared/counter.tsx
  • components/ui/link.tsx
components/app/**

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Place application-specific React components under components/app

Files:

  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/app/marketing-subscription.tsx
components/app/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Use naming pattern {feature}-{purpose}-{type}.tsx for app components

Files:

  • components/app/marketing-header-desktop.tsx
  • components/app/email-roadmap-subscription.tsx
  • components/app/marketing-articles-list.tsx
  • components/app/marketing-subscription.tsx
components/app/marketing-*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Marketing components in components/app must be prefixed with marketing-

Place marketing components under components/app/ with filenames starting marketing-*.tsx

Files:

  • components/app/marketing-header-desktop.tsx
  • components/app/marketing-articles-list.tsx
  • components/app/marketing-subscription.tsx
tsconfig.json

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Enable and maintain TypeScript strict mode across the project

Enable TypeScript strict mode in tsconfig.json

Files:

  • tsconfig.json
components/ui/**

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Place base shadcn/ui components under components/ui

Files:

  • components/ui/link.tsx
schemas/**/input.ts

📄 CodeRabbit inference engine (.cursor/rules/schemas-folder.mdc)

schemas/**/input.ts: Name input schemas as {entity}{Type}InputSchema (e.g., credentialInputSchema, cardMetadataInputSchema)
CRUD input schema naming: create{Entity}InputSchema, get{Entity}InputSchema, update{Entity}InputSchema, delete{Entity}InputSchema, list{Entity}InputSchema
Provide explicit, user-friendly validation messages in input schemas (e.g., .min(..., 'message'), .email('message'))

Files:

  • schemas/user/roadmap/input.ts
orpc/hooks/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/orpc-folder.mdc)

orpc/hooks/**/*.ts: Create React Query hooks that call orpc client methods with stable queryKey factories
Use enabled flags for queries that depend on optional parameters (e.g., only fetch detail when id is truthy)
On successful mutations, invalidate relevant list queries and update detail cache via setQueryData when possible
Centralize query keys with a factory (e.g., entityKeys) to ensure consistent cache keys across hooks

Use TanStack Query for server state in hooks

Files:

  • orpc/hooks/use-users.ts
orpc/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Ensure consistent error handling and responses within oRPC routes and client code

orpc/**/*.ts: Use oRPC standard error codes (ORPCError) with meaningful, contextual messages
Handle specific error types (e.g., Prisma codes), log errors, and map to appropriate ORPC error codes
Use FORBIDDEN for permission errors; include feature and action in the message and upgrade info when applicable

Always encrypt passwords before storage in server-side handlers/services

Files:

  • orpc/hooks/use-users.ts
  • orpc/routers/user.ts
orpc/hooks/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Place React Query hooks for API calls under orpc/hooks/

Files:

  • orpc/hooks/use-users.ts
**/use-*.ts

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)

Hooks files must be named use-kebab-case.ts (e.g., use-copy-to-clipboard.ts)

Files:

  • orpc/hooks/use-users.ts
orpc/hooks/use-*.ts

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)

Place oRPC-related hooks under orpc/hooks with use-*.ts naming

Files:

  • orpc/hooks/use-users.ts
orpc/routers/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/orpc-folder.mdc)

orpc/routers/**/*.ts: In oRPC routers, build procedures using os.$context(), then compose authProcedure and permissionProcedure via .use(...) middleware chaining
Define routes with .input(zodSchema).output(zodSchema).handler(async (...) => { ... }) to enforce schema-validated inputs/outputs
For list endpoints, implement pagination (page, limit), deterministic orderBy, and compute total/hasMore
Apply authMiddleware to protected routes and requirePermission(feature, action) where feature-gated access is needed
Use ORPCError with standard codes (e.g., UNAUTHORIZED, FORBIDDEN, NOT_FOUND, BAD_REQUEST) for predictable error handling
Map known ORM errors (e.g., Prisma P2002) to meaningful ORPCError codes like CONFLICT and fall back to INTERNAL_SERVER_ERROR
Always validate inputs with Zod schemas and define explicit output schemas; handle validation errors gracefully
Never expose sensitive fields in responses; use client-safe entity transformations and selective include projections
Group related procedures in a single router file and use consistent naming for routes and files
Use include helpers (e.g., EntityQuery.getClientSafeInclude()) to fetch only client-safe relations
Use server-side caching or efficient queries where appropriate; prefer pagination over large result sets
Return deterministic ordering for list endpoints (e.g., orderBy createdAt desc)
Provide meaningful error messages when throwing BAD_REQUEST/CONFLICT while avoiding sensitive details

orpc/routers/**/*.ts: Group routes by feature (e.g., credentialRouter, cardRouter) in dedicated router files
Use descriptive, action-indicative procedure names within routers
Use action-based names: create, getById, list, update, delete
Keep procedure names consistent across all routers
Use camelCase for procedure names
Use Zod schemas for all procedure inputs via .input(schema)
List procedures must return metadata (total, hasMore, page, limit) alongside data
Apply authentication middleware to all protecte...

Files:

  • orpc/routers/user.ts
orpc/routers/{credential,card,secret,user}.ts

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Define oRPC routers in orpc/routers/ with files credential.ts, card.ts, secret.ts, and user.ts

Files:

  • orpc/routers/user.ts
orpc/routers/**/!(*index).ts

📄 CodeRabbit inference engine (.cursor/rules/orpc-patterns.mdc)

Export routers individually from their own files

Files:

  • orpc/routers/user.ts
orpc/routers/*.ts

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-rules.mdc)

Define route handlers in orpc/routers with one file per domain (e.g., credential.ts, card.ts)

Files:

  • orpc/routers/user.ts
🧠 Learnings (11)
📚 Learning: 2025-10-21T20:38:30.813Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/schemas-folder.mdc:0-0
Timestamp: 2025-10-21T20:38:30.813Z
Learning: Applies to schemas/**/output.ts : Output schema variants: {entity}SimpleOutputSchema, {entity}IncludeOutputSchema, list{Entity}OutputSchema

Applied to files:

  • schemas/user/roadmap/output.ts
📚 Learning: 2025-10-02T20:55:02.956Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/app-folder.mdc:0-0
Timestamp: 2025-10-02T20:55:02.956Z
Learning: Applies to app/**/{page,layout}.tsx : Export a Metadata object as `export const metadata` in pages and layouts

Applied to files:

  • app/(marketing)/articles/page.tsx
📚 Learning: 2025-10-02T22:16:27.157Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2025-10-02T22:16:27.157Z
Learning: Applies to schemas/**/*.ts : Define Zod validation schemas for API and forms under schemas/

Applied to files:

  • schemas/utils/article.ts
📚 Learning: 2025-10-02T21:03:01.338Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/prisma-folder.mdc:0-0
Timestamp: 2025-10-02T21:03:01.338Z
Learning: Applies to prisma/seed/index.ts : Use prisma/seed/index.ts as the seeding entrypoint; connect, run seeds in dependency order (users → platforms → credentials), handle errors, and disconnect

Applied to files:

  • prisma.config.ts
📚 Learning: 2025-10-02T22:16:27.157Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/architecture.mdc:0-0
Timestamp: 2025-10-02T22:16:27.157Z
Learning: Applies to prisma/seed/** : Store database seeding scripts under prisma/seed/

Applied to files:

  • prisma.config.ts
📚 Learning: 2025-10-02T21:03:01.338Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/prisma-folder.mdc:0-0
Timestamp: 2025-10-02T21:03:01.338Z
Learning: Applies to prisma/schema/schema.prisma : Keep generator and datasource configuration in prisma/schema/schema.prisma (main schema entry)

Applied to files:

  • prisma.config.ts
📚 Learning: 2025-10-02T22:17:55.478Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/database-patterns.mdc:0-0
Timestamp: 2025-10-02T22:17:55.478Z
Learning: Applies to prisma/migrations/*_*/migrate.{ts,js} : Create migration scripts for complex data changes

Applied to files:

  • prisma.config.ts
📚 Learning: 2025-10-02T22:21:11.735Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/zero-locker-rules.mdc:0-0
Timestamp: 2025-10-02T22:21:11.735Z
Learning: Applies to orpc/hooks/use-*.ts : Place oRPC-related hooks under orpc/hooks with use-*.ts naming

Applied to files:

  • orpc/hooks/use-users.ts
📚 Learning: 2025-10-02T22:19:00.164Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/orpc-patterns.mdc:0-0
Timestamp: 2025-10-02T22:19:00.164Z
Learning: Applies to orpc/**/*.ts : Handle specific error types (e.g., Prisma codes), log errors, and map to appropriate ORPC error codes

Applied to files:

  • orpc/routers/user.ts
📚 Learning: 2025-10-02T21:01:44.094Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/orpc-folder.mdc:0-0
Timestamp: 2025-10-02T21:01:44.094Z
Learning: Applies to orpc/routers/**/*.ts : Map known ORM errors (e.g., Prisma P2002) to meaningful ORPCError codes like CONFLICT and fall back to INTERNAL_SERVER_ERROR

Applied to files:

  • orpc/routers/user.ts
📚 Learning: 2025-10-02T22:19:00.164Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/orpc-patterns.mdc:0-0
Timestamp: 2025-10-02T22:19:00.164Z
Learning: Applies to orpc/**/*.ts : Use oRPC standard error codes (ORPCError) with meaningful, contextual messages

Applied to files:

  • orpc/routers/user.ts
🧬 Code graph analysis (11)
app/(marketing)/articles/page.tsx (3)
config/site.ts (1)
  • siteConfig (3-41)
components/app/marketing-articles-list.tsx (1)
  • MarketingArticlesList (11-86)
components/app/marketing-subscription.tsx (1)
  • MarketingSubscription (33-157)
components/app/marketing-header-desktop.tsx (1)
components/ui/link.tsx (1)
  • Link (9-35)
components/app/email-roadmap-subscription.tsx (2)
config/site.ts (1)
  • siteConfig (3-41)
components/ui/link.tsx (1)
  • Link (9-35)
app/(marketing)/articles/[slug]/page.tsx (3)
components/ui/link.tsx (1)
  • Link (9-35)
components/shared/icons.tsx (1)
  • Icons (165-293)
lib/date-utils.ts (1)
  • DateFormatter (11-106)
components/app/marketing-articles-list.tsx (2)
lib/date-utils.ts (1)
  • DateFormatter (11-106)
components/shared/icons.tsx (1)
  • Icons (165-293)
lib/email/send-roadmap-subscription-email.ts (4)
config/site.ts (1)
  • siteConfig (3-41)
lib/email/resend-client.ts (1)
  • resendClient (5-5)
env.ts (1)
  • env (4-27)
components/app/email-roadmap-subscription.tsx (1)
  • EmailSubscription (25-130)
components/app/marketing-subscription.tsx (2)
schemas/user/roadmap/input.ts (2)
  • subscriptionInputSchema (7-14)
  • SubscriptionInput (21-21)
orpc/hooks/use-users.ts (1)
  • useSubscribeToUpdates (94-101)
lib/content-collections.ts (1)
schemas/utils/article.ts (1)
  • articleSchema (3-10)
app/(marketing)/roadmap/page.tsx (1)
components/app/marketing-subscription.tsx (1)
  • MarketingSubscription (33-157)
orpc/hooks/use-users.ts (1)
orpc/client/utils.ts (1)
  • orpc (5-7)
orpc/routers/user.ts (3)
lib/email/send-roadmap-subscription-email.ts (1)
  • sendSubscriptionEmail (13-40)
schemas/user/roadmap/input.ts (1)
  • subscriptionInputSchema (7-14)
schemas/user/roadmap/output.ts (2)
  • subscriptionOutputSchema (7-10)
  • SubscriptionOutput (18-18)
🔇 Additional comments (25)
styles/globals.css (1)

4-5: LGTM! Typography plugin properly configured.

The Tailwind typography plugin is correctly imported to support MDX-rendered article content. The placement after the @import statements follows the correct order for Tailwind v4 configuration.

prisma.config.ts (1)

15-15: LGTM! Improved string quoting.

Switching to single quotes eliminates the need to escape inner double quotes, improving readability while maintaining functional equivalence.

.gitignore (1)

44-46: LGTM! Properly excluding generated content.

The ignore patterns correctly exclude content-collections generated directories, which aligns with the new content-collections integration and the path alias in tsconfig.json.

components/app/marketing-header-desktop.tsx (1)

29-34: LGTM! Articles link properly integrated.

The new Articles navigation link follows the established pattern and styling of the existing Roadmap link, with appropriate mobile-responsive conditional rendering.

package.json (1)

36-38: LGTM! Dependencies properly added.

The new dependencies for content-collections (@content-collections/*) and Tailwind typography (@tailwindcss/typography) correctly support the MDX article rendering and styling features introduced in this PR.

Also applies to: 76-76

components/app/email-roadmap-subscription.tsx (2)

25-29: LGTM! Type-based dynamic content properly implemented.

The refactoring to support both "roadmap" and "articles" subscription types with derived title, linkUrl, and linkText is well-structured and follows good practices for component generalization.


75-105: LGTM! Conditional content rendering correctly implemented.

The conditional blocks properly differentiate between roadmap-specific content (product milestones, roadmap updates, early access) and articles-specific content (technical insights, business updates, feature announcements) based on the subscription type.

tsconfig.json (1)

23-24: LGTM! Content-collections path alias properly configured.

The new @/content-collections path alias correctly maps to the generated content-collections directory, enabling clean imports of generated types and content. The trailing comma addition maintains consistent formatting.

schemas/user/roadmap/input.ts (2)

7-14: Well-structured subscription input schema.

The schema properly validates email and subscription type with clear error messages. The use of z.enum with a custom errorMap provides a good user experience.


21-22: Type exports follow naming conventions.

The exported types SubscriptionInput and RoadmapSubscribeInput follow the required naming pattern for schema-inferred types.

data/articles/advanced-security-features.mdx (3)

1-8: Front matter follows article schema structure.

The metadata fields (id, title, description, publishedAt, image, href) align with the articleSchema defined for content-collections integration.


18-20: All external links are valid and accessible.

Verification confirms all URLs in the article are reachable with proper HTTP status codes (200 for direct access, 301 for permanent redirects). No broken or unreachable links were detected.


26-28: Counter component is properly available in MDX context.

The Counter component is correctly injected. It's imported in app/(marketing)/articles/[slug]/page.tsx and explicitly passed to the MDXContent components prop, making it available to the advanced-security-features.mdx article.

schemas/user/roadmap/output.ts (1)

7-10: Output schema is correctly structured.

The schema defines a clear success/error response pattern with proper optional handling for the error field.

components/shared/counter.tsx (1)

1-35: Component implementation follows best practices.

The Counter component correctly uses:

  • Client directive for useState
  • Explicit TypeScript interface for props
  • Default prop values
  • Functional component pattern
next.config.ts (2)

23-23: Export wrapped with content-collections plugin.

The Next.js config is now wrapped with the content-collections plugin, enabling MDX content processing during build.


2-6: Content Collections plugin integration correctly configured.

The configPath file exists and the plugin wrapper is properly initialized.

data/articles/getting-started-with-zero-locker.mdx (2)

1-8: Front matter structure is consistent with article schema.

The metadata follows the same pattern as the other article, ensuring consistent processing by content-collections.


27-29: Counter component usage consistent with other articles.

The Counter is used with initialValue={0} step={1}, matching the expected props interface. Ensure it's available in the MDX components context (same verification as the other article applies here).

schemas/utils/index.ts (1)

10-10: LGTM - Barrel export is consistent and safe.

The new export * from "./article" follows the existing pattern for utility schema exports. No circular dependencies are present because the codebase correctly uses concrete file imports (e.g., @/schemas/utils/platform/output) rather than barrel imports from @/schemas/utils within the schemas folder.

app/(marketing)/articles/page.tsx (1)

8-18: Page metadata and composition look solid.

App Router conventions followed; metadata defined and server component usage is correct.

Also applies to: 20-35

app/(marketing)/roadmap/page.tsx (1)

8-8: Subscription component swap looks correct.

Updated import and usage align with the new generalized MarketingSubscription.

Also applies to: 67-70

orpc/routers/user.ts (1)

248-253: Email dispatch update is correct.

Passing type: "roadmap" to the generalized sender is appropriate.

lib/content-collections.ts (1)

7-8: Let me inspect the actual repository files to verify the configuration:

Based on my verification:

The review comment's verification request is appropriate.

Verified: data/articles exists at the project root.

⚠️ Unresolved: The critical question remains whether the content-collections plugin resolves the directory path:

  • Relative to the config file location (lib/), where ../data/articles would be correct
  • Relative to the project root, where it should be data/articles

The non-standard config location (lib/content-collections.ts instead of the documented convention of content-collections.ts at project root) combined with available web search results (which show industry-standard behavior of resolving paths from project root) suggests ../data/articles may be incorrect. However, I cannot definitively confirm the plugin's actual behavior without access to its source code or explicit documentation on path resolution.

app/(marketing)/articles/[slug]/page.tsx (1)

14-16: ****

The current code is correct for Next.js 15. The official Next.js 15 documentation explicitly requires params: Promise<{ slug: string }> with await params before accessing properties—exactly as implemented in the file. The suggested changes to remove the Promise type and await calls would introduce runtime errors and contradict the framework's requirements.

Likely an incorrect or invalid review comment.

Comment on lines +91 to +97
<Image
src={image}
alt={`${title} cover image`}
className="object-cover"
fill
priority
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Image alt text and responsive sizes.

  • Alt text includes the word “image,” which violates the repo guideline. Use a meaningful alt without “image/picture/photo.”
  • With fill, provide a sizes attribute for correct responsive behavior. As per coding guidelines.
 <Image
   src={image}
-  alt={`${title} cover image`}
+  alt={`${title} cover`}
   className="object-cover"
   fill
+  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 90vw, 896px"
   priority
 />
🤖 Prompt for AI Agents
In app/(marketing)/articles/[slug]/page.tsx around lines 91 to 97, the Image
component uses an alt that contains the word "image" and is missing a sizes prop
while using fill; update the alt to a concise descriptive phrase without the
words "image", "photo", or "picture" (for example use `${title} cover` or
another meaningful description) and add a responsive sizes attribute appropriate
for the layout (e.g. a mobile-first sizes string such as "(max-width: 640px)
100vw, 640px" or whatever fits the design breakpoints) so the Image has both an
accessible, guideline-compliant alt and correct responsive sizing when using
fill.

Comment on lines +93 to +101
// Subscribe to updates (roadmap or articles)
export function useSubscribeToUpdates() {
return useMutation({
mutationFn: orpc.users.subscribeToUpdates.call,
onError: (error) => {
console.error("Failed to subscribe to updates:", error)
},
})
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove console.error for production code.

The error handler uses console.error, which violates the coding guideline prohibiting console usage. Consider using a proper error logging service or removing the error handler if errors are handled by the caller.

Apply this diff to remove the console statement:

 export function useSubscribeToUpdates() {
   return useMutation({
     mutationFn: orpc.users.subscribeToUpdates.call,
-    onError: (error) => {
-      console.error("Failed to subscribe to updates:", error)
-    },
   })
 }
🤖 Prompt for AI Agents
In orpc/hooks/use-users.ts around lines 93 to 101, remove the console.error
usage by deleting the onError handler block entirely (i.e., remove the onError:
(error) => { console.error(...) },) so the mutation does not call console; if
you need error reporting instead, replace that block with a call to the
project's central logger or error-reporting service (e.g.,
processLogger.error(error) or sendToSentry) rather than using console.

Comment on lines +320 to +423
// Subscribe to updates (roadmap or articles) - unified endpoint
export const subscribeToUpdates = strictPublicProcedure
.input(subscriptionInputSchema)
.output(subscriptionOutputSchema)
.handler(async ({ input }): Promise<SubscriptionOutput> => {
try {
const { email, type } = input

// Check if email already exists in subscriptions for this type
const existingSubscription =
await database.roadmapSubscription.findUnique({
where: { email },
})

if (existingSubscription) {
return {
success: false,
error: "Email is already subscribed",
}
}

// Add to roadmap subscriptions (we'll use the same table for now)
// TODO: Consider creating a unified subscriptions table with a type field
await database.roadmapSubscription.create({
data: {
email,
},
})

// Send subscription confirmation email
try {
await sendSubscriptionEmail({
to: email,
type,
})
} catch (emailError) {
console.error(`Failed to send ${type} subscription email:`, emailError)
// Don't fail the request if email fails
}

return { success: true }
} catch (error) {
// Re-throw ORPC errors to let ORPC handle them
if (error instanceof ORPCError) {
throw error
}

// Handle Prisma-specific errors
if (error instanceof Prisma.PrismaClientKnownRequestError) {
console.error(
`Database constraint error subscribing to ${input.type}:`,
{
code: error.code,
message: error.message,
meta: error.meta,
}
)

// Handle unique constraint violations
if (error.code === "P2002") {
return {
success: false,
error: "Email is already subscribed",
}
}

// Handle other known Prisma errors
return {
success: false,
error: "Database constraint violation occurred",
}
}

// Handle Prisma client errors (connection issues, etc.)
if (error instanceof Prisma.PrismaClientUnknownRequestError) {
console.error(`Unknown Prisma error subscribing to ${input.type}:`, {
message: error.message,
})
return {
success: false,
error: "Database connection issue occurred",
}
}

// Handle Prisma validation errors
if (error instanceof Prisma.PrismaClientValidationError) {
console.error(`Prisma validation error subscribing to ${input.type}:`, {
message: error.message,
})
return {
success: false,
error: "Invalid data provided",
}
}

// Handle unexpected errors
console.error(`Unexpected error subscribing to ${input.type}:`, error)
return {
success: false,
error: "An unexpected error occurred. Please try again later.",
}
}
})

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Functional bug: unified subscription uses a single-table/email-only key — can’t subscribe to both types.

subscribeToUpdates queries and inserts into roadmapSubscription keyed only by email, so an existing roadmap subscriber can’t subscribe to articles. This defeats the new “type-aware” flow. Fix by storing type alongside email with a composite unique key, and querying by both. Also prefer upsert to avoid TOCTOU. Replace console.error with a structured logger.

-// Subscribe to updates (roadmap or articles) - unified endpoint
-export const subscribeToUpdates = strictPublicProcedure
+// Subscribe to updates (roadmap or articles) - unified endpoint
+export const subscribeToUpdates = strictPublicProcedure
   .input(subscriptionInputSchema)
   .output(subscriptionOutputSchema)
   .handler(async ({ input }): Promise<SubscriptionOutput> => {
     try {
       const { email, type } = input
 
-      // Check if email already exists in subscriptions for this type
-      const existingSubscription =
-        await database.roadmapSubscription.findUnique({
-          where: { email },
-        })
-
-      if (existingSubscription) {
-        return {
-          success: false,
-          error: "Email is already subscribed",
-        }
-      }
-
-      // Add to roadmap subscriptions (we'll use the same table for now)
-      // TODO: Consider creating a unified subscriptions table with a type field
-      await database.roadmapSubscription.create({
-        data: {
-          email,
-        },
-      })
+      // Persist subscription by (email, type)
+      // NOTE: Requires Prisma migration: a `marketingSubscription` model with fields { id, email, type, createdAt }
+      // and a unique index on (email, type).
+      await database.marketingSubscription.upsert({
+        where: { email_type: { email, type } },
+        update: {},
+        create: { email, type },
+      })
 
       // Send subscription confirmation email
       try {
         await sendSubscriptionEmail({
           to: email,
           type,
         })
       } catch (emailError) {
-        console.error(`Failed to send ${type} subscription email:`, emailError)
+        // logger.error(`Failed to send ${type} subscription email`, { error: emailError })
       }
 
       return { success: true }
     } catch (error) {
       // Re-throw ORPC errors to let ORPC handle them
       if (error instanceof ORPCError) {
         throw error
       }
 
       // Handle Prisma-specific errors
       if (error instanceof Prisma.PrismaClientKnownRequestError) {
-        console.error(
-          `Database constraint error subscribing to ${input.type}:`,
-          {
-            code: error.code,
-            message: error.message,
-            meta: error.meta,
-          }
-        )
+        // logger.warn(`Constraint error subscribing to ${input.type}`, { code: error.code, meta: error.meta })
 
         // Handle unique constraint violations
         if (error.code === "P2002") {
           return {
             success: false,
             error: "Email is already subscribed",
           }
         }
 
         // Handle other known Prisma errors
         return {
           success: false,
           error: "Database constraint violation occurred",
         }
       }
 
       // Handle Prisma client errors (connection issues, etc.)
       if (error instanceof Prisma.PrismaClientUnknownRequestError) {
-        console.error(`Unknown Prisma error subscribing to ${input.type}:`, {
-          message: error.message,
-        })
+        // logger.error(`Unknown Prisma error subscribing to ${input.type}`, { message: error.message })
         return {
           success: false,
           error: "Database connection issue occurred",
         }
       }
 
       // Handle Prisma validation errors
       if (error instanceof Prisma.PrismaClientValidationError) {
-        console.error(`Prisma validation error subscribing to ${input.type}:`, {
-          message: error.message,
-        })
+        // logger.error(`Prisma validation error subscribing to ${input.type}`, { message: error.message })
         return {
           success: false,
           error: "Invalid data provided",
         }
       }
 
       // Handle unexpected errors
-      console.error(`Unexpected error subscribing to ${input.type}:`, error)
+      // logger.error(`Unexpected error subscribing to ${input.type}`, { error })
       return {
         success: false,
         error: "An unexpected error occurred. Please try again later.",
       }
     }
   })

Migration note (required):

  • Add a marketingSubscription model with email string, type enum('roadmap','articles'), and @@unique([email, type]).
  • Backfill existing roadmapSubscription rows into the new table with type='roadmap'.

As per coding guidelines (avoid index-based keys, avoid console, map DB constraints deterministically).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In orpc/routers/user.ts around lines 320–423 the unified subscription currently
queries and writes only by email into roadmapSubscription, preventing a user
from subscribing to multiple types; update the handler to store and check both
email and type (use a composite unique key [email,type]) and replace the
create-with-prior-find flow with an upsert (or create with unique composite
lookup) to avoid TOCTOU; also replace console.error calls with the project’s
structured logger (include error details), and update returned error messages to
reflect type-aware checks; separately add a migration to introduce a
marketingSubscription model with fields email:string and
type:enum('roadmap','articles') plus @@unique([email,type]) and backfill
existing roadmapSubscription rows as type='roadmap'.

id: z.number(),
title: z.string(),
description: z.string(),
publishedAt: z.string(),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Validate date format for publishedAt.

The publishedAt field accepts any string without date format validation. This could lead to invalid dates being stored and causing runtime errors during date parsing or formatting operations.

Apply this diff to add date string validation:

-  publishedAt: z.string(),
+  publishedAt: z.string().datetime(),

Or if you need date-only format (YYYY-MM-DD):

-  publishedAt: z.string(),
+  publishedAt: z.string().date(),
📝 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
publishedAt: z.string(),
publishedAt: z.string().datetime(),
🤖 Prompt for AI Agents
In schemas/utils/article.ts around line 7, the publishedAt field currently
allows any string; validate it to enforce a proper date format. Replace the
loose z.string() with either a Zod refinement that parses the string to a Date
and checks validity (e.g., use z.string().refine(s =>
!Number.isNaN(Date.parse(s)), { message: 'Invalid ISO date' })) to accept ISO
datetimes, or use a regex-based check for date-only YYYY-MM-DD (e.g.,
z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Invalid date format')). Ensure the
validation message is descriptive and keep the field as a string type in the
schema.

@FindMalek FindMalek merged commit 67eaf96 into main Oct 25, 2025
2 of 3 checks passed
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
components/app/marketing-subscription.tsx (1)

38-43: Fix form type mismatch with schema.

The form is typed as useForm<SubscriptionInput> and uses subscriptionInputSchema (which requires both email and type fields), but the form only collects the email field. The type comes from props and is never part of the form values. This creates a type safety issue where the form's actual shape doesn't match its declared type.

Consider one of these solutions:

Solution 1 (Recommended): Create an email-only schema and type the form correctly

+// At the top of the file, create a local schema
+const emailOnlySchema = z.object({
+  email: z.string().min(1, "Email is required").email("Invalid email address"),
+})
+
+type EmailFormData = z.infer<typeof emailOnlySchema>
+
-  const form = useForm<SubscriptionInput>({
-    resolver: zodResolver(subscriptionInputSchema),
+  const form = useForm<EmailFormData>({
+    resolver: zodResolver(emailOnlySchema),
     defaultValues: {
       email: "",
     },
   })

Then update the onSubmit signature:

-  async function onSubmit(values: SubscriptionInput) {
+  async function onSubmit(values: EmailFormData) {
     subscribeToUpdatesMutation.mutate(

Solution 2: Add type as a hidden form field

   const form = useForm<SubscriptionInput>({
     resolver: zodResolver(subscriptionInputSchema),
     defaultValues: {
       email: "",
+      type,
     },
   })

And add a hidden input in the form JSX after the email field, though this is less clean since the type is already available from props.

♻️ Duplicate comments (2)
app/(marketing)/articles/[slug]/page.tsx (1)

90-96: Image alt text and responsive sizes (previously flagged).

This issue was already identified in a previous review:

  • Alt text includes the word "image," which violates the coding guideline
  • With fill, provide a sizes attribute for correct responsive behavior

As per coding guidelines:

 <Image
   src={image}
-  alt={`${title} cover image`}
+  alt={`${title} cover`}
   className="object-cover"
   fill
+  sizes="(max-width: 640px) 100vw, (max-width: 1024px) 90vw, 896px"
   priority
 />
components/app/marketing-subscription.tsx (1)

81-86: Type-safe error handling needed.

The error parameter is not typed and the code assumes error.message exists without type checking. This is the same issue flagged in a previous review.

Apply the previously suggested fix:

-        onError: (error) => {
+        onError: (error: unknown) => {
+          const message =
+            error instanceof Error
+              ? error.message
+              : "Something went wrong. Please try again."
           toast.error("Subscription failed", {
-            description:
-              error.message || "Something went wrong. Please try again.",
+            description: message,
           })
         },

As per coding guidelines.

🧹 Nitpick comments (2)
app/(marketing)/articles/[slug]/page.tsx (2)

17-21: Consider more robust slug extraction.

Using split("/").pop() assumes the href always follows a specific format and could be fragile. If the article schema includes a slug property, use it directly for clarity and reliability.

If a slug property exists:

 export async function generateStaticParams() {
-  return allArticles.map((article) => ({
-    slug: article.href.split("/").pop(),
-  }))
+  return allArticles.map((article) => ({
+    slug: article.slug,
+  }))
 }

23-42: Remove unnecessary template literal in title.

The template literal on line 34 is redundant since article.title is already a string.

   return {
-    title: `${article.title}`,
+    title: article.title,
     description: article.description,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 383f168 and 18c40b1.

📒 Files selected for processing (3)
  • app/(marketing)/articles/[slug]/page.tsx (1 hunks)
  • components/app/marketing-subscription.tsx (5 hunks)
  • next.config.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,jsx,ts,tsx}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't write functions that exceed a given Cognitive Complexity score
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Use for...of statements instead of Array.forEach
Don't create classes that only have static members
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Don't use ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use arrow functions instead of function expressions
Use Date.now() to get milliseconds since the Unix Epoch
Use .flatMap() instead of map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work
Use concise optional chaining instead of chained logical expressions
Use regular expression literals instead of the RegExp constructor when possible
Don't use number literal object member names that aren't base 10 or use underscore separators
Remove redundant terms from logical expressions
Use while loops instead of for loops when you don't need initializer and update expressions
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min and Math.max to clamp value...

Files:

  • next.config.ts
  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Make sure switch-case statements are exhaustive
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibility modifiers on class properties and methods
Put default function parameters and optional function parameters last
Initialize each enum member value explicitly
Use export type for types
Use import type for types
Make sure all enum members are literal values
Use function types instead of object types with call signatures
Don't use void type outside of generic or return types
Don't use TypeScript const enum
Don't declare empty interfaces
Don't let variables evolve into any type through reassignments
Don't use the any type
Don't misuse the non-null assertion operator (!) in TypeScript files
Don't use implicit any type on variable declarations
Don't merge interfaces and classes unsafely
Don't use overload signatures that aren't next to each other
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces

Use type-only imports when possible

**/*.{ts,tsx}: Use camelCase for function and method names
Use UPPER_SNAKE_CASE for constants
Use camelCase for variables
Use PascalCase for classes and types
Use absolute imports for top-level modules (e.g., @/components, @/lib, @/entities) instead of long relative paths
Follow import order: React imports, third-party, internal abso...

Files:

  • next.config.ts
  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,ts}: Don't use __dirname and __filename in the global scope
Use with { type: "json" } for JSON module imports
Don't access namespace imports dynamically
Use node:assert/strict over node:assert
Use the node: protocol for Node.js builtin modules

Files:

  • next.config.ts
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{html,jsx,tsx}: Don't use the accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for the tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use an explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a title attribute for iframe elements
Include caption tracks for audio and video elements
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Use correct ISO language/country codes for the lang attribute
Don't use variables that haven't been declared in the document
Make sure void (self-closing) elements don't have children
Don't use event handlers on non-interactiv...

Files:

  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{jsx,tsx}: Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Use semantic elements instead of role attributes in JSX
Don't use unnecessary fragments
Don't pass children as props
Don't use the return value of React.render
Make sure all dependencies are correctly specified in React hooks
Make sure all React hooks are called from the top level of component functions
Don't forget key props in iterators and collection literals
Don't define React components inside other components
Don't assign to React component props
Don't use dangerous JSX props
Don't use both children and dangerouslySetInnerHTML props on the same element
Use <>...</> instead of ...
Don't add extra closing tags for components without children
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Watch out for possible "wrong" semicolons inside JSX elements
Don't put multiple components in one file; each file must have one component

Files:

  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
**/*.{html,jsx,tsx,css}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Don't use TailwindCSS class names 'h-NUMBER w-NUMBER'; instead use 'size-NUMBR'

Files:

  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
components/app/**

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Place application-specific React components under components/app

Files:

  • components/app/marketing-subscription.tsx
components/app/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Use naming pattern {feature}-{purpose}-{type}.tsx for app components

Files:

  • components/app/marketing-subscription.tsx
components/app/marketing-*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Marketing components in components/app must be prefixed with marketing-

Place marketing components under components/app/ with filenames starting marketing-*.tsx

Files:

  • components/app/marketing-subscription.tsx
components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.{ts,tsx}: Always define a TypeScript interface for component props
Follow established naming patterns consistently across files
Use strict, explicit TypeScript types for safety

Files:

  • components/app/marketing-subscription.tsx
components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.tsx: Add 'use client' directive for components that use client-side hooks/effects
Use cn() for conditional Tailwind classes
Use semantic Tailwind colors (e.g., text-foreground, bg-background) and consistent spacing (space-y-4, gap-4)
Use responsive Tailwind prefixes (md:, lg:, xl:) for adaptive layouts
Define component variants with cva and defaultVariants when variants are needed
Prefer useState for local state; use useReducer for complex state
Use TanStack Query hooks from /orpc/hooks for server state; prefer server-side data fetching when possible
Implement proper ARIA attributes and keyboard navigation for accessibility
Use React.memo for expensive components
Use useMemo and useCallback for expensive computations and stable callbacks
Lazy-load heavy components via dynamic import
Import only needed icons from lucide-react (no wildcard imports)
Wrap components that might fail in Error Boundaries
Provide loading and error states for async UI
Add JSDoc comments for complex components

components/**/*.tsx: Components must be named in kebab-case.tsx (e.g., dashboard-credential-form.tsx)
Use React Hook Form for form management in UI forms
Use React Hook Form error handling, display validation errors, and handle submission errors gracefully in forms

Files:

  • components/app/marketing-subscription.tsx
components/**/[a-z0-9-]*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Name React components in kebab-case with .tsx under the components directory (e.g., dashboard-credential-form.tsx)

Files:

  • components/app/marketing-subscription.tsx
components/{app,layout,shared,ui}/**

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Organize React components under components/app, components/layout, components/shared, and components/ui

Files:

  • components/app/marketing-subscription.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-patterns.mdc)

**/*.tsx: Use Component Composition Pattern: build complex UI by composing smaller, reusable components (e.g., Form, FormField, FormItem)
Handle form errors with React Hook Form and Zod resolver for client-side validation
Avoid direct database access in React components; use hooks/services instead

**/*.tsx: Use PascalCase for React component names
Use functional components only, typed with explicit props interfaces
Optimize components using React.memo for expensive components, useMemo for heavy computations, and useCallback for stable handlers

**/*.tsx: Never use useState for form state; use React Hook Form (useForm) instead
Always use Zod schemas with zodResolver for form validation
Always use shadcn Form components: Form, FormField, FormItem, FormLabel, FormControl, FormMessage
Always use Icons.spinner with animate-spin for loading states
Never use text like "Deleting..." or "Loading..." for loading states
Use size-4 instead of h-4 w-4 for spinner sizing (Tailwind)
Disable buttons during loading: disabled={isLoading}
Show spinner + text inside buttons when loading
Use Button variant="destructive" for delete actions
Add disabled opacity class to buttons: className="disabled:opacity-50"
Avoid manual form validation; rely on Zod + resolver integration

Files:

  • components/app/marketing-subscription.tsx
  • app/(marketing)/articles/[slug]/page.tsx
app/**/page.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

Page files must be named page.tsx and define the page component and metadata

Page components in the Next.js App Router must be named page.tsx

Pages must be named page.tsx

Files:

  • app/(marketing)/articles/[slug]/page.tsx
app/**/[(]*[)]/**

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

Use parentheses-named folders (group) to create route groups that don’t affect the URL

Files:

  • app/(marketing)/articles/[slug]/page.tsx
app/**/{page,layout}.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

app/**/{page,layout}.tsx: Export a Metadata object as export const metadata in pages and layouts
Perform server-side data fetching in pages and layouts where possible

Files:

  • app/(marketing)/articles/[slug]/page.tsx
app/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/app-folder.mdc)

app/**/*.tsx: Prefer Server Components by default in the app directory
Add "use client" only when necessary for Client Components

Files:

  • app/(marketing)/articles/[slug]/page.tsx
app/(marketing)/**

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Place marketing and landing content within app/(marketing)/

Files:

  • app/(marketing)/articles/[slug]/page.tsx
🧠 Learnings (1)
📚 Learning: 2025-09-30T22:00:32.336Z
Learnt from: CR
PR: FindMalek/zero-locker#0
File: .cursor/rules/general.mdc:0-0
Timestamp: 2025-09-30T22:00:32.336Z
Learning: Applies to **/*.{html,jsx,tsx} : Give all elements requiring alt text meaningful information for screen readers

Applied to files:

  • app/(marketing)/articles/[slug]/page.tsx
🧬 Code graph analysis (2)
components/app/marketing-subscription.tsx (2)
orpc/hooks/use-users.ts (1)
  • useSubscribeToUpdates (94-101)
schemas/user/roadmap/input.ts (2)
  • SubscriptionInput (21-21)
  • subscriptionInputSchema (7-14)
app/(marketing)/articles/[slug]/page.tsx (3)
components/ui/link.tsx (1)
  • Link (9-35)
components/shared/icons.tsx (1)
  • Icons (165-293)
lib/date-utils.ts (1)
  • DateFormatter (11-106)
🔇 Additional comments (6)
next.config.ts (2)

2-6: LGTM! Content Collections plugin properly configured.

The plugin integration follows the standard pattern and correctly references the configuration file at ./lib/content-collections.ts.


19-31: LGTM! Image patterns and plugin export configured correctly.

The new remote patterns enable images from the specified domains, and the config is properly wrapped with the content-collections plugin.

app/(marketing)/articles/[slug]/page.tsx (3)

1-15: LGTM! Imports and type definitions are correct.

The interface correctly types params as a Promise, which aligns with Next.js 15's async params requirement.


44-85: LGTM! Article lookup and header rendering are well-structured.

The component correctly handles async params, validates the article exists, and provides clear navigation. The article lookup duplication between generateMetadata and the component is standard in Next.js.


101-110: LGTM! MDX rendering configured correctly.

The prose styling and CustomLink component override ensure proper article rendering and link behavior.

components/app/marketing-subscription.tsx (1)

99-151: LGTM: Clean form implementation following guidelines.

The JSX structure correctly follows all coding guidelines:

  • Uses shadcn Form components properly
  • Button includes type="submit" attribute
  • Loading state uses Icons.spinner with size-4 and animate-spin
  • Buttons are disabled during pending state
  • Shows spinner with text (not "Loading..." text)
  • Proper accessibility with semantic HTML and form validation

@coderabbitai coderabbitai bot mentioned this pull request Oct 25, 2025
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.

1 participant