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-general ↔ 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
Sections601924%
Commands000—
Section tags500100%

What each file covers

Sections

6 shared · 0 only in A · 19 only in B
  • + 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
  •   Project Instructions
  •   Overview
  •   Tech Stack
  •   Project Structure
  •   Rules
  •   General Rules

Commands

neither file has any

Section tags

5 shared · 0 only in A · 0 only in B
  •   code-style
  •   architecture
  •   security
  •   do-not
  •   agent-behaviour

Line diff

+565 added−7 removed95 unchanged14.4% identical
sportiz91/vibe-template · .cursor/rules/general.mdc
@@ −1 @@
1---
2description: Follow these rules for all requests.
3globs:
4alwaysApply: false
5---
6# Project Instructions
7# Project Instructions
8 
9Use specification and guidelines as you build the app.
10 
@@ −97 @@
97export * from "./actions-types"
98```
99 
100- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
101 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102 
sportiz91/vibe-template · .cursorrules
@@ +1 @@
 
 
 
 
 
1# Project Instructions
 
2 
3Use specification and guidelines as you build the app.
4 
@@ +91 @@
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: Follow these rules for all requests.
3−globs:
4−alwaysApply: false
5−---
61 # Project Instructions
7−# Project Instructions
82  
93 Use specification and guidelines as you build the app.
104  
@@ −97 +91 @@
9791 export * from "./actions-types"
9892 ```
9993  
100−- Always access environment variables via the centralized config module (`serverConfig` and `publicEnv` from `@/lib/config`). Do not use `process.env` directly in application code.
94+### Frontend Rules
10195  
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
417+ }
418+ } catch (error) {
419+ console.error("Error creating todo:", error)
420+ return { isSuccess: false, message: "Failed to create todo" }
421+ }
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)
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+ 
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
457+ }
458+ } catch (error) {
459+ console.error("Error updating todo:", error)
460+ return { isSuccess: false, message: "Failed to update todo" }
461+ }
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
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+ 
481+Follow these rules when working on auth.
482+ 
483+It 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+ 
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" }
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+ 
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")
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
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]);
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
102660  
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