RuleStack

Configs

Stacks

Compare

Diff

RuleStack

Configs

Stacks

Compare

Diff

Read API

RuleStack

Configs

Stacks

Compare

Diff

Read API

Diff/sportiz91-vibe-template-cursor-rules-coding-standards ↔ sportiz91-vibe-template-cursorrules

Comparison

A · Cursor rules · sportiz91/vibe-templateB · .cursorrules · sportiz91/vibe-template
What each file covers, counted
DimensionSharedOnly in AOnly in BOverlap
Sections00250%
Commands000—
Section tags12414%

What each file covers

Sections

0 shared · 0 only in A · 25 only in B
  • + Project Instructions
  • + Overview
  • + Tech Stack
  • + Project Structure
  • + Rules
  • + General Rules
  • + Frontend Rules
  • + Backend Rules
  • + Auth Rules
  • + Payments Rules
  • + Analytics Rules
  • + Storage Rules
  • + Organization
  • + Buckets
  • + File Structure
  • + Actions
  • + File Handling
  • + Upload Rules
  • + Download Rules
  • + Delete Rules
  • + Security
  • + Bucket Policies
  • + Access Control
  • + Error Handling
  • + Optimization

Commands

neither file has any

Section tags

1 shared · 2 only in A · 4 only in B
  • − types
  • − ui
  • + architecture
  • + security
  • + do-not
  • + agent-behaviour
  •   code-style

Line diff

+618 added−313 removed42 unchanged6.4% identical
sportiz91/vibe-template · .cursor/rules/coding-standards.mdc
@@ −1 @@
1---
2description:
3globs:
4alwaysApply: false
5---
6Rule Name: coding-standards
7Description:
8This rule defines the coding standards and formatting guidelines that must be followed for all code changes in this project.
9 
10<coding_format>
11 
12A. Syntax & Structure
13- File names must be dash-case (word-cloud.service.ts) unless an existing pattern differs.
14- Group imports: node/standard → npm packages → internal paths. No unused imports.
15- Use arrow functions everywhere except inside class bodies, where concise method syntax is allowed.
16- Prefer early returns; nested if/else blocks deeper than two levels are disallowed.
17- Early returns must use block format with braces (e.g., `if (!value) { return }`) for readability.
18- Extract function call results as scope variables before using in conditions (e.g., `const trimmedText = text.trim(); if (!trimmedText) {...}` instead of `if (!text.trim()) {...}`).
19- Use async/await—never chain .then().
20- No .forEach for side effects; use for (const x of arr) instead.
21- Array combinators (map, reduce, filter) are allowed only when you return their result.
22- Identifiers must be English.
23- No commented code allowed.
24 
25B. Functional-Programming Rules
26- Each function must:
27 * Be ≤ 50 lines (preferably; extract helpers if longer).
28 * Take ≤ 4 parameters (optional ones last).
29 * Have a single responsibility.
30 * Be pure unless it is an intentional I/O wrapper (e.g. DB write); such wrappers must be ≤ 15 lines.
31 * Name functions with camelCase imperative verbs (calculateTotals, getUserById).
32 
33C. Type Safety & Error Handling
34- Explicitly type all function parameters, return types, and exported constants.
35- Type all local variables inside a function.
36- **Special attention for async operations**: Variables from awaited functions (e.g., `const { userId } = await auth()`) must be explicitly typed, especially in Next.js components where auth results should use proper domain types.
37- No any; if an external library forces it, wrap and narrow.
38- Error handling in catch blocks:
39 - If the error variable is not used, use `catch {}` (no parameter).
40 - If the error is used, type it as `unknown` and handle it safely within the catch block.
41 
42D. React Component Standards
43 
44- Always define props with interfaces, never inline types
45- Place interfaces directly above component definitions
46- Use const arrow functions for component definitions
47- Use implicit return syntax when components only return JSX (no logic before return)
48- Export components using export default pattern (required for Next.js pages/layouts)
49- Handler functions inside components must be ≤ 20 lines and have a single, clear responsibility. Extract helper functions for complex logic.
50 
51 **Wrong (~50 lines in one handler):**
52 ```tsx
53 const handleFormSubmit = async (): Promise<void> => {
54 const trimmedName: string = formData.name.trim()
55 const trimmedEmail: string = formData.email.trim()
56 const trimmedMessage: string = formData.message.trim()
57
58 if (!trimmedName) {
59 setErrors({ ...errors, name: "Name is required" })
60 toast({ title: "Error", description: "Name is required", variant: "destructive" })
61 return
62 }
63
64 if (!trimmedEmail || !trimmedEmail.includes("@")) {
65 setErrors({ ...errors, email: "Valid email is required" })
66 toast({ title: "Error", description: "Valid email is required", variant: "destructive" })
67 return
68 }
69
70 if (!trimmedMessage || trimmedMessage.length < 10) {
71 setErrors({ ...errors, message: "Message must be at least 10 characters" })
72 toast({ title: "Error", description: "Message too short", variant: "destructive" })
73 return
74 }
75
76 setIsSubmitting(true)
77 setErrors({})
78
79 try {
80 const payload: FormPayload = {
81 name: trimmedName,
82 email: trimmedEmail,
83 message: trimmedMessage,
84 timestamp: new Date().toISOString()
85 }
86
87 const response: Response = await fetch("/api/contact", {
88 method: "POST",
89 headers: { "Content-Type": "application/json" },
90 body: JSON.stringify(payload)
91 })
92
93 if (!response.ok) {
94 throw new Error("Failed to submit")
95 }
96
97 const result: SubmissionResult = await response.json()
98
99 setFormData({ name: "", email: "", message: "" })
100 setSubmissionCount(prev => prev + 1)
101
102 toast({ title: "Success", description: "Message sent successfully!" })
103
104 if (onSuccess) {
105 onSuccess(result)
106 }
107 } catch (error: unknown) {
108 const errorMessage: string = error instanceof Error ? error.message : "Unknown error"
109 console.error("Submission error:", errorMessage)
110 setErrors({ submit: "Failed to send message" })
111 toast({ title: "Error", description: "Failed to send message", variant: "destructive" })
112 } finally {
113 setIsSubmitting(false)
114 }
115 }
116 ```
117 
118 **Good (broken into focused helpers ≤ 20 lines each):**
119 ```tsx
120 const validateForm = (): boolean => {
121 const trimmedName: string = formData.name.trim()
122 const trimmedEmail: string = formData.email.trim()
123 const trimmedMessage: string = formData.message.trim()
124
125 if (!trimmedName) {
126 setErrors({ ...errors, name: "Name is required" })
127 toast({ title: "Error", description: "Name is required", variant: "destructive" })
128 return false
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129 }
130
131 if (!trimmedEmail || !trimmedEmail.includes("@")) {
132 setErrors({ ...errors, email: "Valid email is required" })
133 toast({ title: "Error", description: "Valid email is required", variant: "destructive" })
134 return false
135 }
136
137 if (!trimmedMessage || trimmedMessage.length < 10) {
138 setErrors({ ...errors, message: "Message must be at least 10 characters" })
139 toast({ title: "Error", description: "Message too short", variant: "destructive" })
140 return false
141 }
142
143 return true
144 }
145
146 const submitForm = async (): Promise<SubmissionResult> => {
147 const payload: FormPayload = {
148 name: formData.name.trim(),
149 email: formData.email.trim(),
150 message: formData.message.trim(),
151 timestamp: new Date().toISOString()
152 }
153
154 const response: Response = await fetch("/api/contact", {
155 method: "POST",
156 headers: { "Content-Type": "application/json" },
157 body: JSON.stringify(payload)
158 })
159
160 if (!response.ok) {
161 throw new Error("Failed to submit")
 
162 }
163
164 return response.json()
 
165 }
166
167 const handleSuccess = (result: SubmissionResult): void => {
168 setFormData({ name: "", email: "", message: "" })
169 setSubmissionCount((prev: number) => prev + 1)
170 toast({ title: "Success", description: "Message sent successfully!" })
171
172 if (onSuccess) {
173 onSuccess(result)
 
 
 
 
 
 
 
 
 
174 }
 
 
 
175 }
176
177 const handleError = (error: unknown): void => {
178 const errorMessage: string = error instanceof Error ? error.message : "Unknown error"
179 console.error("Submission error:", errorMessage)
180 setErrors({ submit: "Failed to send message" })
181 toast({ title: "Error", description: "Failed to send message", variant: "destructive" })
182 }
183
184 const handleFormSubmit = async (): Promise<void> => {
185 const isValid: boolean = validateForm()
186
187 if (!isValid) {
188 return
189 }
190
191 setIsSubmitting(true)
192 setErrors({})
193
194 try {
195 const result: SubmissionResult = await submitForm()
196 handleSuccess(result)
197 } catch (error: unknown) {
198 handleError(error)
199 } finally {
200 setIsSubmitting(false)
201 }
202 }
203 ```
204- Example with implicit return:
205 
206 ```tsx
207 interface MyComponentProps {
208 title: string
209 children: React.ReactNode
210 }
211 
212 const MyComponent = ({ title, children }: MyComponentProps) => (
213 <div>
214 {title}
215 {children}
216 </div>
217 )
218 
219 export default MyComponent
220 ```
221 
222- Example with explicit return (when logic is present):
223 
224 ```tsx
225 interface MyComponentProps {
226 title: string
227 children: React.ReactNode
228 }
229 
230 const MyComponent = ({ title, children }: MyComponentProps) => {
231 const processedTitle = title.toUpperCase()
232
233 return (
234 <div>
235 {processedTitle}
236 {children}
237 </div>
238 )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239 }
 
 
240 
241 export default MyComponent
242 ```
243 
244- Normal components that are not Next.js pages/layouts should be exported
245 using export const pattern
246- Example with implicit return:
247 
248 ```tsx
249 interface NotAPageOrLayoutComponentProps {
250 title: string
251 children: React.ReactNode
 
 
 
 
 
 
 
 
 
 
 
252 }
253 
254 export const NotAPageOrLayoutComponent = ({
255 title,
256 children
257 }: NotAPageOrLayoutComponentProps) => (
258 <div>
259 {title}
260 {children}
261 </div>
262 )
263 
264E. Component Granularity & Organization
265- Break down large components into smaller, focused components for better maintainability.
266- When a component contains multiple logical sections (e.g., Card with CardHeader + CardContent), extract each section into separate components.
267- Create dedicated folders for related component groups:
268 * Use kebab-case folder names matching the main component concept
269 * Place related sub-components within the same folder using kebab-case file names
270 * Example structure: `component-name/component-name-header.tsx`, `component-name/component-name-content.tsx`
271- Each sub-component should have a single, clear responsibility.
272- Maintain the parent component as a composition wrapper that orchestrates child components.
273- Follow this pattern when refactoring existing components or creating new feature components.
274 
275F. Advanced Component Architecture Patterns
276 
277F.1. Pure Functions and Constants Organization
278- **Pure functions** (no side effects, deterministic output) must be extracted outside components:
279 * Place above the component definition
280 * Examples: `getGreeting()`, `getMembershipBadgeColor()`, `formatDate()`
281- **Constants and static data** must be moved outside components:
282 * Place after imports and interfaces, before pure functions
283 * Use SCREAMING_SNAKE_CASE for naming (e.g., `TEMPLATE_FEATURES`, `TECH_STACK`)
284 * **Always explicitly type constants** with appropriate type annotations
285 * Examples: `const API_URL: string = "..."`, `const MAX_RETRIES: number = 3`
286 * Use `as const` for immutable values when type inference is sufficient
287 * Group related constants together
288 
289F.1.1. Custom Hooks Organization
290- **Custom hooks** must be extracted to separate files in the `/hooks/` directory:
291 * Use kebab-case file naming: `use-scroll-detection.ts`, `use-local-storage.ts`
292 * Start hook names with `use` prefix following React conventions
293 * Place hooks in `/hooks/` folder at project root level
294 * Export hooks using named exports: `export const useScrollDetection = () => {}`
295 * **Always explicitly type hook return values** and parameters
296 * Examples: `useScrollDetection(): boolean`, `useLocalStorage<T>(key: string): [T, (value: T) => void]`
297 * Group related hooks in the same file only if they're tightly coupled
298 
299F.1.2. Whitespace and Formatting Rules
300- **Component variable organization**: Maintain consistent whitespace between different types of declarations:
301 * Add a blank line between React state declarations and custom hook calls
302 * Add a blank line between custom hook calls and other variable declarations
303 * Example:
304 ```tsx
305 const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
306 const [isVisible, setIsVisible] = useState<boolean>(true)
307
308 const isScrolled: boolean = useScrollDetection()
309 const userData: UserData = useUserData()
310
311 const processedData: ProcessedData = processUserData(userData)
312 ```
313 
314F.2. Nested Component Structure for Complex Components
315When a component has multiple distinct sections, create nested folder structure:
316 
 
 
 
 
 
 
 
 
 
 
 
 
317```
318dashboard-welcome/
319├── dashboard-welcome.tsx // Main orchestrator component
320├── greeting.tsx // Self-contained greeting section
321├── whats-included/ // Folder for multi-part section
322│ ├── whats-included.tsx // Section orchestrator
323│ ├── whats-included-title.tsx // Title sub-component
324│ └── whats-included-features.tsx // Features list sub-component
325├── core-technologies/ // Folder for multi-part section
326│ ├── core-technologies.tsx // Section orchestrator
327│ ├── core-technologies-title.tsx // Title sub-component
328│ └── core-technologies-list.tsx // Tech list sub-component
329└── get-started/ // Folder for multi-part section
330 ├── get-started.tsx // Section orchestrator
331 ├── get-started-title.tsx // Title sub-component
332 ├── get-started-features.tsx // Features grid sub-component
333 └── get-started-feature-2.tsx // Individual feature card
334```
335 
336F.3. Component Organization Rules
3371. **Main orchestrator**: Composition only, minimal logic, imports and renders sub-components
3382. **Section orchestrators**: Handle section-specific logic, render related sub-components
3393. **Leaf components**: Single responsibility, pure presentation, accept props only
3404. **Shared constants**: Extract to file level, use proper naming conventions
3415. **Pure functions**: Extract above component definitions, properly typed
3426. **File structure**: Mirror logical component hierarchy in folder structure
343 
344</coding_format>
 
 
 
 
345 
346<usage_guidelines>
347 
3481. Apply these standards to all new code and when refactoring existing code.
3492. When making any code changes, ensure they conform to these guidelines.
3503. If existing code doesn't follow these standards, update it to comply when modifying those files.
3514. Use these standards as a checklist when reviewing code changes.
3525. Prefer extracting helper functions over writing long, complex functions.
3536. Always prioritize code readability and maintainability.
354 
355</usage_guidelines>
 
 
 
 
 
sportiz91/vibe-template · .cursorrules
@@ +1 @@
1# Project Instructions
 
 
 
 
 
 
 
2 
3Use specification and guidelines as you build the app.
4 
5Write the complete code for every step. Do not get lazy.
 
 
 
 
 
 
 
 
 
 
 
6 
7Your goal is to completely finish whatever I ask for.
 
 
 
 
 
 
8 
9You will see <ai_context> tags in the code. These are context tags that you should use to help you understand the codebase.
 
 
 
 
 
 
 
10 
11## Overview
12 
13This is a web app template.
 
 
 
 
 
14 
15## Tech Stack
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16 
17- Frontend: Next.js, Tailwind, Shadcn, Framer Motion
18- Backend: Postgres, Supabase, Drizzle ORM, Server Actions
19- Auth: Clerk
20- Payments: Stripe
21- Analytics: PostHog
22- Deployment: Vercel
23 
24## Project Structure
25 
26- `actions` - Server actions
27 - `db` - Database related actions
28 - Other actions
29- `app` - Next.js app router
30 - `api` - API routes
31 - `route` - An example route
32 - `_components` - One-off components for the route
33 - `layout.tsx` - Layout for the route
34 - `page.tsx` - Page for the route
35- `components` - Shared components
36 - `ui` - UI components
37 - `utilities` - Utility components
38- `db` - Database
39 - `schema` - Database schemas
40- `lib` - Library code
41 - `hooks` - Custom hooks
42 - `services` - Business logic services
43- `prompts` - Prompt files
44- `public` - Static assets
45- `types` - Type definitions
46 
47## Rules
48 
49Follow these rules when building the app.
50 
51### General Rules
52 
53- Use `@` to import anything from the app unless otherwise specified
54- Use kebab case for all files and folders unless otherwise specified
55- Don't update shadcn components unless otherwise specified
56 
57#### Env Rules
58 
59- If you update environment variables, update the `.env.example` file
60- All environment variables should go in `.env.local`
61- Do not expose environment variables to the frontend
62- Use `NEXT_PUBLIC_` prefix for environment variables that need to be accessed from the frontend
63- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
64 
65#### Type Rules
66 
67Follow these rules when working with types.
68 
69- When importing types, use `@/types`
70- Name files like `example-types.ts`
71- All types should go in `types`
72- Make sure to export the types in `types/index.ts`
73- Prefer interfaces over type aliases
74- If referring to db types, use `@/db/schema` such as `SelectTodo` from `todos-schema.ts`
75 
76An example of a type:
77 
78`types/actions-types.ts`
79 
80```ts
81export type ActionState<T> =
82 | { isSuccess: true; message: string; data: T }
83 | { isSuccess: false; message: string; data?: never }
84```
85 
86And exporting it:
87 
88`types/index.ts`
89 
90```ts
91export * from "./actions-types"
92```
93 
94### Frontend Rules
95 
96Follow these rules when working on the frontend.
97 
98It uses Next.js, Tailwind, Shadcn, and Framer Motion.
99 
100#### General Rules
101 
102- Use `lucide-react` for icons
103- useSidebar must be used within a SidebarProvider
104 
105#### Components
106 
107- Use divs instead of other html tags unless otherwise specified
108- Separate the main parts of a component's html with an extra blank line for visual spacing
109- Always tag a component with either `use server` or `use client` at the top, including layouts and pages
110 
111##### Organization
112 
113- All components be named using kebab case like `example-component.tsx` unless otherwise specified
114- Put components in `/_components` in the route if one-off components
115- Put components in `/components` from the root if shared components
116 
117##### Data Fetching
118 
119- Fetch data in server components and pass the data down as props to client components.
120- Use server actions from `/actions` to mutate data.
121 
122##### Server Components
123 
124- Use `"use server"` at the top of the file.
125- Implement Suspense for asynchronous data fetching to show loading states while data is being fetched.
126- If no asynchronous logic is required for a given server component, you do not need to wrap the component in `<Suspense>`. You can simply return the final UI directly since there is no async boundary needed.
127- If asynchronous fetching is required, you can use a `<Suspense>` boundary and a fallback to indicate a loading state while data is loading.
128- Server components cannot be imported into client components. If you want to use a server component in a client component, you must pass the as props using the "children" prop
129- params in server pages should be awaited such as `const { courseId } = await params` where the type is `params: Promise<{ courseId: string }>`
130 
131Example of a server layout:
132 
133```tsx
134"use server"
135 
136export default async function ExampleServerLayout({
137 children
138}: {
139 children: React.ReactNode
140}) {
141 return children
142}
143```
144 
145Example of a server page (with async logic):
146 
147```tsx
148"use server"
149 
150import { Suspense } from "react"
151import { SomeAction } from "@/actions/some-actions"
152import SomeComponent from "./_components/some-component"
153import SomeSkeleton from "./_components/some-skeleton"
154 
155export default async function ExampleServerPage() {
156 return (
157 <Suspense fallback={<SomeSkeleton className="some-class" />}>
158 <SomeComponentFetcher />
159 </Suspense>
160 )
161}
162 
163async function SomeComponentFetcher() {
164 const { data } = await SomeAction()
165 return <SomeComponent className="some-class" initialData={data || []} />
166}
167```
168 
169Example of a server page (no async logic required):
170 
171```tsx
172"use server"
173 
174import SomeClientComponent from "./_components/some-client-component"
175 
176// In this case, no asynchronous work is being done, so no Suspense or fallback is required.
177export default async function ExampleServerPage() {
178 return <SomeClientComponent initialData={[]} />
179}
180```
181 
182Example of a server component:
183 
184```tsx
185"use server"
186 
187interface ExampleServerComponentProps {
188 // Your props here
189}
190 
191export async function ExampleServerComponent({
192 props
193}: ExampleServerComponentProps) {
194 // Your code here
195}
196```
197 
198##### Client Components
199 
200- Use `"use client"` at the top of the file
201- Client components can safely rely on props passed down from server components, or handle UI interactions without needing <Suspense> if there's no async logic.
202- Never use server actions in client components. If you need to create a new server action, create it in `/actions`
203 
204Example of a client page:
205 
206```tsx
207"use client"
208 
209export default function ExampleClientPage() {
210 // Your code here
211}
212```
213 
214Example of a client component:
215 
216```tsx
217"use client"
218 
219interface ExampleClientComponentProps {
220 initialData: any[]
221}
222 
223export default function ExampleClientComponent({
224 initialData
225}: ExampleClientComponentProps) {
226 // Client-side logic here
227 return <div>{initialData.length} items</div>
228}
229```
230 
231### Backend Rules
232 
233Follow these rules when working on the backend.
234 
235It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
236 
237#### General Rules
238 
239- Never generate migrations. You do not have to do anything in the `db/migrations` folder inluding migrations and metadata. Ignore it.
240 
241#### Organization
242 
243#### Schemas
244 
245- When importing schemas, use `@/db/schema`
246- Name files like `example-schema.ts`
247- All schemas should go in `db/schema`
248- Make sure to export the schema in `db/schema/index.ts`
249- Make sure to add the schema to the `schema` object in `db/db.ts`
250- If using a userId, always use `userId: text("user_id").notNull()`
251- Always include createdAt and updatedAt columns in all tables
252- Make sure to cascade delete when necessary
253- Use enums for columns that have a limited set of possible values such as:
254 
255```ts
256import { pgEnum } from "drizzle-orm/pg-core"
257 
258export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
259 "membership",
260 MEMBERSHIP_VALUES
261)
262 
263membership: MEMBERSHIP("membership").notNull().default("free")
264```
265 
266Example of a schema:
267 
268`db/schema/todos-schema.ts`
269 
270```ts
271import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
272 
273export const todosTable = pgTable("todos", {
274 id: uuid("id").defaultRandom().primaryKey(),
275 userId: text("user_id").notNull(),
276 content: text("content").notNull(),
277 completed: boolean("completed").default(false).notNull(),
278 createdAt: timestamp("created_at").defaultNow().notNull(),
279 updatedAt: timestamp("updated_at")
280 .defaultNow()
281 .notNull()
282 .$onUpdate(() => new Date())
283})
284 
285export type InsertTodo = typeof todosTable.$inferInsert
286export type SelectTodo = typeof todosTable.$inferSelect
287```
288 
289And exporting it:
290 
291`db/schema/index.ts`
292 
293```ts
294export * from "./todos-schema"
295```
296 
297And adding it to the schema in `db/db.ts`:
298 
299`db/db.ts`
300 
301```ts
302import { todosTable } from "@/db/schema"
303 
304const schema = {
305 todos: todosTable
306}
307```
308 
309And a more complex schema:
310 
311```ts
312import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
313 
314export const chatsTable = pgTable("chats", {
315 id: uuid("id").defaultRandom().primaryKey(),
316 userId: text("user_id").notNull(),
317 name: text("name").notNull(),
318 createdAt: timestamp("created_at").defaultNow().notNull(),
319 updatedAt: timestamp("updated_at")
320 .defaultNow()
321 .notNull()
322 .$onUpdate(() => new Date())
323})
324 
325export type InsertChat = typeof chatsTable.$inferInsert
326export type SelectChat = typeof chatsTable.$inferSelect
327```
328 
329```ts
330import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
331import { chatsTable } from "./chats-schema"
332 
333export const roleEnum = pgEnum("role", ["assistant", "user"])
334 
335export const messagesTable = pgTable("messages", {
336 id: uuid("id").defaultRandom().primaryKey(),
337 chatId: uuid("chat_id")
338 .references(() => chatsTable.id, { onDelete: "cascade" })
339 .notNull(),
340 content: text("content").notNull(),
341 role: roleEnum("role").notNull(),
342 createdAt: timestamp("created_at").defaultNow().notNull(),
343 updatedAt: timestamp("updated_at")
344 .defaultNow()
345 .notNull()
346 .$onUpdate(() => new Date())
347})
348 
349export type InsertMessage = typeof messagesTable.$inferInsert
350export type SelectMessage = typeof messagesTable.$inferSelect
351```
352 
353And exporting it:
354 
355`db/schema/index.ts`
356 
357```ts
358export * from "./chats-schema"
359export * from "./messages-schema"
360```
361 
362And adding it to the schema in `db/db.ts`:
363 
364`db/db.ts`
365 
366```ts
367import { chatsTable, messagesTable } from "@/db/schema"
368 
369const schema = {
370 chats: chatsTable,
371 messages: messagesTable
372}
373```
374 
375#### Server Actions
376 
377- When importing actions, use `@/actions` or `@/actions/db` if db related
378- DB related actions should go in the `actions/db` folder
379- Other actions should go in the `actions` folder
380- Name files like `example-actions.ts`
381- All actions should go in the `actions` folder
382- Only write the needed actions
383- Return an ActionState with the needed data type from actions
384- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
385- Actions should return a Promise<ActionState<T>>
386- Sort in CRUD order: Create, Read, Update, Delete
387- Make sure to return undefined as the data type if the action is not supposed to return any data
388- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
389 
390```ts
391export type ActionState<T> =
392 | { isSuccess: true; message: string; data: T }
393 | { isSuccess: false; message: string; data?: never }
394```
395 
396Example of an action:
397 
398`actions/db/todos-actions.ts`
399 
400```ts
401"use server"
402 
403import { db } from "@/db/db"
404import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
405import { ActionState } from "@/types"
406import { eq } from "drizzle-orm"
407 
408export async function createTodoAction(
409 todo: InsertTodo
410): Promise<ActionState<SelectTodo>> {
411 try {
412 const [newTodo] = await db.insert(todosTable).values(todo).returning()
413 return {
414 isSuccess: true,
415 message: "Todo created successfully",
416 data: newTodo
417 }
418 } catch (error) {
419 console.error("Error creating todo:", error)
420 return { isSuccess: false, message: "Failed to create todo" }
 
 
 
 
 
 
 
 
 
 
 
421 }
422}
423 
424export async function getTodosAction(
425 userId: string
426): Promise<ActionState<SelectTodo[]>> {
427 try {
428 const todos = await db.query.todos.findMany({
429 where: eq(todosTable.userId, userId)
 
 
 
 
 
430 })
431 return {
432 isSuccess: true,
433 message: "Todos retrieved successfully",
434 data: todos
435 }
436 } catch (error) {
437 console.error("Error getting todos:", error)
438 return { isSuccess: false, message: "Failed to get todos" }
439 }
440}
441 
442export async function updateTodoAction(
443 id: string,
444 data: Partial<InsertTodo>
445): Promise<ActionState<SelectTodo>> {
446 try {
447 const [updatedTodo] = await db
448 .update(todosTable)
449 .set(data)
450 .where(eq(todosTable.id, id))
451 .returning()
452 
453 return {
454 isSuccess: true,
455 message: "Todo updated successfully",
456 data: updatedTodo
457 }
458 } catch (error) {
459 console.error("Error updating todo:", error)
460 return { isSuccess: false, message: "Failed to update todo" }
461 }
462}
463 
464export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
465 try {
466 await db.delete(todosTable).where(eq(todosTable.id, id))
467 return {
468 isSuccess: true,
469 message: "Todo deleted successfully",
470 data: undefined
 
 
 
 
471 }
472 } catch (error) {
473 console.error("Error deleting todo:", error)
474 return { isSuccess: false, message: "Failed to delete todo" }
 
 
 
 
 
 
 
 
 
475 }
476}
477```
478 
479### Auth Rules
 
 
 
 
480 
481Follow these rules when working on auth.
 
 
 
 
 
482 
483It uses Clerk for authentication.
 
484 
485#### General Rules
486 
487- Import the auth helper with `import { auth } from "@clerk/nextjs/server"` in server components
488- await the auth helper in server actions
 
 
 
489 
490### Payments Rules
491 
492Follow these rules when working on payments.
493 
494It uses Stripe for payments.
495 
496### Analytics Rules
497 
498Follow these rules when working on analytics.
499 
500It uses PostHog for analytics.
501 
502# Storage Rules
503 
504Follow these rules when working with Supabase Storage.
505 
506It uses Supabase Storage for file uploads, downloads, and management.
507 
508## General Rules
509 
510- Always use environment variables for bucket names to maintain consistency across environments
511- Never hardcode bucket names in the application code
512- Always handle file size limits and allowed file types at the application level
513- Use the `upsert` method instead of `upload` when you want to replace existing files
514- Always implement proper error handling for storage operations
515- Use content-type headers when uploading files to ensure proper file handling
516 
517## Organization
518 
519### Buckets
520 
521- Name buckets in kebab-case: `user-uploads`, `profile-images`
522- Create separate buckets for different types of files (e.g., `profile-images`, `documents`, `attachments`)
523- Document bucket purposes in a central location
524- Set appropriate bucket policies (public/private) based on access requirements
525- Implement RLS (Row Level Security) policies for buckets that need user-specific access
526- Make sure to let me know instructions for setting up RLS policies on Supabase since you can't do this yourself, including the SQL scripts I need to run in the editor
527 
528### File Structure
529 
530- Organize files in folders based on their purpose and ownership
531- Use predictable, collision-resistant naming patterns
532- Structure: `{bucket}/{userId}/{purpose}/{filename}`
533- Example: `profile-images/123e4567-e89b/avatar/profile.jpg`
534- Include timestamps in filenames when version history is important
535- Example: `documents/123e4567-e89b/contracts/2024-02-13-contract.pdf`
536 
537## Actions
538 
539- When importing storage actions, use `@/actions/storage`
540- Name files like `example-storage-actions.ts`
541- Include Storage at the end of function names `Ex: uploadFile -> uploadFileStorage`
542- Follow the same ActionState pattern as DB actions
543 
544Example of a storage action:
545 
546```ts
547"use server"
548 
549import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
550import { ActionState } from "@/types"
551 
552export async function uploadFileStorage(
553 bucket: string,
554 path: string,
555 file: File
556): Promise<ActionState<{ path: string }>> {
557 try {
558 const supabase = createClientComponentClient()
559 
560 const { data, error } = await supabase.storage
561 .from(bucket)
562 .upload(path, file, {
563 upsert: false,
564 contentType: file.type
565 })
566 
567 if (error) throw error
568 
569 return {
570 isSuccess: true,
571 message: "File uploaded successfully",
572 data: { path: data.path }
573 }
574 } catch (error) {
575 console.error("Error uploading file:", error)
576 return { isSuccess: false, message: "Failed to upload file" }
577 }
578}
579```
580 
581## File Handling
 
582 
583### Upload Rules
 
 
584 
585- Always validate file size before upload
586- Implement file type validation using both extension and MIME type
587- Generate unique filenames to prevent collisions
588- Set appropriate content-type headers
589- Handle existing files appropriately (error or upsert)
590 
591Example validation:
592 
593```ts
594const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
595const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"]
596 
597function validateFile(file: File): boolean {
598 if (file.size > MAX_FILE_SIZE) {
599 throw new Error("File size exceeds limit")
600 }
601 
602 if (!ALLOWED_TYPES.includes(file.type)) {
603 throw new Error("File type not allowed")
604 }
 
 
 
 
 
 
605 
606 return true
607}
608```
 
 
 
 
 
 
 
609 
610### Download Rules
611 
612- Always handle missing files gracefully
613- Implement proper error handling for failed downloads
614- Use signed URLs for private files
 
 
 
 
 
 
 
 
615 
616### Delete Rules
 
 
 
 
 
 
 
 
617 
618- Implement soft deletes when appropriate
619- Clean up related database records when deleting files
620- Handle bulk deletions carefully
621- Verify ownership before deletion
622- Always delete all versions/transforms of a file
 
 
 
 
 
 
 
 
 
623 
624## Security
 
625 
626### Bucket Policies
627 
628- Make buckets private by default
629- Only make buckets public when absolutely necessary
630- Use RLS policies to restrict access to authorized users
631- Example RLS policy:
632 
633```sql
634CREATE POLICY "Users can only access their own files"
635ON storage.objects
636FOR ALL
637USING (auth.uid()::text = (storage.foldername(name))[1]);
638```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639 
640### Access Control
 
 
 
 
 
 
641 
642- Generate short-lived signed URLs for private files
643- Implement proper CORS policies
644- Use separate buckets for public and private files
645- Never expose internal file paths
646- Validate user permissions before any operation
647 
648## Error Handling
649 
650- Implement specific error types for common storage issues
651- Always provide meaningful error messages
652- Implement retry logic for transient failures
653- Log storage errors separately for monitoring
 
 
654 
655## Optimization
656 
657- Implement progressive upload for large files
658- Clean up temporary files and failed uploads
659- Use batch operations when handling multiple files
660 
@@ −1 +1 @@
1−---
2−description:
3−globs:
4−alwaysApply: false
5−---
6−Rule Name: coding-standards
7−Description:
8−This rule defines the coding standards and formatting guidelines that must be followed for all code changes in this project.
1+# Project Instructions
92  
10−<coding_format>
3+Use specification and guidelines as you build the app.
114  
12−A. Syntax & Structure
13−- File names must be dash-case (word-cloud.service.ts) unless an existing pattern differs.
14−- Group imports: node/standard → npm packages → internal paths. No unused imports.
15−- Use arrow functions everywhere except inside class bodies, where concise method syntax is allowed.
16−- Prefer early returns; nested if/else blocks deeper than two levels are disallowed.
17−- Early returns must use block format with braces (e.g., `if (!value) { return }`) for readability.
18−- Extract function call results as scope variables before using in conditions (e.g., `const trimmedText = text.trim(); if (!trimmedText) {...}` instead of `if (!text.trim()) {...}`).
19−- Use async/await—never chain .then().
20−- No .forEach for side effects; use for (const x of arr) instead.
21−- Array combinators (map, reduce, filter) are allowed only when you return their result.
22−- Identifiers must be English.
23−- No commented code allowed.
5+Write the complete code for every step. Do not get lazy.
246  
25−B. Functional-Programming Rules
26−- Each function must:
27− * Be ≤ 50 lines (preferably; extract helpers if longer).
28− * Take ≤ 4 parameters (optional ones last).
29− * Have a single responsibility.
30− * Be pure unless it is an intentional I/O wrapper (e.g. DB write); such wrappers must be ≤ 15 lines.
31− * Name functions with camelCase imperative verbs (calculateTotals, getUserById).
7+Your goal is to completely finish whatever I ask for.
328  
33−C. Type Safety & Error Handling
34−- Explicitly type all function parameters, return types, and exported constants.
35−- Type all local variables inside a function.
36−- **Special attention for async operations**: Variables from awaited functions (e.g., `const { userId } = await auth()`) must be explicitly typed, especially in Next.js components where auth results should use proper domain types.
37−- No any; if an external library forces it, wrap and narrow.
38−- Error handling in catch blocks:
39− - If the error variable is not used, use `catch {}` (no parameter).
40− - If the error is used, type it as `unknown` and handle it safely within the catch block.
9+You will see <ai_context> tags in the code. These are context tags that you should use to help you understand the codebase.
4110  
42−D. React Component Standards
11+## Overview
4312  
44−- Always define props with interfaces, never inline types
45−- Place interfaces directly above component definitions
46−- Use const arrow functions for component definitions
47−- Use implicit return syntax when components only return JSX (no logic before return)
48−- Export components using export default pattern (required for Next.js pages/layouts)
49−- Handler functions inside components must be ≤ 20 lines and have a single, clear responsibility. Extract helper functions for complex logic.
13+This is a web app template.
5014  
51− **Wrong (~50 lines in one handler):**
52− ```tsx
53− const handleFormSubmit = async (): Promise<void> => {
54− const trimmedName: string = formData.name.trim()
55− const trimmedEmail: string = formData.email.trim()
56− const trimmedMessage: string = formData.message.trim()
57−
58− if (!trimmedName) {
59− setErrors({ ...errors, name: "Name is required" })
60− toast({ title: "Error", description: "Name is required", variant: "destructive" })
61− return
62− }
63−
64− if (!trimmedEmail || !trimmedEmail.includes("@")) {
65− setErrors({ ...errors, email: "Valid email is required" })
66− toast({ title: "Error", description: "Valid email is required", variant: "destructive" })
67− return
68− }
69−
70− if (!trimmedMessage || trimmedMessage.length < 10) {
71− setErrors({ ...errors, message: "Message must be at least 10 characters" })
72− toast({ title: "Error", description: "Message too short", variant: "destructive" })
73− return
74− }
75−
76− setIsSubmitting(true)
77− setErrors({})
78−
79− try {
80− const payload: FormPayload = {
81− name: trimmedName,
82− email: trimmedEmail,
83− message: trimmedMessage,
84− timestamp: new Date().toISOString()
85− }
86−
87− const response: Response = await fetch("/api/contact", {
88− method: "POST",
89− headers: { "Content-Type": "application/json" },
90− body: JSON.stringify(payload)
91− })
92−
93− if (!response.ok) {
94− throw new Error("Failed to submit")
95− }
96−
97− const result: SubmissionResult = await response.json()
98−
99− setFormData({ name: "", email: "", message: "" })
100− setSubmissionCount(prev => prev + 1)
101−
102− toast({ title: "Success", description: "Message sent successfully!" })
103−
104− if (onSuccess) {
105− onSuccess(result)
106− }
107− } catch (error: unknown) {
108− const errorMessage: string = error instanceof Error ? error.message : "Unknown error"
109− console.error("Submission error:", errorMessage)
110− setErrors({ submit: "Failed to send message" })
111− toast({ title: "Error", description: "Failed to send message", variant: "destructive" })
112− } finally {
113− setIsSubmitting(false)
114− }
115− }
116− ```
15+## Tech Stack
11716  
118− **Good (broken into focused helpers ≤ 20 lines each):**
119− ```tsx
120− const validateForm = (): boolean => {
121− const trimmedName: string = formData.name.trim()
122− const trimmedEmail: string = formData.email.trim()
123− const trimmedMessage: string = formData.message.trim()
124−
125− if (!trimmedName) {
126− setErrors({ ...errors, name: "Name is required" })
127− toast({ title: "Error", description: "Name is required", variant: "destructive" })
128− return false
17+- Frontend: Next.js, Tailwind, Shadcn, Framer Motion
18+- Backend: Postgres, Supabase, Drizzle ORM, Server Actions
19+- Auth: Clerk
20+- Payments: Stripe
21+- Analytics: PostHog
22+- Deployment: Vercel
23+ 
24+## Project Structure
25+ 
26+- `actions` - Server actions
27+ - `db` - Database related actions
28+ - Other actions
29+- `app` - Next.js app router
30+ - `api` - API routes
31+ - `route` - An example route
32+ - `_components` - One-off components for the route
33+ - `layout.tsx` - Layout for the route
34+ - `page.tsx` - Page for the route
35+- `components` - Shared components
36+ - `ui` - UI components
37+ - `utilities` - Utility components
38+- `db` - Database
39+ - `schema` - Database schemas
40+- `lib` - Library code
41+ - `hooks` - Custom hooks
42+ - `services` - Business logic services
43+- `prompts` - Prompt files
44+- `public` - Static assets
45+- `types` - Type definitions
46+ 
47+## Rules
48+ 
49+Follow these rules when building the app.
50+ 
51+### General Rules
52+ 
53+- Use `@` to import anything from the app unless otherwise specified
54+- Use kebab case for all files and folders unless otherwise specified
55+- Don't update shadcn components unless otherwise specified
56+ 
57+#### Env Rules
58+ 
59+- If you update environment variables, update the `.env.example` file
60+- All environment variables should go in `.env.local`
61+- Do not expose environment variables to the frontend
62+- Use `NEXT_PUBLIC_` prefix for environment variables that need to be accessed from the frontend
63+- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
64+ 
65+#### Type Rules
66+ 
67+Follow these rules when working with types.
68+ 
69+- When importing types, use `@/types`
70+- Name files like `example-types.ts`
71+- All types should go in `types`
72+- Make sure to export the types in `types/index.ts`
73+- Prefer interfaces over type aliases
74+- If referring to db types, use `@/db/schema` such as `SelectTodo` from `todos-schema.ts`
75+ 
76+An example of a type:
77+ 
78+`types/actions-types.ts`
79+ 
80+```ts
81+export type ActionState<T> =
82+ | { isSuccess: true; message: string; data: T }
83+ | { isSuccess: false; message: string; data?: never }
84+```
85+ 
86+And exporting it:
87+ 
88+`types/index.ts`
89+ 
90+```ts
91+export * from "./actions-types"
92+```
93+ 
94+### Frontend Rules
95+ 
96+Follow these rules when working on the frontend.
97+ 
98+It uses Next.js, Tailwind, Shadcn, and Framer Motion.
99+ 
100+#### General Rules
101+ 
102+- Use `lucide-react` for icons
103+- useSidebar must be used within a SidebarProvider
104+ 
105+#### Components
106+ 
107+- Use divs instead of other html tags unless otherwise specified
108+- Separate the main parts of a component's html with an extra blank line for visual spacing
109+- Always tag a component with either `use server` or `use client` at the top, including layouts and pages
110+ 
111+##### Organization
112+ 
113+- All components be named using kebab case like `example-component.tsx` unless otherwise specified
114+- Put components in `/_components` in the route if one-off components
115+- Put components in `/components` from the root if shared components
116+ 
117+##### Data Fetching
118+ 
119+- Fetch data in server components and pass the data down as props to client components.
120+- Use server actions from `/actions` to mutate data.
121+ 
122+##### Server Components
123+ 
124+- Use `"use server"` at the top of the file.
125+- Implement Suspense for asynchronous data fetching to show loading states while data is being fetched.
126+- If no asynchronous logic is required for a given server component, you do not need to wrap the component in `<Suspense>`. You can simply return the final UI directly since there is no async boundary needed.
127+- If asynchronous fetching is required, you can use a `<Suspense>` boundary and a fallback to indicate a loading state while data is loading.
128+- Server components cannot be imported into client components. If you want to use a server component in a client component, you must pass the as props using the "children" prop
129+- params in server pages should be awaited such as `const { courseId } = await params` where the type is `params: Promise<{ courseId: string }>`
130+ 
131+Example of a server layout:
132+ 
133+```tsx
134+"use server"
135+ 
136+export default async function ExampleServerLayout({
137+ children
138+}: {
139+ children: React.ReactNode
140+}) {
141+ return children
142+}
143+```
144+ 
145+Example of a server page (with async logic):
146+ 
147+```tsx
148+"use server"
149+ 
150+import { Suspense } from "react"
151+import { SomeAction } from "@/actions/some-actions"
152+import SomeComponent from "./_components/some-component"
153+import SomeSkeleton from "./_components/some-skeleton"
154+ 
155+export default async function ExampleServerPage() {
156+ return (
157+ <Suspense fallback={<SomeSkeleton className="some-class" />}>
158+ <SomeComponentFetcher />
159+ </Suspense>
160+ )
161+}
162+ 
163+async function SomeComponentFetcher() {
164+ const { data } = await SomeAction()
165+ return <SomeComponent className="some-class" initialData={data || []} />
166+}
167+```
168+ 
169+Example of a server page (no async logic required):
170+ 
171+```tsx
172+"use server"
173+ 
174+import SomeClientComponent from "./_components/some-client-component"
175+ 
176+// In this case, no asynchronous work is being done, so no Suspense or fallback is required.
177+export default async function ExampleServerPage() {
178+ return <SomeClientComponent initialData={[]} />
179+}
180+```
181+ 
182+Example of a server component:
183+ 
184+```tsx
185+"use server"
186+ 
187+interface ExampleServerComponentProps {
188+ // Your props here
189+}
190+ 
191+export async function ExampleServerComponent({
192+ props
193+}: ExampleServerComponentProps) {
194+ // Your code here
195+}
196+```
197+ 
198+##### Client Components
199+ 
200+- Use `"use client"` at the top of the file
201+- Client components can safely rely on props passed down from server components, or handle UI interactions without needing <Suspense> if there's no async logic.
202+- Never use server actions in client components. If you need to create a new server action, create it in `/actions`
203+ 
204+Example of a client page:
205+ 
206+```tsx
207+"use client"
208+ 
209+export default function ExampleClientPage() {
210+ // Your code here
211+}
212+```
213+ 
214+Example of a client component:
215+ 
216+```tsx
217+"use client"
218+ 
219+interface ExampleClientComponentProps {
220+ initialData: any[]
221+}
222+ 
223+export default function ExampleClientComponent({
224+ initialData
225+}: ExampleClientComponentProps) {
226+ // Client-side logic here
227+ return <div>{initialData.length} items</div>
228+}
229+```
230+ 
231+### Backend Rules
232+ 
233+Follow these rules when working on the backend.
234+ 
235+It uses Postgres, Supabase, Drizzle ORM, and Server Actions.
236+ 
237+#### General Rules
238+ 
239+- Never generate migrations. You do not have to do anything in the `db/migrations` folder inluding migrations and metadata. Ignore it.
240+ 
241+#### Organization
242+ 
243+#### Schemas
244+ 
245+- When importing schemas, use `@/db/schema`
246+- Name files like `example-schema.ts`
247+- All schemas should go in `db/schema`
248+- Make sure to export the schema in `db/schema/index.ts`
249+- Make sure to add the schema to the `schema` object in `db/db.ts`
250+- If using a userId, always use `userId: text("user_id").notNull()`
251+- Always include createdAt and updatedAt columns in all tables
252+- Make sure to cascade delete when necessary
253+- Use enums for columns that have a limited set of possible values such as:
254+ 
255+```ts
256+import { pgEnum } from "drizzle-orm/pg-core"
257+ 
258+export const MEMBERSHIP: PgEnum<Membership> = pgEnum(
259+ "membership",
260+ MEMBERSHIP_VALUES
261+)
262+ 
263+membership: MEMBERSHIP("membership").notNull().default("free")
264+```
265+ 
266+Example of a schema:
267+ 
268+`db/schema/todos-schema.ts`
269+ 
270+```ts
271+import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
272+ 
273+export const todosTable = pgTable("todos", {
274+ id: uuid("id").defaultRandom().primaryKey(),
275+ userId: text("user_id").notNull(),
276+ content: text("content").notNull(),
277+ completed: boolean("completed").default(false).notNull(),
278+ createdAt: timestamp("created_at").defaultNow().notNull(),
279+ updatedAt: timestamp("updated_at")
280+ .defaultNow()
281+ .notNull()
282+ .$onUpdate(() => new Date())
283+})
284+ 
285+export type InsertTodo = typeof todosTable.$inferInsert
286+export type SelectTodo = typeof todosTable.$inferSelect
287+```
288+ 
289+And exporting it:
290+ 
291+`db/schema/index.ts`
292+ 
293+```ts
294+export * from "./todos-schema"
295+```
296+ 
297+And adding it to the schema in `db/db.ts`:
298+ 
299+`db/db.ts`
300+ 
301+```ts
302+import { todosTable } from "@/db/schema"
303+ 
304+const schema = {
305+ todos: todosTable
306+}
307+```
308+ 
309+And a more complex schema:
310+ 
311+```ts
312+import { pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
313+ 
314+export const chatsTable = pgTable("chats", {
315+ id: uuid("id").defaultRandom().primaryKey(),
316+ userId: text("user_id").notNull(),
317+ name: text("name").notNull(),
318+ createdAt: timestamp("created_at").defaultNow().notNull(),
319+ updatedAt: timestamp("updated_at")
320+ .defaultNow()
321+ .notNull()
322+ .$onUpdate(() => new Date())
323+})
324+ 
325+export type InsertChat = typeof chatsTable.$inferInsert
326+export type SelectChat = typeof chatsTable.$inferSelect
327+```
328+ 
329+```ts
330+import { pgEnum, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"
331+import { chatsTable } from "./chats-schema"
332+ 
333+export const roleEnum = pgEnum("role", ["assistant", "user"])
334+ 
335+export const messagesTable = pgTable("messages", {
336+ id: uuid("id").defaultRandom().primaryKey(),
337+ chatId: uuid("chat_id")
338+ .references(() => chatsTable.id, { onDelete: "cascade" })
339+ .notNull(),
340+ content: text("content").notNull(),
341+ role: roleEnum("role").notNull(),
342+ createdAt: timestamp("created_at").defaultNow().notNull(),
343+ updatedAt: timestamp("updated_at")
344+ .defaultNow()
345+ .notNull()
346+ .$onUpdate(() => new Date())
347+})
348+ 
349+export type InsertMessage = typeof messagesTable.$inferInsert
350+export type SelectMessage = typeof messagesTable.$inferSelect
351+```
352+ 
353+And exporting it:
354+ 
355+`db/schema/index.ts`
356+ 
357+```ts
358+export * from "./chats-schema"
359+export * from "./messages-schema"
360+```
361+ 
362+And adding it to the schema in `db/db.ts`:
363+ 
364+`db/db.ts`
365+ 
366+```ts
367+import { chatsTable, messagesTable } from "@/db/schema"
368+ 
369+const schema = {
370+ chats: chatsTable,
371+ messages: messagesTable
372+}
373+```
374+ 
375+#### Server Actions
376+ 
377+- When importing actions, use `@/actions` or `@/actions/db` if db related
378+- DB related actions should go in the `actions/db` folder
379+- Other actions should go in the `actions` folder
380+- Name files like `example-actions.ts`
381+- All actions should go in the `actions` folder
382+- Only write the needed actions
383+- Return an ActionState with the needed data type from actions
384+- Include Action at the end of function names `Ex: exampleFunction -> exampleFunctionAction`
385+- Actions should return a Promise<ActionState<T>>
386+- Sort in CRUD order: Create, Read, Update, Delete
387+- Make sure to return undefined as the data type if the action is not supposed to return any data
388+- **Date Handling:** For columns defined as `PgDateString` (or any date string type), always convert JavaScript `Date` objects to ISO strings using `.toISOString()` before performing operations (e.g., comparisons or insertions). This ensures value type consistency and prevents type errors.
389+ 
390+```ts
391+export type ActionState<T> =
392+ | { isSuccess: true; message: string; data: T }
393+ | { isSuccess: false; message: string; data?: never }
394+```
395+ 
396+Example of an action:
397+ 
398+`actions/db/todos-actions.ts`
399+ 
400+```ts
401+"use server"
402+ 
403+import { db } from "@/db/db"
404+import { InsertTodo, SelectTodo, todosTable } from "@/db/schema/todos-schema"
405+import { ActionState } from "@/types"
406+import { eq } from "drizzle-orm"
407+ 
408+export async function createTodoAction(
409+ todo: InsertTodo
410+): Promise<ActionState<SelectTodo>> {
411+ try {
412+ const [newTodo] = await db.insert(todosTable).values(todo).returning()
413+ return {
414+ isSuccess: true,
415+ message: "Todo created successfully",
416+ data: newTodo
129417 }
130−
131− if (!trimmedEmail || !trimmedEmail.includes("@")) {
132− setErrors({ ...errors, email: "Valid email is required" })
133− toast({ title: "Error", description: "Valid email is required", variant: "destructive" })
134− return false
135− }
136−
137− if (!trimmedMessage || trimmedMessage.length < 10) {
138− setErrors({ ...errors, message: "Message must be at least 10 characters" })
139− toast({ title: "Error", description: "Message too short", variant: "destructive" })
140− return false
141− }
142−
143− return true
418+ } catch (error) {
419+ console.error("Error creating todo:", error)
420+ return { isSuccess: false, message: "Failed to create todo" }
144421 }
145−
146− const submitForm = async (): Promise<SubmissionResult> => {
147− const payload: FormPayload = {
148− name: formData.name.trim(),
149− email: formData.email.trim(),
150− message: formData.message.trim(),
151− timestamp: new Date().toISOString()
152− }
153−
154− const response: Response = await fetch("/api/contact", {
155− method: "POST",
156− headers: { "Content-Type": "application/json" },
157− body: JSON.stringify(payload)
422+}
423+ 
424+export async function getTodosAction(
425+ userId: string
426+): Promise<ActionState<SelectTodo[]>> {
427+ try {
428+ const todos = await db.query.todos.findMany({
429+ where: eq(todosTable.userId, userId)
158430 })
159−
160− if (!response.ok) {
161− throw new Error("Failed to submit")
431+ return {
432+ isSuccess: true,
433+ message: "Todos retrieved successfully",
434+ data: todos
162435 }
163−
164− return response.json()
436+ } catch (error) {
437+ console.error("Error getting todos:", error)
438+ return { isSuccess: false, message: "Failed to get todos" }
165439 }
166−
167− const handleSuccess = (result: SubmissionResult): void => {
168− setFormData({ name: "", email: "", message: "" })
169− setSubmissionCount((prev: number) => prev + 1)
170− toast({ title: "Success", description: "Message sent successfully!" })
171−
172− if (onSuccess) {
173− onSuccess(result)
440+}
441+ 
442+export async function updateTodoAction(
443+ id: string,
444+ data: Partial<InsertTodo>
445+): Promise<ActionState<SelectTodo>> {
446+ try {
447+ const [updatedTodo] = await db
448+ .update(todosTable)
449+ .set(data)
450+ .where(eq(todosTable.id, id))
451+ .returning()
452+ 
453+ return {
454+ isSuccess: true,
455+ message: "Todo updated successfully",
456+ data: updatedTodo
174457 }
458+ } catch (error) {
459+ console.error("Error updating todo:", error)
460+ return { isSuccess: false, message: "Failed to update todo" }
175461 }
176−
177− const handleError = (error: unknown): void => {
178− const errorMessage: string = error instanceof Error ? error.message : "Unknown error"
179− console.error("Submission error:", errorMessage)
180− setErrors({ submit: "Failed to send message" })
181− toast({ title: "Error", description: "Failed to send message", variant: "destructive" })
182− }
183−
184− const handleFormSubmit = async (): Promise<void> => {
185− const isValid: boolean = validateForm()
186−
187− if (!isValid) {
188− return
462+}
463+ 
464+export async function deleteTodoAction(id: string): Promise<ActionState<void>> {
465+ try {
466+ await db.delete(todosTable).where(eq(todosTable.id, id))
467+ return {
468+ isSuccess: true,
469+ message: "Todo deleted successfully",
470+ data: undefined
189471 }
190−
191− setIsSubmitting(true)
192− setErrors({})
193−
194− try {
195− const result: SubmissionResult = await submitForm()
196− handleSuccess(result)
197− } catch (error: unknown) {
198− handleError(error)
199− } finally {
200− setIsSubmitting(false)
201− }
472+ } catch (error) {
473+ console.error("Error deleting todo:", error)
474+ return { isSuccess: false, message: "Failed to delete todo" }
202475 }
203− ```
204−- Example with implicit return:
476+}
477+```
205478  
206− ```tsx
207− interface MyComponentProps {
208− title: string
209− children: React.ReactNode
210− }
479+### Auth Rules
211480  
212− const MyComponent = ({ title, children }: MyComponentProps) => (
213− <div>
214− {title}
215− {children}
216− </div>
217− )
481+Follow these rules when working on auth.
218482  
219− export default MyComponent
220− ```
483+It uses Clerk for authentication.
221484  
222−- Example with explicit return (when logic is present):
485+#### General Rules
223486  
224− ```tsx
225− interface MyComponentProps {
226− title: string
227− children: React.ReactNode
228− }
487+- Import the auth helper with `import { auth } from "@clerk/nextjs/server"` in server components
488+- await the auth helper in server actions
229489  
230− const MyComponent = ({ title, children }: MyComponentProps) => {
231− const processedTitle = title.toUpperCase()
232−
233− return (
234− <div>
235− {processedTitle}
236− {children}
237− </div>
238− )
490+### Payments Rules
491+ 
492+Follow these rules when working on payments.
493+ 
494+It uses Stripe for payments.
495+ 
496+### Analytics Rules
497+ 
498+Follow these rules when working on analytics.
499+ 
500+It uses PostHog for analytics.
501+ 
502+# Storage Rules
503+ 
504+Follow these rules when working with Supabase Storage.
505+ 
506+It uses Supabase Storage for file uploads, downloads, and management.
507+ 
508+## General Rules
509+ 
510+- Always use environment variables for bucket names to maintain consistency across environments
511+- Never hardcode bucket names in the application code
512+- Always handle file size limits and allowed file types at the application level
513+- Use the `upsert` method instead of `upload` when you want to replace existing files
514+- Always implement proper error handling for storage operations
515+- Use content-type headers when uploading files to ensure proper file handling
516+ 
517+## Organization
518+ 
519+### Buckets
520+ 
521+- Name buckets in kebab-case: `user-uploads`, `profile-images`
522+- Create separate buckets for different types of files (e.g., `profile-images`, `documents`, `attachments`)
523+- Document bucket purposes in a central location
524+- Set appropriate bucket policies (public/private) based on access requirements
525+- Implement RLS (Row Level Security) policies for buckets that need user-specific access
526+- Make sure to let me know instructions for setting up RLS policies on Supabase since you can't do this yourself, including the SQL scripts I need to run in the editor
527+ 
528+### File Structure
529+ 
530+- Organize files in folders based on their purpose and ownership
531+- Use predictable, collision-resistant naming patterns
532+- Structure: `{bucket}/{userId}/{purpose}/{filename}`
533+- Example: `profile-images/123e4567-e89b/avatar/profile.jpg`
534+- Include timestamps in filenames when version history is important
535+- Example: `documents/123e4567-e89b/contracts/2024-02-13-contract.pdf`
536+ 
537+## Actions
538+ 
539+- When importing storage actions, use `@/actions/storage`
540+- Name files like `example-storage-actions.ts`
541+- Include Storage at the end of function names `Ex: uploadFile -> uploadFileStorage`
542+- Follow the same ActionState pattern as DB actions
543+ 
544+Example of a storage action:
545+ 
546+```ts
547+"use server"
548+ 
549+import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"
550+import { ActionState } from "@/types"
551+ 
552+export async function uploadFileStorage(
553+ bucket: string,
554+ path: string,
555+ file: File
556+): Promise<ActionState<{ path: string }>> {
557+ try {
558+ const supabase = createClientComponentClient()
559+ 
560+ const { data, error } = await supabase.storage
561+ .from(bucket)
562+ .upload(path, file, {
563+ upsert: false,
564+ contentType: file.type
565+ })
566+ 
567+ if (error) throw error
568+ 
569+ return {
570+ isSuccess: true,
571+ message: "File uploaded successfully",
572+ data: { path: data.path }
573+ }
574+ } catch (error) {
575+ console.error("Error uploading file:", error)
576+ return { isSuccess: false, message: "Failed to upload file" }
239577 }
578+}
579+```
240580  
241− export default MyComponent
242− ```
581+## File Handling
243582  
244−- Normal components that are not Next.js pages/layouts should be exported
245− using export const pattern
246−- Example with implicit return:
583+### Upload Rules
247584  
248− ```tsx
249− interface NotAPageOrLayoutComponentProps {
250− title: string
251− children: React.ReactNode
585+- Always validate file size before upload
586+- Implement file type validation using both extension and MIME type
587+- Generate unique filenames to prevent collisions
588+- Set appropriate content-type headers
589+- Handle existing files appropriately (error or upsert)
590+ 
591+Example validation:
592+ 
593+```ts
594+const MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB
595+const ALLOWED_TYPES = ["image/jpeg", "image/png", "image/webp"]
596+ 
597+function validateFile(file: File): boolean {
598+ if (file.size > MAX_FILE_SIZE) {
599+ throw new Error("File size exceeds limit")
252600 }
253601  
254− export const NotAPageOrLayoutComponent = ({
255− title,
256− children
257− }: NotAPageOrLayoutComponentProps) => (
258− <div>
259− {title}
260− {children}
261− </div>
262− )
602+ if (!ALLOWED_TYPES.includes(file.type)) {
603+ throw new Error("File type not allowed")
604+ }
263605  
264−E. Component Granularity & Organization
265−- Break down large components into smaller, focused components for better maintainability.
266−- When a component contains multiple logical sections (e.g., Card with CardHeader + CardContent), extract each section into separate components.
267−- Create dedicated folders for related component groups:
268− * Use kebab-case folder names matching the main component concept
269− * Place related sub-components within the same folder using kebab-case file names
270− * Example structure: `component-name/component-name-header.tsx`, `component-name/component-name-content.tsx`
271−- Each sub-component should have a single, clear responsibility.
272−- Maintain the parent component as a composition wrapper that orchestrates child components.
273−- Follow this pattern when refactoring existing components or creating new feature components.
606+ return true
607+}
608+```
274609  
275−F. Advanced Component Architecture Patterns
610+### Download Rules
276611  
277−F.1. Pure Functions and Constants Organization
278−- **Pure functions** (no side effects, deterministic output) must be extracted outside components:
279− * Place above the component definition
280− * Examples: `getGreeting()`, `getMembershipBadgeColor()`, `formatDate()`
281−- **Constants and static data** must be moved outside components:
282− * Place after imports and interfaces, before pure functions
283− * Use SCREAMING_SNAKE_CASE for naming (e.g., `TEMPLATE_FEATURES`, `TECH_STACK`)
284− * **Always explicitly type constants** with appropriate type annotations
285− * Examples: `const API_URL: string = "..."`, `const MAX_RETRIES: number = 3`
286− * Use `as const` for immutable values when type inference is sufficient
287− * Group related constants together
612+- Always handle missing files gracefully
613+- Implement proper error handling for failed downloads
614+- Use signed URLs for private files
288615  
289−F.1.1. Custom Hooks Organization
290−- **Custom hooks** must be extracted to separate files in the `/hooks/` directory:
291− * Use kebab-case file naming: `use-scroll-detection.ts`, `use-local-storage.ts`
292− * Start hook names with `use` prefix following React conventions
293− * Place hooks in `/hooks/` folder at project root level
294− * Export hooks using named exports: `export const useScrollDetection = () => {}`
295− * **Always explicitly type hook return values** and parameters
296− * Examples: `useScrollDetection(): boolean`, `useLocalStorage<T>(key: string): [T, (value: T) => void]`
297− * Group related hooks in the same file only if they're tightly coupled
616+### Delete Rules
298617  
299−F.1.2. Whitespace and Formatting Rules
300−- **Component variable organization**: Maintain consistent whitespace between different types of declarations:
301− * Add a blank line between React state declarations and custom hook calls
302− * Add a blank line between custom hook calls and other variable declarations
303− * Example:
304− ```tsx
305− const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false)
306− const [isVisible, setIsVisible] = useState<boolean>(true)
307−
308− const isScrolled: boolean = useScrollDetection()
309− const userData: UserData = useUserData()
310−
311− const processedData: ProcessedData = processUserData(userData)
312− ```
618+- Implement soft deletes when appropriate
619+- Clean up related database records when deleting files
620+- Handle bulk deletions carefully
621+- Verify ownership before deletion
622+- Always delete all versions/transforms of a file
313623  
314−F.2. Nested Component Structure for Complex Components
315−When a component has multiple distinct sections, create nested folder structure:
624+## Security
316625  
626+### Bucket Policies
627+ 
628+- Make buckets private by default
629+- Only make buckets public when absolutely necessary
630+- Use RLS policies to restrict access to authorized users
631+- Example RLS policy:
632+ 
633+```sql
634+CREATE POLICY "Users can only access their own files"
635+ON storage.objects
636+FOR ALL
637+USING (auth.uid()::text = (storage.foldername(name))[1]);
317638 ```
318−dashboard-welcome/
319−├── dashboard-welcome.tsx // Main orchestrator component
320−├── greeting.tsx // Self-contained greeting section
321−├── whats-included/ // Folder for multi-part section
322−│ ├── whats-included.tsx // Section orchestrator
323−│ ├── whats-included-title.tsx // Title sub-component
324−│ └── whats-included-features.tsx // Features list sub-component
325−├── core-technologies/ // Folder for multi-part section
326−│ ├── core-technologies.tsx // Section orchestrator
327−│ ├── core-technologies-title.tsx // Title sub-component
328−│ └── core-technologies-list.tsx // Tech list sub-component
329−└── get-started/ // Folder for multi-part section
330− ├── get-started.tsx // Section orchestrator
331− ├── get-started-title.tsx // Title sub-component
332− ├── get-started-features.tsx // Features grid sub-component
333− └── get-started-feature-2.tsx // Individual feature card
334−```
335639  
336−F.3. Component Organization Rules
337−1. **Main orchestrator**: Composition only, minimal logic, imports and renders sub-components
338−2. **Section orchestrators**: Handle section-specific logic, render related sub-components
339−3. **Leaf components**: Single responsibility, pure presentation, accept props only
340−4. **Shared constants**: Extract to file level, use proper naming conventions
341−5. **Pure functions**: Extract above component definitions, properly typed
342−6. **File structure**: Mirror logical component hierarchy in folder structure
640+### Access Control
343641  
344−</coding_format>
642+- Generate short-lived signed URLs for private files
643+- Implement proper CORS policies
644+- Use separate buckets for public and private files
645+- Never expose internal file paths
646+- Validate user permissions before any operation
345647  
346−<usage_guidelines>
648+## Error Handling
347649  
348−1. Apply these standards to all new code and when refactoring existing code.
349−2. When making any code changes, ensure they conform to these guidelines.
350−3. If existing code doesn't follow these standards, update it to comply when modifying those files.
351−4. Use these standards as a checklist when reviewing code changes.
352−5. Prefer extracting helper functions over writing long, complex functions.
353−6. Always prioritize code readability and maintainability.
650+- Implement specific error types for common storage issues
651+- Always provide meaningful error messages
652+- Implement retry logic for transient failures
653+- Log storage errors separately for monitoring
354654  
355−</usage_guidelines>
655+## Optimization
656+ 
657+- Implement progressive upload for large files
658+- Clean up temporary files and failed uploads
659+- Use batch operations when handling multiple files
660+ 
RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack

RuleStack

Built by

Kynth Studio

Directory

Configs
Stacks
Compare formats
Diff two configs
Best AGENTS.md examples

Formats

AGENTS.md
CLAUDE.md
Cursor rules
Copilot instructions

Reference

Read API
Corpus health
Privacy Policy
Terms

RuleStack